From f8f8306c55f29a4782ac880aebd4c271bc0c0679 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 1 Feb 2018 15:39:36 +0000 Subject: [PATCH 001/648] Add target to collect the js and golang dependencies for upload to OBS (#1427) * Add target to collect the js and golang dependencies for upload to OBS * Fix lint issue * Replace use of fs with fsx --- .gitignore | 1 + build/bk-build.js | 5 +- build/deps.gulp.js | 116 ++++++++++++++++++ build/main.gulp.js | 1 + .../gogo/protobuf/.stratos-dependency | 3 + .../cli/.stratos-dependency | 3 + .../gofileutils/.stratos-dependency | 3 + package.json | 1 + 8 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 build/deps.gulp.js create mode 100644 components/app-core/backend/__vendor/github.com/gogo/protobuf/.stratos-dependency create mode 100644 components/cf-app-push/backend/__vendor/code.cloudfoundry.org/cli/.stratos-dependency create mode 100644 components/cf-app-push/backend/__vendor/code.cloudfoundry.org/gofileutils/.stratos-dependency diff --git a/.gitignore b/.gitignore index d7f917f554..f9bdab9104 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ vendor/ **/vendor **/*.so .glide +.deps/ tools/ssl build/secrets.json diff --git a/build/bk-build.js b/build/bk-build.js index c940f5b0ba..c387dac012 100644 --- a/build/bk-build.js +++ b/build/bk-build.js @@ -350,10 +350,11 @@ 'delete-temp' ); }); - gulp.task('cf-get-backend-deps', function () { + gulp.task('cf-get-backend-deps', function (cb) { return runSequence( 'init-build', - 'dedup-vendor' + 'dedup-vendor', + cb ); }); diff --git a/build/deps.gulp.js b/build/deps.gulp.js new file mode 100644 index 0000000000..677b776161 --- /dev/null +++ b/build/deps.gulp.js @@ -0,0 +1,116 @@ +/* eslint-disable angular/json-functions,angular/log,no-console,no-process-env,no-process-exit,no-sync */ +(function () { + 'use strict'; + + // Gulp tasks for dumping the dependencies + var _ = require('lodash'); + var gulp = require('gulp'); + var path = require('path'); + var runSequence = require('run-sequence'); + var fsx = require('fs-extra'); + + var components; + var depsFolder = path.resolve('./.deps'); + + gulp.task('dump-deps-backend-prep', function (cb) { + process.env.STRATOS_TEMP = path.resolve(__dirname, '../tmp'); + fsx.mkdirpSync(process.env.STRATOS_TEMP); + return cb(); + }); + + gulp.task('dump-deps', function () { + components = require('./components'); + components.initialize(); + fsx.ensureDirSync(depsFolder); + fsx.emptyDirSync(depsFolder); + return runSequence( + 'dump-deps-frontend', + 'dump-deps-backend-prep', + 'cf-get-backend-deps', + 'dump-deps-backend' + ); + }); + + gulp.task('dump-deps-frontend', function (cb) { + console.log('Dumping front-end dependencies'); + var folders = fsx.readdirSync(components.getBowerFolder()); + var localFolders = _.keys(components.findLocalPathComponentFolders()); + + // Exclude local components + var deps = _.difference(folders, localFolders); + _.each(deps, function (name) { + var folder = path.join(components.getBowerFolder(), name); + if (fsx.lstatSync(folder).isDirectory()) { + console.log('Storing dependency: ' + name); + var cmpBower = JSON.parse(fsx.readFileSync(path.join(folder, '.bower.json'), 'utf8')); + var depName = 'js-' + cmpBower.name; + fsx.copySync(folder, path.join(depsFolder, depName)); + + var version = cmpBower.version; + var commit = cmpBower._resolution ? cmpBower._resolution.commit || version : version; + // Write the version and name to a file + fsx.writeFileSync(path.join(depsFolder, depName, '.stratos-dependency'), cmpBower.name + '\n' + commit + '\n' + version); + } + }); + + return cb(); + }); + + function replaceAll(target, search, replacement) { + return target.split(search).join(replacement); + } + + function findStratosDepFiles(results, folder) { + var strat = path.join(folder, '.stratos-dependency'); + if (fsx.existsSync(strat)) { + var res = { + depFile: strat, + folder: folder + }; + results.push(res); + } + + var items = fsx.readdirSync(folder); + _.each(items, function (f) { + if (fsx.lstatSync(path.join(folder, f)).isDirectory()) { + findStratosDepFiles(results, path.join(folder, f)); + } + }); + } + + gulp.task('dump-deps-backend', function (cb) { + var yaml = require('js-yaml'); + var imports = {}; + _.each(components.findLocalPathComponentFolders(), function (folder) { + var glideLockFile = path.join(folder, 'backend', 'glide.lock'); + if (fsx.existsSync(glideLockFile)) { + // Read the lock file + var doc = yaml.safeLoad(fsx.readFileSync(glideLockFile, 'utf8')); + _.each(doc.imports, function (obj) { + imports[obj.name] = obj.version; + }); + } + + // Look for local __vendor packages checked in + var vendorFolder = path.join(folder, 'backend', '__vendor'); + if (fsx.existsSync(vendorFolder)) { + var deps = []; + findStratosDepFiles(deps, vendorFolder); + _.each(deps, function (dep) { + var lines = fsx.readFileSync(dep.depFile, 'utf-8').split('\n').filter(Boolean); + imports[lines[0]] = lines[1]; + }); + } + }); + + _.each(imports, function (v, n) { + console.log('Storing dependency: ' + n); + var srcFolder = './tmp/src/' + n; + var depName = 'golang-' + replaceAll(n, '/', '-'); + fsx.copySync(srcFolder, path.join(depsFolder, depName)); + // Write the version and name to a file + fsx.writeFileSync(path.join(depsFolder, depName, '.stratos-dependency'), n + '\n' + v + '\n' + v.substr(0, 8)); + }); + return cb(); + }); +})(); diff --git a/build/main.gulp.js b/build/main.gulp.js index eda90b17e3..f4796faebe 100644 --- a/build/main.gulp.js +++ b/build/main.gulp.js @@ -33,6 +33,7 @@ var devDeps = require('./dev-dependencies'); // Pull in the gulp tasks for e2e tests require('./e2e.gulp'); + require('./deps.gulp'); var paths = config.paths; var localComponents, assetFiles, i18nFiles, jsSourceFiles, pluginFiles, diff --git a/components/app-core/backend/__vendor/github.com/gogo/protobuf/.stratos-dependency b/components/app-core/backend/__vendor/github.com/gogo/protobuf/.stratos-dependency new file mode 100644 index 0000000000..775f1857df --- /dev/null +++ b/components/app-core/backend/__vendor/github.com/gogo/protobuf/.stratos-dependency @@ -0,0 +1,3 @@ +github.com/gogo/protobuf +30433562cfbf487fe1df7cd26c7bab168d2f14d0 +30433562 \ No newline at end of file diff --git a/components/cf-app-push/backend/__vendor/code.cloudfoundry.org/cli/.stratos-dependency b/components/cf-app-push/backend/__vendor/code.cloudfoundry.org/cli/.stratos-dependency new file mode 100644 index 0000000000..ad72a1f51e --- /dev/null +++ b/components/cf-app-push/backend/__vendor/code.cloudfoundry.org/cli/.stratos-dependency @@ -0,0 +1,3 @@ +code.cloudfoundry.org/cli +96e853b95d5a3b1c0afb79e61d9918a35037d041 +96e853b9 \ No newline at end of file diff --git a/components/cf-app-push/backend/__vendor/code.cloudfoundry.org/gofileutils/.stratos-dependency b/components/cf-app-push/backend/__vendor/code.cloudfoundry.org/gofileutils/.stratos-dependency new file mode 100644 index 0000000000..92abb898ad --- /dev/null +++ b/components/cf-app-push/backend/__vendor/code.cloudfoundry.org/gofileutils/.stratos-dependency @@ -0,0 +1,3 @@ +code.cloudfoundry.org/gofileutils +4d0c80011a0f37da1711c184028bc40137cd45af +4d0c8001 \ No newline at end of file diff --git a/package.json b/package.json index 10e62dbbea..cdea7a23c4 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "jasmine-jquery": "^2.1.1", "jasmine-reporters": "^2.2.1", "jasmine-spec-reporter": "^4.2.1", + "js-yaml": "^3.10.0", "karma": "^1.7.0", "karma-chrome-launcher": "^2.2.0", "karma-cli": "^1.0.1", From 826de09bdf9611718f57ce889c9fa22fca9560db Mon Sep 17 00:00:00 2001 From: concourse Date: Mon, 25 Jun 2018 16:51:51 +0000 Subject: [PATCH 002/648] Dev releases Helm repository updated for tag: --- index.yaml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/index.yaml b/index.yaml index e43cdf3e54..95fc9c6698 100755 --- a/index.yaml +++ b/index.yaml @@ -1,6 +1,14 @@ apiVersion: v1 entries: console: + - apiVersion: v1 + created: 2018-06-25T16:51:47.597748002Z + description: A Helm chart for deploying Stratos UI Console + digest: 6f828e10f100bb479842b7aa3b393d8036bb664a8f3d96bd656201e45ff52206 + name: console + urls: + - https://github.com/SUSE/stratos/releases/download/2.0.0-cap-test/console-helm-chart-2.0.0-cap-test.tgz + version: 2.0.0-cap-test - apiVersion: v1 created: 2018-01-09T11:48:55.233833731Z description: A Helm chart for deploying Stratos UI Console @@ -73,4 +81,4 @@ entries: urls: - https://github.com/suse/stratos-ui/releases/download/0.9.0/console-helm-chart-0.9.0.tgz version: 0.9.0 -generated: 2018-01-09T11:48:55.232685995Z +generated: 2018-06-25T16:51:47.597023767Z From 709b82726538b7bd2cfd54d37bab659fce5c014f Mon Sep 17 00:00:00 2001 From: Irfan Habib Date: Tue, 26 Jun 2018 12:24:29 +0100 Subject: [PATCH 003/648] Update index.yaml --- index.yaml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/index.yaml b/index.yaml index 95fc9c6698..41c198a48f 100755 --- a/index.yaml +++ b/index.yaml @@ -1,14 +1,6 @@ apiVersion: v1 entries: console: - - apiVersion: v1 - created: 2018-06-25T16:51:47.597748002Z - description: A Helm chart for deploying Stratos UI Console - digest: 6f828e10f100bb479842b7aa3b393d8036bb664a8f3d96bd656201e45ff52206 - name: console - urls: - - https://github.com/SUSE/stratos/releases/download/2.0.0-cap-test/console-helm-chart-2.0.0-cap-test.tgz - version: 2.0.0-cap-test - apiVersion: v1 created: 2018-01-09T11:48:55.233833731Z description: A Helm chart for deploying Stratos UI Console From cf964dbc7f903a55837e6ca2f5aa33f4b49bfd78 Mon Sep 17 00:00:00 2001 From: concourse Date: Thu, 4 Oct 2018 13:13:53 +0000 Subject: [PATCH 004/648] Dev releases Helm repository updated for tag: 2.1.0-dev-kube-integration --- index.yaml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/index.yaml b/index.yaml index 41c198a48f..29a07d75aa 100755 --- a/index.yaml +++ b/index.yaml @@ -1,6 +1,14 @@ apiVersion: v1 entries: console: + - apiVersion: v1 + created: 2018-10-04T13:13:48.554358996Z + description: A Helm chart for deploying Stratos UI Console + digest: 15f9439f35301ab42a2990c06845259db975dee0b11335f5db83708b8a0c1b51 + name: console + urls: + - https://github.com/SUSE/stratos/releases/download/2.1.0-dev-kube-integration/console-helm-chart-v2.1.0-dev-kube-integration.tgz + version: 2.1.0-dev-kube-integration - apiVersion: v1 created: 2018-01-09T11:48:55.233833731Z description: A Helm chart for deploying Stratos UI Console @@ -73,4 +81,4 @@ entries: urls: - https://github.com/suse/stratos-ui/releases/download/0.9.0/console-helm-chart-0.9.0.tgz version: 0.9.0 -generated: 2018-06-25T16:51:47.597023767Z +generated: 2018-10-04T13:13:48.553077364Z From ba80fc1d282db49a080750c424d6e721045a60e3 Mon Sep 17 00:00:00 2001 From: concourse Date: Fri, 5 Oct 2018 16:33:10 +0000 Subject: [PATCH 005/648] Dev releases Helm repository updated for tag: 2.1.0-dev-kube-integration --- index.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.yaml b/index.yaml index 29a07d75aa..74c3da60be 100755 --- a/index.yaml +++ b/index.yaml @@ -2,7 +2,7 @@ apiVersion: v1 entries: console: - apiVersion: v1 - created: 2018-10-04T13:13:48.554358996Z + created: 2018-10-05T16:33:04.933450440Z description: A Helm chart for deploying Stratos UI Console digest: 15f9439f35301ab42a2990c06845259db975dee0b11335f5db83708b8a0c1b51 name: console From 6995dfe419e9146c3b89b4ac2dbc6146151c5f10 Mon Sep 17 00:00:00 2001 From: concourse Date: Mon, 8 Oct 2018 11:56:08 +0000 Subject: [PATCH 006/648] Dev releases Helm repository updated for tag: 2.1.0-dev-kube-integration --- index.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.yaml b/index.yaml index 74c3da60be..88dfa0d14a 100755 --- a/index.yaml +++ b/index.yaml @@ -2,9 +2,9 @@ apiVersion: v1 entries: console: - apiVersion: v1 - created: 2018-10-05T16:33:04.933450440Z + created: 2018-10-08T11:56:03.675982355Z description: A Helm chart for deploying Stratos UI Console - digest: 15f9439f35301ab42a2990c06845259db975dee0b11335f5db83708b8a0c1b51 + digest: f8b7126da3fe9cd358447912f4c0d7785437bcf5462f4a548487f943159ef13b name: console urls: - https://github.com/SUSE/stratos/releases/download/2.1.0-dev-kube-integration/console-helm-chart-v2.1.0-dev-kube-integration.tgz From cff41a030703ea2e23601398f845880b010ce144 Mon Sep 17 00:00:00 2001 From: concourse Date: Tue, 9 Oct 2018 19:20:13 +0000 Subject: [PATCH 007/648] Dev releases Helm repository updated for tag: 2.1.0-dev-kube-integration --- index.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.yaml b/index.yaml index 88dfa0d14a..5d6a6c98c3 100755 --- a/index.yaml +++ b/index.yaml @@ -2,9 +2,9 @@ apiVersion: v1 entries: console: - apiVersion: v1 - created: 2018-10-08T11:56:03.675982355Z + created: 2018-10-09T19:20:08.808206182Z description: A Helm chart for deploying Stratos UI Console - digest: f8b7126da3fe9cd358447912f4c0d7785437bcf5462f4a548487f943159ef13b + digest: e10528c7b1764137bc1588db5c630fe78b8bf61e8c7f14617024eee64dac0dff name: console urls: - https://github.com/SUSE/stratos/releases/download/2.1.0-dev-kube-integration/console-helm-chart-v2.1.0-dev-kube-integration.tgz From f7c431c913e219f8d2f4ae9a7f6433f24dce377a Mon Sep 17 00:00:00 2001 From: concourse Date: Mon, 22 Oct 2018 10:31:35 +0000 Subject: [PATCH 008/648] Dev releases Helm repository updated for tag: 2.0.0-dev --- index.yaml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/index.yaml b/index.yaml index 5d6a6c98c3..c8f2a7cb86 100755 --- a/index.yaml +++ b/index.yaml @@ -9,6 +9,15 @@ entries: urls: - https://github.com/SUSE/stratos/releases/download/2.1.0-dev-kube-integration/console-helm-chart-v2.1.0-dev-kube-integration.tgz version: 2.1.0-dev-kube-integration + - apiVersion: v1 + appVersion: 2.1.1 + created: 2018-10-22T10:31:32.486999738Z + description: A Helm chart for deploying Stratos UI Console + digest: a599d45c14b3ea51525d580976686f3980696b9a1238eb9946ab29a7c79298f3 + name: console + urls: + - https://github.com/SUSE/stratos/releases/download/2.0.0-dev/console-helm-chart-v2.0.0-dev.tgz + version: 2.0.0-dev - apiVersion: v1 created: 2018-01-09T11:48:55.233833731Z description: A Helm chart for deploying Stratos UI Console @@ -81,4 +90,4 @@ entries: urls: - https://github.com/suse/stratos-ui/releases/download/0.9.0/console-helm-chart-0.9.0.tgz version: 0.9.0 -generated: 2018-10-04T13:13:48.553077364Z +generated: 2018-10-22T10:31:32.48601894Z From 5e65cd77107f72f1bdc775b707aeffb6a77769ec Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Sun, 31 Mar 2019 17:43:31 +0100 Subject: [PATCH 009/648] Minor updates --- .../helm-release-service-card.component.html | 10 +++ .../helm-release-service-card.component.ts | 73 +++++++++++++++++- .../tabs/helm-release-helper.service.ts | 2 - .../helm-release-summary-tab.component.html | 9 +-- .../helm-release-summary-tab.component.scss | 7 ++ .../helm-release-summary-tab.component.ts | 59 +++++++++++--- .../helm-release-values-tab.component.ts | 10 +-- custom-src/frontend/assets/nav-logo-icon.png | Bin 862 -> 1226 bytes custom-src/frontend/assets/nav-logo.png | Bin 1421 -> 3778 bytes .../ring-chart/ring-chart.component.scss | 3 +- 10 files changed, 147 insertions(+), 26 deletions(-) diff --git a/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.html b/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.html index 4a62aabc41..8daf5f9d9f 100644 --- a/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.html +++ b/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.html @@ -2,6 +2,7 @@ {{ row.name }} + + + Ports + +
+
{{ port.name }}
+
+
+
\ No newline at end of file diff --git a/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts b/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts index d60751bf79..6ad2f27516 100644 --- a/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts +++ b/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts @@ -1,11 +1,78 @@ -import { Component } from '@angular/core'; +import { Component, Input, OnDestroy } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { Subscription } from 'rxjs'; +import { filter, map, switchMap } from 'rxjs/operators'; +import { AppState } from '../../../../../../store/src/app-state'; +import { entityFactory } from '../../../../../../store/src/helpers/entity-factory'; +import { getPaginationObservables } from '../../../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper'; +import { EntityServiceFactory } from '../../../../core/entity-service-factory.service'; import { CardCell } from '../../../../shared/components/list/list.types'; -import { HelmReleaseService } from '../../store/helm.types'; +import { PaginationMonitor } from '../../../../shared/monitors/pagination-monitor'; +import { KubeService } from '../../../kubernetes/store/kube.types'; +import { GetKubernetesServicesInNamespace } from '../../../kubernetes/store/kubernetes.actions'; +import { GetHelmReleases } from '../../store/helm.actions'; +import { helmReleasesSchemaKey } from '../../store/helm.entities'; +import { HelmRelease, HelmReleaseService } from '../../store/helm.types'; @Component({ selector: 'app-release-service-card', templateUrl: './helm-release-service-card.component.html', styleUrls: ['./helm-release-service-card.component.scss'] }) -export class HelmReleaseServiceCardComponent extends CardCell { } +export class HelmReleaseServiceCardComponent extends CardCell implements OnDestroy { + + private pRow: HelmReleaseService; + private svcSub: Subscription; + + private ports = []; + + @Input() set row(row: HelmReleaseService) { + this.pRow = row; + if (!this.svcSub && row) { + this.svcSub = this.fetchRelease(row.endpointGuid, row.releaseTitle).pipe( + switchMap((release: any) => { + const action = new GetKubernetesServicesInNamespace(row.endpointGuid, release.namespace); + const paginationMonitor = new PaginationMonitor(this.store, action.paginationKey, entityFactory(action.entityKey)); + return getPaginationObservables({ store: this.store, action, paginationMonitor }).entities$; + }), + filter(entities => !!entities), + map((services: any) => services.find(service => service.metadata.name === row.name)) + ).subscribe(service => { + console.log('SERVICE: ', service); + this.ports = service.spec.ports; + }); + } + + + } + get row() { + return this.pRow; + } + + + constructor( + private store: Store, + private esf: EntityServiceFactory + ) { + super(); + } + + ngOnDestroy() { + if (this.svcSub) { + this.svcSub.unsubscribe(); + } + } + + private fetchRelease(endpointGuid: string, releaseTitle: string) { + const action = new GetHelmReleases(); + const paginationMonitor = new PaginationMonitor(this.store, action.paginationKey, entityFactory(helmReleasesSchemaKey)); + const svc = getPaginationObservables({ store: this.store, action, paginationMonitor }); + + + return svc.entities$.pipe( + map((items: HelmRelease[]) => items.find(item => item.guid === `${endpointGuid}:${releaseTitle}`)) + ); + } + +} diff --git a/custom-src/frontend/app/custom/helm/release/tabs/helm-release-helper.service.ts b/custom-src/frontend/app/custom/helm/release/tabs/helm-release-helper.service.ts index ad0215fa28..5b62b9191b 100644 --- a/custom-src/frontend/app/custom/helm/release/tabs/helm-release-helper.service.ts +++ b/custom-src/frontend/app/custom/helm/release/tabs/helm-release-helper.service.ts @@ -65,8 +65,6 @@ export const parseHelmReleaseStatus = (res: string): HelmReleaseStatus => { } }; - console.log(res); - // Process let i = 0; while (i < lines.length) { diff --git a/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html b/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html index 8fa2b7eb05..4a9a5f39f0 100644 --- a/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html +++ b/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html @@ -39,15 +39,14 @@ - - + - + - + - + diff --git a/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss b/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss index 952b0aaac5..c759665b86 100644 --- a/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss +++ b/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss @@ -8,4 +8,11 @@ margin-top: 0; } } +} + +.helm-release-summary { + + &__chart-tile { + width: 50%; + } } \ No newline at end of file diff --git a/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts b/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts index bc07fe1050..8052ca25f4 100644 --- a/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts +++ b/custom-src/frontend/app/custom/helm/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts @@ -1,7 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; -import { Observable, of as observableOf } from 'rxjs'; +import { Observable, of as observableOf, combineLatest } from 'rxjs'; import { map, startWith, tap } from 'rxjs/operators'; import { ClearPaginationOfType } from '../../../../../../../store/src/actions/pagination.actions'; @@ -12,6 +12,12 @@ import { ConfirmationDialogService } from '../../../../../shared/components/conf import { helmReleasesSchemaKey } from '../../../store/helm.entities'; import { HelmReleaseHelperService } from '../helm-release-helper.service'; +const podErrorStatus = { + Failed: true, + CrashLoopBackOff: true, + ErrImgPull: true, +}; + @Component({ selector: 'app-helm-release-summary-tab', templateUrl: './helm-release-summary-tab.component.html', @@ -44,8 +50,16 @@ export class HelmReleaseSummaryTabComponent { } ]; - // Blue: #00B2E2 - // Yellow: #FFC107 + public podsChartColors = [ + { + name: 'OK', + value: '#4DD3A7' + }, + { + name: 'Error', + value: '#E7727D' + } + ]; constructor( public helmReleaseHelper: HelmReleaseHelperService, @@ -53,19 +67,23 @@ export class HelmReleaseSummaryTabComponent { private confirmDialog: ConfirmationDialogService, private httpClient: HttpClient, ) { - this.isBusy$ = this.helmReleaseHelper.isFetching$; // Async fetch release status - this.helmReleaseHelper.fetchReleaseStatus().subscribe(data => { + const fetchStatus = this.helmReleaseHelper.fetchReleaseStatus(); + const isStatusBusy$ = fetchStatus.pipe(map(d => false)); + this.isBusy$ = combineLatest(this.helmReleaseHelper.isFetching$, isStatusBusy$).pipe( + map(([a, b]) => a || b) + ); + + fetchStatus.subscribe(data => { const chart = []; - console.log(data); Object.keys(data.pods.status).forEach(status => { chart.push({ name: status, value: data.pods.status[status] }); }); - this.podsChartData = chart; + this.podsChartData = this.collatePodStatus(data); this.containersChartData = [ { @@ -80,6 +98,30 @@ export class HelmReleaseSummaryTabComponent { }); } + private collatePodStatus(data: any): any { + let okay = 0; + let error = 0; + + Object.keys(data.pods.status).forEach(status => { + if (podErrorStatus[status]) { + error++; + } else { + okay++; + } + }); + + return [ + { + name: 'OK', + value: okay + }, + { + name: 'Error', + value: error + } + ]; + } + public deleteRelease() { this.confirmDialog.open(this.deleteReleaseConfirmation, () => { // Make the http request to delete the release @@ -87,9 +129,6 @@ export class HelmReleaseSummaryTabComponent { const deleting$ = this.httpClient.delete(`/pp/v1/helm/releases/${endpointAndName}`); this.loadingMessage = 'Deleting Release'; this.isBusy$ = deleting$.pipe( - tap(d => { - console.log(d); - }), map(d => false), startWith(true), ); diff --git a/custom-src/frontend/app/custom/helm/release/tabs/helm-release-values-tab/helm-release-values-tab.component.ts b/custom-src/frontend/app/custom/helm/release/tabs/helm-release-values-tab/helm-release-values-tab.component.ts index 3759231456..2d28e1e570 100644 --- a/custom-src/frontend/app/custom/helm/release/tabs/helm-release-values-tab/helm-release-values-tab.component.ts +++ b/custom-src/frontend/app/custom/helm/release/tabs/helm-release-values-tab/helm-release-values-tab.component.ts @@ -16,7 +16,7 @@ export class HelmReleaseValuesTabComponent { constructor(public helmReleaseHelper: HelmReleaseHelperService) { this.values$ = helmReleaseHelper.release$.pipe( - map(release => { + map((release: any) => { if (release.config.raw) { return this.hidePasswords(release.config.raw); } else { @@ -27,10 +27,10 @@ export class HelmReleaseValuesTabComponent { } private hidePasswords(values: string): string { - let mask = values.replace(new RegExp('(PASSWORD: [a-zA-Z0-9_\-]*)', 'gm'), 'PASSWORD: **********'); - mask = mask.replace(new RegExp('(password: [a-zA-Z0-9_\-]*)', 'gm'), 'password: **********'); - mask = mask.replace(new RegExp('(SECRET: [a-zA-Z0-9_\-]*)', 'gm'), 'SECRET: **********'); - mask = mask.replace(new RegExp('(secret: [a-zA-Z0-9_\-]*)', 'gm'), 'secret: **********'); + let mask = values.replace(new RegExp('(PASSWORD: [\.a-zA-Z0-9_\-]*)', 'gm'), 'PASSWORD: **********'); + mask = mask.replace(new RegExp('(password: [\.a-zA-Z0-9_\-]*)', 'gm'), 'password: **********'); + mask = mask.replace(new RegExp('(SECRET: [\.a-zA-Z0-9_\-]*)', 'gm'), 'SECRET: **********'); + mask = mask.replace(new RegExp('(secret: [\.a-zA-Z0-9_\-]*)', 'gm'), 'secret: **********'); return mask; } } diff --git a/custom-src/frontend/assets/nav-logo-icon.png b/custom-src/frontend/assets/nav-logo-icon.png index fbcc1430b83ed6341448aba4d3c7e779cead6c17..bcff369d203474cb98c628062e0d5b7776b8600a 100644 GIT binary patch delta 1206 zcmV;n1WEhe2FeMLB!2;OQb$4nuFf3k00004XF*Lt006O%3;baP00009a7bBm000id z000id0mpBsWB>pF8FWQhbW?9;ba!ELWdLwtX>N2bZe?^JG%heMF*h@ApJM<31UgAX zK~z{r-Ir}>m1P*m=M0uFDTQRDhW2r=K+)Bt7bRa}A|wzW_kG>h z%gyutl%-Osk|*daC7ePFrNsYhlSwou?RuK^rtcrG>|;n7)ZbvG-_x z3Y$p8o`70Q4}XfVC$mu+O@dA}mKdLe4e%zs2%~U6jhzhh;VbAJigxioVP3Fup71Bz zXj~8tYG8mhH9L)Y-IiiSTshQ{bJ=qHVWus6V%hJX?Ff-Aiq3|+_|>EZ^AffSDlDX+ z$Qe!iZmV$zVmF!@#E}-obn0&6Q}M0lTW0fz&;#GXr++XVcezWYZ3D zh{cVd*ioz#6zkk#bg5(+4qD9{K z@C?H>*ncnKHPA!<794RqM6Byl`rUFWFUkndIK$m)#iKK z_Ng9C=4-%s{k=&(NIZ-!O62T;SvEdy8>xg}VWRnebUQ?hUo~GF#_R7*@0c9#EvzePP^CNo8*JU+keZL0ZIsDLhWIWb=WAJ|m$ zRqjwC#`oYFZm+*L$v7h-G07(PV&-7Ghdj==6{G8NJ#2?}K=1s%p$He#YHif#FSw4| z-%n-`BNE-aHcrGmfJqETVpoFun7|%|<**Q%z`gS&#(H)(TaPj3JBRT%nM5RZ5L{5i z>3@#i2;<=?=I_HH_*?5~G1unw-8tP_sK-oOYsVDcWHgAxP1wXDULOrDFhljQW8e*z z^wyfkST~M7GCqONEJlwWT!W6$r ziR2yIjh*g{Faf zW?J#gC)aR94c#QFH2{;%o!dX^2$3y{=|}56eih#b?z9yna&(II>!BKVp~jY+)PFOZ z97l+#DEbzpk7yr!$T0R-K_W(%Of8(Z+?z1lmNR0>?;mx92nqjG8XU(uG46#gU@kn5 z%UufCXPN8g?Q_uE#EJ3GFvS&%h-Hr)M~Hl4O-E)KfB!32COGiWi000000Qp0^e*gdg32;bRa{vGf6951U69E94oEQKA z00(qQO+^Re0~`-60>l$7g#Z8o>q$gGR9M69m|KXIRTRg6dmMGt%rRwjz!a6jv>H$f zLlIx{AqYgo=P*&i2Mh7hC+&hyN|dN?MLk;i;CKlIFD0UuLVt>v%qVY}*Gxr6e!eVW zGmf6~&H2teodpNJefD1e_1|mnwb$Ak8q$!46qZy5h&lH{mw1NAp`WM2~eG9&PGkss9l>PB^?IWuN9mPv5@Qv%{)g@C!MexkPY;L*oR-&z*Qv-VDYQV+!UR&`j0`9}PIqzr7DHtuVnIr?hy zDRxzCX_}7R0IWFvFf0<2Kdm$&P~ax&aK1)cAqA1(*wr1TF$i>iYmGrQdsL z;|WjRuHdQ3H%@5{i?80ak)2F88Exl zxuG*fU7h85)hp^(>a?r?c`5of)=F|o!RWrJE>cJIfYqw5P%lL8e=l_A*Glr0Dr?4` zQsN(Rh#ynWsrRDq!@%AZ*yGE{I%h0L_NZ@XrGK01p|W!Ki!~6t7DYy$4xh^kjG@o6 z-CXr$D5byDFMy@MePB#PZVNCxe#3x)Ko@WaxDH%QDc$M;@_XO~V0B9Azp`t}V=?;! z>hNCZ4^?*t#@>2W<}DEjMn-wJfbQ&Adewbu|9anb)@6I{u)0+3$cx*k++S4}tNXL@ zA3*ozjd7HEqQI5*m)=z2WjzkO3`~#5f!Bdi+2g>kz;@tZR`E2XAq}awJOW|*K;j$d R#ccop002ovPDHLkV1i+%mz@9r diff --git a/custom-src/frontend/assets/nav-logo.png b/custom-src/frontend/assets/nav-logo.png index 6d860e7023e527a5c93235aafac1fde1cdf33b23..9f4b0afb8425f6e8056f39350d995f28abcd58e7 100644 GIT binary patch literal 3778 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 literal 1421 zcmcJP*;f(>7{yUi8k5lDIwg}*DQVK8iBeiByNG30uDMl;xRl~p?v$t8VgdpcHA`V~ zIpi`}NoM1cj+t3xZn@@4WVjWHro(?QPjk^kVy7*0Kk?40@~TjyLCPURItqn43w4~_3NGxX??r{}wQ+kegxY>0WP*(8FlEri}jn3Q) zeLFF~=TstkVn+572n}h|!SW%rRtG7K4x}=jDrplmTTU#dVIQXtfrvnI`v%GdkMFjJ zd(#jp&dx_+wd<@EUY!D9w(1~-^zH@JOhtR6PD$^?bBlM$e>mxv*y@0!0HvXk$r*Ye zs};}dh>isP053Af~HAG*KG0phm=; zTg@iMW#jo5l{acvFhGt4ckkm7G!G~|3q`{2;e!!S(@O`rPP@MjgNtV*ZJLKP)O6`f zRV%e{iZ(;+U!6J6Kjz!-Hl|PA_t6X0qL~3w+>f;Na$^5^6QU>OZDTWoH7RQiE6v@E zX<`pt{*V{lQxY-65TvAT(ZV4E`o}N9+z(j=*;61ig&K@ku~N`tE^*ramwd6_7vzMZ z6n~t1zl^VhWESA%ubvZ}`3GbzH&)r-ofl!lzq<08rwAqf+Qop&pE6=|1@-Bn6?*PM zXm8XXSAiZm)7W4#iiS`_B?~E7Dleay*SaPH7Q&xoiE@019;-2Qdp6=n!&H}c962B( z*xN6{QdZQ((hM&@GKR}Myh!znnu`UK2w_&&H8(41`0^FI(y!N2b4mRng}Pv@)EhOv zvr|^4{XnwlulxpjEa-Jnr<&9#wIXa}aORZ*m3KS2ye62CkGiE{>25YCnrW9Hus|BC zT8{$dPot4n39kFC$aalU7Ef}sR`yllgWx643x6&g<(myGLc_$2xozKy@UAbI7$nGL z3>nA|8(y{{EyzqYZp}X0w590K#uybTsuxc+b}bz9AALHS?{U#&XO%S(f@OwOwfY%6 zJUEE*qYAn`jn_k97s#A1ipG9a7F`6F0XX@0Z5*h13qzAM!*nQm?=S_1-^s_6UZ+4cWeeFKf6L&pDJ&Iv&XWujklvpJ%ppJ0QU)bUu7WhmU z7khsV@58HG0DHw$Nwv{KtNM+)T{ww)UgR8i?qQYm44_8GPb<(OhB<1&m{PEsSfU3H zx)#}pU;Y`vxsnr?_%?{`%)Xv7rK9A&y)-FaKBq6F38%Npd=M$4`%pZ)!NWgzH6&*p2%CFuIUFr4Lw diff --git a/src/frontend/packages/core/src/shared/components/ring-chart/ring-chart.component.scss b/src/frontend/packages/core/src/shared/components/ring-chart/ring-chart.component.scss index f847591977..b52e50489e 100644 --- a/src/frontend/packages/core/src/shared/components/ring-chart/ring-chart.component.scss +++ b/src/frontend/packages/core/src/shared/components/ring-chart/ring-chart.component.scss @@ -10,7 +10,7 @@ .advanced-pie-legend { display: flex; flex-direction: column; - margin-right: 36px; + margin-right: 24px; top: 0; transform: none; @@ -19,6 +19,7 @@ } .total-label { + font-size: 18px; margin: -8px 0 0; } From 9c9898a493f28f485d20d0eff50cb858ab03053e Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 2 Apr 2019 07:08:59 -0500 Subject: [PATCH 010/648] Neil's fixes --- .../helm-release-service-card.component.ts | 4 +- .../kubernetes-svcproxy.component.html | 8 + .../kubernetes-svcproxy.component.scss | 15 ++ .../kubernetes-svcproxy.component.spec.ts | 25 +++ .../kubernetes-svcproxy.component.ts | 93 ++++++++++ .../plugins/kubernetes/kube_svc_proxy.go | 160 ++++++++++++++++++ 6 files changed, 303 insertions(+), 2 deletions(-) create mode 100644 custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.html create mode 100644 custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.scss create mode 100644 custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.spec.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.ts create mode 100644 src/jetstream/plugins/kubernetes/kube_svc_proxy.go diff --git a/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts b/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts index 6ad2f27516..7de422b085 100644 --- a/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts +++ b/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts @@ -30,9 +30,9 @@ export class HelmReleaseServiceCardComponent extends CardCell { - const action = new GetKubernetesServicesInNamespace(row.endpointGuid, release.namespace); + const action = new GetKubernetesServicesInNamespace(row.endpointId, release.namespace); const paginationMonitor = new PaginationMonitor(this.store, action.paginationKey, entityFactory(action.entityKey)); return getPaginationObservables({ store: this.store, action, paginationMonitor }).entities$; }), diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.html b/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.html new file mode 100644 index 0000000000..ca33850770 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.html @@ -0,0 +1,8 @@ + +

Service Proxy

+
+ + + + + diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.scss b/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.scss new file mode 100644 index 0000000000..ff32c05101 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.scss @@ -0,0 +1,15 @@ +.kube-dashboard { + border: 0; + display: flex; + height: 100%; + width: 100%; +} + +.kube-dashboard__hidden { + visibility: hidden; +} + +.kube-dashboard__search { + display: inline; + font-size: 16px; +} diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.spec.ts new file mode 100644 index 0000000000..78671a16f0 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { KubernetesServiceProxyComponent } from './kubernetes-svcproxy.component'; + +describe('KubernetesServiceProxyComponent', () => { + let component: KubernetesServiceProxyComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ KubernetesServiceProxyComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubernetesServiceProxyComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.ts new file mode 100644 index 0000000000..6d12f4f83b --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-svcproxy/kubernetes-svcproxy.component.ts @@ -0,0 +1,93 @@ +import { BehaviorSubject, Observable } from 'rxjs'; +import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; +import { Component, OnInit, ViewChild, ElementRef, Renderer2 } from '@angular/core'; +import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; +import { BaseKubeGuid } from '../kubernetes-page.types'; +import { ActivatedRoute } from '@angular/router'; +import { KubernetesService } from '../services/kubernetes.service'; +import { IHeaderBreadcrumb } from '../../../shared/components/page-header/page-header.types'; +import { map } from 'rxjs/operators'; + +@Component({ + selector: 'app-kubernetes-svcproxy', + templateUrl: './kubernetes-svcproxy.component.html', + styleUrls: ['./kubernetes-svcproxy.component.scss'], + providers: [ + { + provide: BaseKubeGuid, + useFactory: (activatedRoute: ActivatedRoute) => { + return { + guid: activatedRoute.snapshot.params.endpointId + }; + }, + deps: [ + ActivatedRoute + ] + }, + KubernetesService, + KubernetesEndpointService, + ] +}) +export class KubernetesServiceProxyComponent implements OnInit { + + @ViewChild('kubeDash', {read: ElementRef}) kubeDash: ElementRef; + + source: SafeResourceUrl; + isLoading$ = new BehaviorSubject(true); + + href = ''; + + public breadcrumbs$: Observable; + + constructor(public kubeEndpointService: KubernetesEndpointService, private sanitizer: DomSanitizer, public renderer: Renderer2) { } + + ngOnInit() { + const guid = this.kubeEndpointService.baseKube.guid; + + let href = window.location.href; + const index = href.indexOf('svcproxy'); + href = href.substr(index + 9); + console.log(href); + this.href = href; + const url = `/pp/v1/kubesvc/${guid}/${href}/`; + console.log(url); + this.source = this.sanitizer.bypassSecurityTrustResourceUrl(url); + console.log(window.location); + + this.breadcrumbs$ = this.kubeEndpointService.endpoint$.pipe( + map(endpoint => ([{ + breadcrumbs: [ + { value: endpoint.entity.name, routerLink: `/kubernetes/${endpoint.entity.guid}` }, + ] + }]) + ) + ); + } + + iframeLoaded() { + const iframeWindow = this.kubeDash.nativeElement.contentWindow; + console.log('iframe loaded'); + this.isLoading$.next(false); + + iframeWindow.addEventListener('hashchange', () => { + console.log('iframe hashchange'); + console.log(iframeWindow.location); + console.log(this.href); + + if (this.href) { + let h2 = decodeURI(this.href); + h2 = decodeURI(h2); + + h2 = h2.replace('%3F', '?'); + h2 = h2.replace('%3D', '='); + console.log(h2); + h2 = '#!' + h2; + console.log('Changing location hash'); + iframeWindow.location.hash = h2; + this.href = ''; + } + }); + + } + +} diff --git a/src/jetstream/plugins/kubernetes/kube_svc_proxy.go b/src/jetstream/plugins/kubernetes/kube_svc_proxy.go new file mode 100644 index 0000000000..d62154a6fb --- /dev/null +++ b/src/jetstream/plugins/kubernetes/kube_svc_proxy.go @@ -0,0 +1,160 @@ +package kubernetes + +import ( + "errors" + "fmt" + "net/http" + "net/http/httputil" + "net/url" + "strings" + + "github.com/labstack/echo" + log "github.com/sirupsen/logrus" + + utilnet "k8s.io/apimachinery/pkg/util/net" + "k8s.io/client-go/rest" +) + +// GET /api/v1/namespaces/{namespace}/pods/{name}/proxy + +// http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/. + +//GET /api/v1/namespaces/{namespace}/services/{name}/proxy + +func (k *KubernetesSpecification) kubeServiceProxy(c echo.Context) error { + log.Info("kubeServiceProxy request") + + // c.Response().Header().Set("X-FRAME-OPTIONS", "sameorigin") + + cnsiGUID := c.Param("guid") + namespace := c.Param("ns") + serviceName := c.Param("svc") + servicePortName := c.Param("port") + userGUID := c.Get("user_id").(string) + + var p = k.portalProxy + + log.Debug(c.Request().RequestURI) + + var prefix = "/pp/v1/kubesvc/" + cnsiGUID + "/" + namespace + "/" + serviceName + "/" + servicePortName + "/" + + path := c.Request().RequestURI[len(prefix):] + + log.Info(path) + + cnsiRecord, err := p.GetCNSIRecord(cnsiGUID) + if err != nil { + //return sendSSHError("Could not get endpoint information") + return errors.New("Could not get endpoint information") + } + + // Get token for this users + tokenRec, ok := p.GetCNSITokenRecord(cnsiGUID, userGUID) + if !ok { + //return sendSSHError("Could not get endpoint information") + return errors.New("Could not get token") + } + + // Make the info call to the SSH endpoint info + // Currently this is not cached, so we must get it each time + apiEndpoint := cnsiRecord.APIEndpoint + log.Debug(apiEndpoint) + target := fmt.Sprintf("%s/api/v1/namespaces/%s/services/%s:%s/proxy/%s", apiEndpoint, namespace, serviceName, servicePortName, path) + targetURL, _ := url.Parse(target) + targetURL = normalizeLocation(targetURL) + + log.Infof("Target URL: %s", targetURL) + + config, err := getConfig(&cnsiRecord, &tokenRec) + if err != nil { + return errors.New("Could not get config for this auth type") + } + + log.Info("Config") + log.Info(config.Host) + log.Info("Making request") + req := c.Request() + w := c.Response().Writer + log.Info("%v+", req) + + loc := targetURL + loc.RawQuery = req.URL.RawQuery + + // If original request URL ended in '/', append a '/' at the end of the + // of the proxy URL + if !strings.HasSuffix(loc.Path, "/") && strings.HasSuffix(req.URL.Path, "/") { + loc.Path += "/" + } + + log.Info(loc) + + // From pkg/genericapiserver/endpoints/handlers/proxy.go#ServeHTTP: + // Redirect requests with an empty path to a location that ends with a '/' + // This is essentially a hack for http://issue.k8s.io/4958. + // Note: Keep this code after tryUpgrade to not break that flow. + if len(loc.Path) == 0 { + log.Info("Redirecting") + var queryPart string + if len(req.URL.RawQuery) > 0 { + queryPart = "?" + req.URL.RawQuery + } + w.Header().Set("Location", req.URL.Path+"/"+queryPart) + w.WriteHeader(http.StatusMovedPermanently) + return nil + } + + // if transport == nil || wrapTransport { + // h.Transport = h.defaultProxyTransport(req.URL, h.Transport) + // } + + transport, err := rest.TransportFor(config) + if err != nil { + log.Info("Could not get transport") + return err + } + + log.Info(transport) + + // WithContext creates a shallow clone of the request with the new context. + newReq := req.WithContext(req.Context()) + //newReq := req.WithContext(context.Background()) + newReq.Header = utilnet.CloneHeader(req.Header) + newReq.URL = loc + + // Set auth header so we log in if needed + if len(tokenRec.AuthToken) > 0 { + newReq.Header.Add("Authorization", "Bearer "+tokenRec.AuthToken) + log.Info("Setting auth header") + } + + proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: loc.Scheme, Host: loc.Host}) + proxy.Transport = transport + proxy.FlushInterval = defaultFlushInterval + proxy.ModifyResponse = func(response *http.Response) error { + log.Debugf("GOT PROXY RESPONSE: %s", loc.String()) + log.Debugf("%d", response.StatusCode) + log.Debug(response.Header.Get("Content-Type")) + + log.Debugf("%v+", response.Header) + response.Header.Del("X-FRAME-OPTIONS") + response.Header.Set("X-FRAME-OPTIONS", "sameorigin") + log.Debug("%v+", response) + return nil + } + + log.Errorf("Proxy: %s", target) + + // Note that ServeHttp is non blocking and uses a go routine under the hood + proxy.ServeHTTP(w, newReq) + + // We need this to be blocking + + // select { + // case <-newReq.Context().Done(): + // return newReq.Context().Err() + // } + + log.Errorf("Finished proxying request: %s", target) + + return nil +} From 998f5c6f06cd63f366c9c15bcc8d39ce3d1a9724 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 2 Apr 2019 12:10:50 -0500 Subject: [PATCH 011/648] FIXES --- .../demo-helper/demo-helper.component.html | 9 +++ .../frontend/app/custom/helm/helm.module.ts | 2 +- .../helm-release-service-card.component.html | 10 --- .../helm-release-service-card.component.ts | 73 +------------------ .../custom/kubernetes/kubernetes.module.ts | 2 + 5 files changed, 15 insertions(+), 81 deletions(-) diff --git a/custom-src/frontend/app/custom/demo/demo-helper/demo-helper.component.html b/custom-src/frontend/app/custom/demo/demo-helper/demo-helper.component.html index 4f7f70ff90..4a8cae66cf 100644 --- a/custom-src/frontend/app/custom/demo/demo-helper/demo-helper.component.html +++ b/custom-src/frontend/app/custom/demo/demo-helper/demo-helper.component.html @@ -3,6 +3,15 @@

SUSECON '19 Demo

+ +

Cloud Foundry

+ +
    +
  • https://api.10.86.2.125.xip.io
  • +
+ +
+

Helm Repositories

    diff --git a/custom-src/frontend/app/custom/helm/helm.module.ts b/custom-src/frontend/app/custom/helm/helm.module.ts index ca818ffc25..af13987c32 100644 --- a/custom-src/frontend/app/custom/helm/helm.module.ts +++ b/custom-src/frontend/app/custom/helm/helm.module.ts @@ -82,7 +82,7 @@ import { RepositoryTabComponent } from './tabs/repository-tab/repository-tab.com HelmReleaseValuesTabComponent, HelmReleasePodsTabComponent, HelmReleaseServicesTabComponent, - KubernetesServicePortsComponent + KubernetesServicePortsComponent, ], providers: [ ChartsService, diff --git a/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.html b/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.html index 547294936e..3f71b45e59 100644 --- a/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.html +++ b/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.html @@ -2,7 +2,6 @@ {{ row.name }} - - - Ports - -
    -
    {{ port.name }}
    -
    -
    -
    \ No newline at end of file diff --git a/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts b/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts index 7de422b085..d60751bf79 100644 --- a/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts +++ b/custom-src/frontend/app/custom/helm/list-types/helm-release-service-card/helm-release-service-card.component.ts @@ -1,78 +1,11 @@ -import { Component, Input, OnDestroy } from '@angular/core'; -import { Store } from '@ngrx/store'; -import { Subscription } from 'rxjs'; -import { filter, map, switchMap } from 'rxjs/operators'; +import { Component } from '@angular/core'; -import { AppState } from '../../../../../../store/src/app-state'; -import { entityFactory } from '../../../../../../store/src/helpers/entity-factory'; -import { getPaginationObservables } from '../../../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper'; -import { EntityServiceFactory } from '../../../../core/entity-service-factory.service'; import { CardCell } from '../../../../shared/components/list/list.types'; -import { PaginationMonitor } from '../../../../shared/monitors/pagination-monitor'; -import { KubeService } from '../../../kubernetes/store/kube.types'; -import { GetKubernetesServicesInNamespace } from '../../../kubernetes/store/kubernetes.actions'; -import { GetHelmReleases } from '../../store/helm.actions'; -import { helmReleasesSchemaKey } from '../../store/helm.entities'; -import { HelmRelease, HelmReleaseService } from '../../store/helm.types'; +import { HelmReleaseService } from '../../store/helm.types'; @Component({ selector: 'app-release-service-card', templateUrl: './helm-release-service-card.component.html', styleUrls: ['./helm-release-service-card.component.scss'] }) -export class HelmReleaseServiceCardComponent extends CardCell implements OnDestroy { - - private pRow: HelmReleaseService; - private svcSub: Subscription; - - private ports = []; - - @Input() set row(row: HelmReleaseService) { - this.pRow = row; - if (!this.svcSub && row) { - this.svcSub = this.fetchRelease(row.endpointId, row.releaseTitle).pipe( - switchMap((release: any) => { - const action = new GetKubernetesServicesInNamespace(row.endpointId, release.namespace); - const paginationMonitor = new PaginationMonitor(this.store, action.paginationKey, entityFactory(action.entityKey)); - return getPaginationObservables({ store: this.store, action, paginationMonitor }).entities$; - }), - filter(entities => !!entities), - map((services: any) => services.find(service => service.metadata.name === row.name)) - ).subscribe(service => { - console.log('SERVICE: ', service); - this.ports = service.spec.ports; - }); - } - - - } - get row() { - return this.pRow; - } - - - constructor( - private store: Store, - private esf: EntityServiceFactory - ) { - super(); - } - - ngOnDestroy() { - if (this.svcSub) { - this.svcSub.unsubscribe(); - } - } - - private fetchRelease(endpointGuid: string, releaseTitle: string) { - const action = new GetHelmReleases(); - const paginationMonitor = new PaginationMonitor(this.store, action.paginationKey, entityFactory(helmReleasesSchemaKey)); - const svc = getPaginationObservables({ store: this.store, action, paginationMonitor }); - - - return svc.entities$.pipe( - map((items: HelmRelease[]) => items.find(item => item.guid === `${endpointGuid}:${releaseTitle}`)) - ); - } - -} +export class HelmReleaseServiceCardComponent extends CardCell { } diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes.module.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes.module.ts index 63012671aa..1915af3b1c 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes.module.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes.module.ts @@ -86,6 +86,7 @@ import { KubernetesNamespacesTabComponent } from './tabs/kubernetes-namespaces-t import { KubernetesNodesTabComponent } from './tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component'; import { KubernetesPodsTabComponent } from './tabs/kubernetes-pods-tab/kubernetes-pods-tab.component'; import { KubernetesSummaryTabComponent } from './tabs/kubernetes-summary-tab/kubernetes-summary.component'; +import { KubernetesServiceProxyComponent } from './kubernetes-svcproxy/kubernetes-svcproxy.component'; /* tslint:disable:max-line-length */ /* tslint:enable */ @@ -139,6 +140,7 @@ import { KubernetesSummaryTabComponent } from './tabs/kubernetes-summary-tab/kub KubeNamespacePodCountComponent, PodNameLinkComponent, NodePodCountComponent, + KubernetesServiceProxyComponent, ], providers: [ KubernetesService, From 29a15e9b7e5cec05a168f05eedfac96c92853bcc Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 2 Apr 2019 15:58:54 -0500 Subject: [PATCH 012/648] Fixes, docker AIO build fix --- .../app/custom/kubernetes/kubernetes.routing.ts | 17 +++++++++++++++++ .../app/custom/kubernetes/store/kube.types.ts | 4 ++++ deploy/Dockerfile.all-in-one | 2 +- .../Dockerfile.stratos-go-build-base.tmpl | 4 ++-- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes.routing.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes.routing.ts index 77d1c9d821..0a1fb0341d 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes.routing.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes.routing.ts @@ -1,3 +1,4 @@ +import { KubernetesServiceProxyComponent } from './kubernetes-svcproxy/kubernetes-svcproxy.component'; import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; @@ -167,6 +168,22 @@ const kubernetes: Routes = [{ } } ] +}, +{ + path: ':endpointId/svcproxy', + component: KubernetesServiceProxyComponent, + data: { + uiNoMargin: true + }, + children: [ + { + path: '**', + component: KubernetesServiceProxyComponent, + data: { + uiNoMargin: true + } + } + ] } ]; diff --git a/custom-src/frontend/app/custom/kubernetes/store/kube.types.ts b/custom-src/frontend/app/custom/kubernetes/store/kube.types.ts index 9c78513045..b0817e880e 100644 --- a/custom-src/frontend/app/custom/kubernetes/store/kube.types.ts +++ b/custom-src/frontend/app/custom/kubernetes/store/kube.types.ts @@ -1,3 +1,5 @@ +import { Observable } from "rxjs"; + export interface KubernetesInfo { nodes: {}; pods: {}; @@ -18,7 +20,9 @@ export interface KubeService { metadata: KubeServiceMetadata; status: ServiceStatus; spec: DeploymentSpec; + kubeService$?: Observable; } + export interface KubernetesStatefulSet { metadata: KubeServiceMetadata; status: ServiceStatus; diff --git a/deploy/Dockerfile.all-in-one b/deploy/Dockerfile.all-in-one index 3f23b62a95..ee5a82601a 100644 --- a/deploy/Dockerfile.all-in-one +++ b/deploy/Dockerfile.all-in-one @@ -3,7 +3,7 @@ FROM splatform/stratos-aio-base:opensuse as builder COPY --chown=stratos:users *.json ./ COPY --chown=stratos:users gulpfile.js ./ -COPY --chown=stratos:users Gopkg.* ./ +#COPY --chown=stratos:users Gopkg.* ./ COPY --chown=stratos:users src ./src COPY --chown=stratos:users build ./build/ COPY --chown=stratos:users deploy/tools/generate_cert.sh generate_cert.sh diff --git a/deploy/stratos-base-images/Dockerfile.stratos-go-build-base.tmpl b/deploy/stratos-base-images/Dockerfile.stratos-go-build-base.tmpl index 8b156d27e3..b26bfd694e 100644 --- a/deploy/stratos-base-images/Dockerfile.stratos-go-build-base.tmpl +++ b/deploy/stratos-base-images/Dockerfile.stratos-go-build-base.tmpl @@ -11,8 +11,8 @@ RUN zypper ref RUN zypper -n ref && \ zypper -n up && \ zypper in -y which tar git gcc curl wget -RUN wget https://storage.googleapis.com/golang/go1.9.7.linux-amd64.tar.gz && \ - tar -xzf go1.9.7.linux-amd64.tar.gz -C /usr/local/ && \ +RUN wget https://dl.google.com/go/go1.12.1.linux-amd64.tar.gz && \ + tar -xzf go1.12.1.linux-amd64.tar.gz -C /usr/local/ && \ mkdir -p /home/stratos/go/bin && \ mkdir -p /home/stratos/go/src From 8df21b7e21d07b80f9c0394232abac82e481b473 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 3 Apr 2019 21:58:09 -0500 Subject: [PATCH 013/648] Refinements --- .../create-release.component.html | 2 +- .../create-release.component.scss | 1 + .../kubernetes-service-ports.component.html | 2 +- src/jetstream/datastore/datastore.go | 13 +++++-- .../plugins/kubernetes/endpoint_config.go | 39 ++++++++++++++----- .../plugins/kubernetes/helm_client.go | 2 +- .../plugins/kubernetes/kube_dashboard.go | 6 +-- .../plugins/kubernetes/kube_svc_proxy.go | 12 +++++- src/jetstream/plugins/kubernetes/main.go | 2 + 9 files changed, 59 insertions(+), 20 deletions(-) diff --git a/custom-src/frontend/app/custom/helm/create-release/create-release.component.html b/custom-src/frontend/app/custom/helm/create-release/create-release.component.html index 3d8f081b1f..e009d50e47 100644 --- a/custom-src/frontend/app/custom/helm/create-release/create-release.component.html +++ b/custom-src/frontend/app/custom/helm/create-release/create-release.component.html @@ -32,7 +32,7 @@

    Enter YAML Value Overrides

    Values + [matTextareaAutosize]="true" cdkAutosizeMinRows="12" cdkAutosizeMaxRows="16"> diff --git a/custom-src/frontend/app/custom/helm/create-release/create-release.component.scss b/custom-src/frontend/app/custom/helm/create-release/create-release.component.scss index b1b37d0268..1d498faabe 100644 --- a/custom-src/frontend/app/custom/helm/create-release/create-release.component.scss +++ b/custom-src/frontend/app/custom/helm/create-release/create-release.component.scss @@ -14,5 +14,6 @@ form { &__yaml { background-color: rgba(0, 0, 0, .1); font-family: 'Source Code Pro', monospace; + font-size: 14px; } } diff --git a/custom-src/frontend/app/custom/helm/list-types/kubernetes-service-ports/kubernetes-service-ports.component.html b/custom-src/frontend/app/custom/helm/list-types/kubernetes-service-ports/kubernetes-service-ports.component.html index 35187acf75..94a0dfa883 100644 --- a/custom-src/frontend/app/custom/helm/list-types/kubernetes-service-ports/kubernetes-service-ports.component.html +++ b/custom-src/frontend/app/custom/helm/list-types/kubernetes-service-ports/kubernetes-service-ports.component.html @@ -8,7 +8,7 @@ - + {{ port.name}} {{port.name}} diff --git a/src/jetstream/datastore/datastore.go b/src/jetstream/datastore/datastore.go index 1b8110f8e0..1a1cf3997e 100644 --- a/src/jetstream/datastore/datastore.go +++ b/src/jetstream/datastore/datastore.go @@ -4,6 +4,7 @@ import ( "database/sql" "fmt" "os" + "path" "regexp" "strings" "time" @@ -64,7 +65,7 @@ const ( // SQLiteSchemaFile - SQLite schema file SQLiteSchemaFile = "./deploy/db/sqlite_schema.sql" // SQLiteDatabaseFile - SQLite database file - SQLiteDatabaseFile = "./console-database.db" + SQLiteDatabaseFile = "console-database.db" // Default database provider when not specified DefaultDatabaseProvider = MYSQL ) @@ -147,16 +148,20 @@ func GetConnection(dc DatabaseConfig, env *env.VarSet) (*sql.DB, error) { } // SQL Lite - return GetSQLLiteConnection(env.MustBool("SQLITE_KEEP_DB")) + return GetSQLLiteConnection(env.MustBool("SQLITE_KEEP_DB"), env.String("SQLITE_DB_DIR", ".")) } // GetSQLLiteConnection returns an SQLite DB Connection -func GetSQLLiteConnection(sqliteKeepDB bool) (*sql.DB, error) { +func GetSQLLiteConnection(sqliteKeepDB bool, sqlDbDir string) (*sql.DB, error) { if !sqliteKeepDB { os.Remove(SQLiteDatabaseFile) } - db, err := sql.Open("sqlite3", SQLiteDatabaseFile) + dbFilePath := path.Join(sqlDbDir, SQLiteDatabaseFile) + + log.Infof("SQLite Database file: %s", dbFilePath) + + db, err := sql.Open("sqlite3", dbFilePath) if err != nil { return nil, err } diff --git a/src/jetstream/plugins/kubernetes/endpoint_config.go b/src/jetstream/plugins/kubernetes/endpoint_config.go index cf2fbe79a2..06893ebe52 100644 --- a/src/jetstream/plugins/kubernetes/endpoint_config.go +++ b/src/jetstream/plugins/kubernetes/endpoint_config.go @@ -3,6 +3,7 @@ package kubernetes import ( "encoding/json" "errors" + "fmt" "strings" log "github.com/sirupsen/logrus" @@ -15,7 +16,7 @@ import ( ) // GetConfigForEndpoint gets a config for the Kubernetes go-client for the specified endpoint -func GetConfigForEndpoint(masterURL string, token interfaces.TokenRecord) (*restclient.Config, error) { +func (c *KubernetesSpecification) GetConfigForEndpoint(masterURL string, token interfaces.TokenRecord) (*restclient.Config, error) { return clientcmd.BuildConfigFromKubeconfigGetter(masterURL, func() (*clientcmdapi.Config, error) { log.Debug("GetConfigForEndpoint") @@ -36,7 +37,7 @@ func GetConfigForEndpoint(masterURL string, token interfaces.TokenRecord) (*rest // Configure auth information authInfo := clientcmdapi.NewAuthInfo() - err := addAuthInfoForEndpoint(authInfo, token) + err := c.addAuthInfoForEndpoint(authInfo, token) config := clientcmdapi.NewConfig() config.Clusters[name] = cluster @@ -49,7 +50,7 @@ func GetConfigForEndpoint(masterURL string, token interfaces.TokenRecord) (*rest } -func addAuthInfoForEndpoint(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { +func (c *KubernetesSpecification) addAuthInfoForEndpoint(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { log.Debug("addAuthInfoForEndpoint") log.Warn(tokenRec.AuthType) @@ -57,16 +58,18 @@ func addAuthInfoForEndpoint(info *clientcmdapi.AuthInfo, tokenRec interfaces.Tok switch { case tokenRec.AuthType == "gke-auth": log.Warn("GKE AUTH") - return addGKEAuth(info, tokenRec) + return c.addGKEAuth(info, tokenRec) case tokenRec.AuthType == AuthConnectTypeCertAuth, tokenRec.AuthType == AuthConnectTypeKubeConfigAz: - return addCertAuth(info, tokenRec) + return c.addCertAuth(info, tokenRec) + case tokenRec.AuthType == AuthConnectTypeAWSIAM: + return c.addAWSAuth(info, tokenRec) default: log.Error("Unsupported auth type") } return errors.New("Unsupported auth type") } -func addCertAuth(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { +func (c *KubernetesSpecification) addCertAuth(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { kubeAuthToken := &KubeCertAuth{} err := json.NewDecoder(strings.NewReader(tokenRec.AuthToken)).Decode(kubeAuthToken) if err != nil { @@ -80,15 +83,33 @@ func addCertAuth(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) e return nil } -func addGKEAuth(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { +func (c *KubernetesSpecification) addGKEAuth(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { gkeInfo := &GKEConfig{} err := json.Unmarshal([]byte(tokenRec.RefreshToken), &gkeInfo) if err != nil { return err } - log.Warn("HERE") - info.Token = tokenRec.AuthToken return nil } + +func (c *KubernetesSpecification) addAWSAuth(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { + + awsInfo := &AWSIAMUserInfo{} + err := json.Unmarshal([]byte(tokenRec.RefreshToken), &awsInfo) + if err != nil { + return err + } + + // NOTE: We really should check first to see if the token has expired before we try and get another + + // Get an access token + token, err := c.getTokenIAM(*awsInfo) + if err != nil { + return fmt.Errorf("Could not get new token using the IAM info: %v+", err) + } + + info.Token = token + return nil +} diff --git a/src/jetstream/plugins/kubernetes/helm_client.go b/src/jetstream/plugins/kubernetes/helm_client.go index 305da7a13f..2bbb9e8148 100644 --- a/src/jetstream/plugins/kubernetes/helm_client.go +++ b/src/jetstream/plugins/kubernetes/helm_client.go @@ -46,7 +46,7 @@ func (c *KubernetesSpecification) GetHelmClient(endpointGUID, userID string) (he return nil, nil, nil, errors.New("Can not get user token for endpoint") } - config, err := GetConfigForEndpoint(cnsiRecord.APIEndpoint.String(), tokenRecord) + config, err := c.GetConfigForEndpoint(cnsiRecord.APIEndpoint.String(), tokenRecord) if err != nil { return nil, nil, nil, errors.New("Can not get Kubernetes config for specified endpoint") } diff --git a/src/jetstream/plugins/kubernetes/kube_dashboard.go b/src/jetstream/plugins/kubernetes/kube_dashboard.go index d24d596380..afcbb0b078 100644 --- a/src/jetstream/plugins/kubernetes/kube_dashboard.go +++ b/src/jetstream/plugins/kubernetes/kube_dashboard.go @@ -51,9 +51,9 @@ func (r *responder) Error(w http.ResponseWriter, req *http.Request, err error) { } // Get the config for the certificate authentication -func getConfig(cnsiRecord *interfaces.CNSIRecord, tokenRecord *interfaces.TokenRecord) (*rest.Config, error) { +func (k *KubernetesSpecification) getConfig(cnsiRecord *interfaces.CNSIRecord, tokenRecord *interfaces.TokenRecord) (*rest.Config, error) { masterURL := cnsiRecord.APIEndpoint.String() - return GetConfigForEndpoint(masterURL, *tokenRecord) + return k.GetConfigForEndpoint(masterURL, *tokenRecord) } // Get the config for the certificate authentication @@ -159,7 +159,7 @@ func (k *KubernetesSpecification) kubeDashboardProxy(c echo.Context) error { targetURL, _ := url.Parse(target) targetURL = normalizeLocation(targetURL) - config, err := getConfig(&cnsiRecord, &tokenRec) + config, err := k.getConfig(&cnsiRecord, &tokenRec) if err != nil { return errors.New("Could not get config for this auth type") } diff --git a/src/jetstream/plugins/kubernetes/kube_svc_proxy.go b/src/jetstream/plugins/kubernetes/kube_svc_proxy.go index d62154a6fb..f4035a6500 100644 --- a/src/jetstream/plugins/kubernetes/kube_svc_proxy.go +++ b/src/jetstream/plugins/kubernetes/kube_svc_proxy.go @@ -59,13 +59,14 @@ func (k *KubernetesSpecification) kubeServiceProxy(c echo.Context) error { // Currently this is not cached, so we must get it each time apiEndpoint := cnsiRecord.APIEndpoint log.Debug(apiEndpoint) + absTarget := fmt.Sprintf("/api/v1/namespaces/%s/services/%s:%s/proxy/", namespace, serviceName, servicePortName) target := fmt.Sprintf("%s/api/v1/namespaces/%s/services/%s:%s/proxy/%s", apiEndpoint, namespace, serviceName, servicePortName, path) targetURL, _ := url.Parse(target) targetURL = normalizeLocation(targetURL) log.Infof("Target URL: %s", targetURL) - config, err := getConfig(&cnsiRecord, &tokenRec) + config, err := k.getConfig(&cnsiRecord, &tokenRec) if err != nil { return errors.New("Could not get config for this auth type") } @@ -139,6 +140,15 @@ func (k *KubernetesSpecification) kubeServiceProxy(c echo.Context) error { response.Header.Del("X-FRAME-OPTIONS") response.Header.Set("X-FRAME-OPTIONS", "sameorigin") log.Debug("%v+", response) + + if response.StatusCode == 302 { + redirect := response.Header.Get("Location") + if strings.Index(redirect, absTarget) == 0 { + redirect = redirect[len(absTarget):] + response.Header.Set("Location", redirect) + } + + } return nil } diff --git a/src/jetstream/plugins/kubernetes/main.go b/src/jetstream/plugins/kubernetes/main.go index c8b1f1553a..b4d004f945 100644 --- a/src/jetstream/plugins/kubernetes/main.go +++ b/src/jetstream/plugins/kubernetes/main.go @@ -152,6 +152,8 @@ func (c *KubernetesSpecification) AddSessionGroupRoutes(echoGroup *echo.Group) { // Kubernetes Dashboard Proxy echoGroup.GET("/kubedash/ui/:guid/*", c.kubeDashboardProxy) echoGroup.GET("/kubedash/:guid/status", c.kubeDashboardStatus) + echoGroup.GET("/kubesvc/:guid/:ns/:svc/:port/*", c.kubeServiceProxy) + echoGroup.GET("/kubesvc/:guid/:ns/:svc/:port", c.kubeServiceProxy) // Helm Routes echoGroup.GET("/helm/releases", c.ListReleases) From 91ad7f5d40faa18cb6bccaae51058fb73c068050 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 4 Apr 2019 06:25:55 -0500 Subject: [PATCH 014/648] Tweaks --- .../kubernetes-service-ports.component.ts | 2 +- .../helm-release-summary-tab.component.ts | 13 +++---------- .../simple-usage-chart.component.ts | 2 +- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/custom-src/frontend/app/custom/helm/list-types/kubernetes-service-ports/kubernetes-service-ports.component.ts b/custom-src/frontend/app/custom/helm/list-types/kubernetes-service-ports/kubernetes-service-ports.component.ts index 4910d35619..e38ab70603 100644 --- a/custom-src/frontend/app/custom/helm/list-types/kubernetes-service-ports/kubernetes-service-ports.component.ts +++ b/custom-src/frontend/app/custom/helm/list-types/kubernetes-service-ports/kubernetes-service-ports.component.ts @@ -36,6 +36,6 @@ export class KubernetesServicePortsComponent extends CardCell { - const isStatusBusy$ = fetchStatus.pipe(map(d => false)); - this.isBusy$ = combineLatest(this.helmReleaseHelper.isFetching$, isStatusBusy$).pipe( - map(([a, b]) => a || b) - ); - fetchStatus.subscribe(data => { - - this.podsChartData = Object.keys(data.pods.status).map(status => ({ - name: status, - value: data.pods.status[status] - })); + // Pods status (grouped) + this.podsChartData = this.collatePodStatus(data); + // Container Readiness this.containersChartData = [ { name: 'Ready', diff --git a/src/frontend/packages/core/src/shared/components/simple-usage-chart/simple-usage-chart.component.ts b/src/frontend/packages/core/src/shared/components/simple-usage-chart/simple-usage-chart.component.ts index b0d68ba8c8..d39e7d6217 100644 --- a/src/frontend/packages/core/src/shared/components/simple-usage-chart/simple-usage-chart.component.ts +++ b/src/frontend/packages/core/src/shared/components/simple-usage-chart/simple-usage-chart.component.ts @@ -30,7 +30,7 @@ export class SimpleUsageChartComponent { @Input() title = 'Usage'; - @Input() height = '250px'; + @Input() height = '160px'; @Input() thresholds: IChartThresholds = { danger: 85, From a255c19e54ce3f534e7c478a327b906128964eed Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 30 Apr 2019 13:46:58 +0100 Subject: [PATCH 015/648] WIP: Build and gtag AIO image --- deploy/ci/build-images-stable.yml | 104 +++++++++--------- .../tasks/dev-releases/check-docker-image.yml | 36 ++++++ .../tasks/dev-releases/generate-tag-files.yml | 5 +- 3 files changed, 92 insertions(+), 53 deletions(-) create mode 100644 deploy/ci/tasks/dev-releases/check-docker-image.yml diff --git a/deploy/ci/build-images-stable.yml b/deploy/ci/build-images-stable.yml index 716824e395..c87c42eb10 100644 --- a/deploy/ci/build-images-stable.yml +++ b/deploy/ci/build-images-stable.yml @@ -1,5 +1,7 @@ # Stable image build pipeline -# This pipeline builds the stable Docker images for Docker Compose and AIO when the stable tag is updated +# This pipeline builds the stable Docker image for the AIO when the stable tag is updated +# It also tags this stable image with the tag of the release version - e.g. 2.3.0 +# The latest tag is also updated as this track 'stable' --- resource_types: - name: docker-image @@ -24,6 +26,18 @@ resources: private_key: ((github-private-key)) # Match stable tag tag_filter: "stable" + +# Artifacts +- name: image-tag + type: s3 + source: + bucket: ((minio-bucket)) + endpoint: ((minio-server-endpoint)) + regexp: temp-artifacts/release-(.*).tar + access_key_id: ((minio-access-key)) + secret_access_key: ((minio-secret-access-key)) + region_name: eu-central-1 + - name: aio-docker-image type: docker-image source: @@ -31,76 +45,62 @@ resources: password: ((docker-password)) repository: splatform/stratos # Docker Images -- name: jetstream-dc-image - type: docker-image - source: - username: ((docker-username)) - password: ((docker-password)) - repository: ((docker-organization))/stratos-dc-jetstream -- name: dc-db-migrator-image - type: docker-image - source: - username: ((docker-username)) - password: ((docker-password)) - repository: ((docker-organization))/stratos-dc-db-migrator -- name: mariadb-dc-image - type: docker-image - source: - username: ((docker-username)) - password: ((docker-password)) - repository: ((docker-organization))/stratos-dc-mariadb - name: ui-dc-image type: docker-image source: username: ((docker-username)) password: ((docker-password)) repository: ((docker-organization))/stratos-dc-console -groups: -- name: tests - jobs: - - build-dc-images - - build-aio-image jobs: -- name: build-dc-images +- name: generate-tag-files plan: - get: stratos trigger: true - - aggregate: - - do: - - put: jetstream-dc-image - params: - dockerfile: stratos/deploy/Dockerfile.bk - build: stratos/ - target_name: dev-build - tag: stratos/deploy/ci/tasks/build-images/stable-tag - - put: dc-db-migrator-image - params: - dockerfile: stratos/deploy/Dockerfile.bk - build: stratos/ - target_name: db-migrator - tag: stratos/deploy/ci/tasks/build-images/stable-tag - - put: mariadb-dc-image - params: - dockerfile: stratos/deploy/db/Dockerfile.mariadb - build: stratos/deploy/db - tag: stratos/deploy/ci/tasks/build-images/stable-tag - - put: ui-dc-image - params: - dockerfile: stratos/deploy/Dockerfile.ui - build: stratos/ - target_name: prod-build - tag: stratos/deploy/ci/tasks/build-images/stable-tag - timeout: 2h30m - + - do: + - task: generate-tag + file: stratos/deploy/ci/tasks/dev-releases/generate-tag-files.yml + params: + TAG_SUFFIX: ((tag-suffix)) + - put: image-tag + params: + file: image-tag/*.tar + acl: public-read +- name: check-github + plan: + - get: stratos + passed: [generate-tag-files] + trigger: true + - get: image-tag + passed: [generate-tag-files] + params: + unpack: true + - do: + - task: build + privileged: true + timeout: 30m + file: stratos/deploy/ci/tasks/dev-releases/check-docker-image.yml + params: + DOCKER_REGISTRY: ((docker-registry)) + DOCKER_ORG: ((docker-organization)) + DOCKER_USERNAME: ((docker-username)) + DOCKER_PASSWORD: ((docker-password)) + IMAGE_NAME: stratos - name: build-aio-image public: true serial: true plan: - get: stratos + passed: [check-github] trigger: true + - get: image-tag + passed: [check-github] + params: + unpack: true - put: aio-docker-image params: build: stratos dockerfile: stratos/deploy/Dockerfile.all-in-one tag: stratos/deploy/ci/tasks/build-images/stable-tag + tas_as_latest: true + labels_file: image-tag/image-labels diff --git a/deploy/ci/tasks/dev-releases/check-docker-image.yml b/deploy/ci/tasks/dev-releases/check-docker-image.yml new file mode 100644 index 0000000000..b5577bde56 --- /dev/null +++ b/deploy/ci/tasks/dev-releases/check-docker-image.yml @@ -0,0 +1,36 @@ +--- +platform: linux +inputs: +- name: stratos +- name: image-tag +image_resource: + type: docker-image + source: + # Generated using scripts/Dockerfile.stratos-ci + repository: splatform/stratos-ci-concourse + tag: "latest" + +run: + path: sh + args: + - -exc + - | + # Check that an image with the same Commit DOES NOT exist + ROOT_DIR=${PWD} + VERSION=$(cat image-tag/v2-version) + FULL_VERSION=$(cat image-tag/v2-alpha-tag) + GIT_TAG=$(cat image-tag/v2-tag) + STRATOS=${ROOT_DIR}/stratos + + # Make the Docker Auth token + AUTH="${DOCKER_USERNAME}:${DOCKER_PASSWORD}" + TOKEN=`echo ${AUTH} | base64" + + URL=https://${DOCKER_REGISTRY}/v2/${DOCKER_ORG}/${IMAGE_NAME}/tags/list + + # Fetch the image tags + curl -s -H "Authorization: Basic ${TOKEN}" ${URL} + + echo "okay" + + diff --git a/deploy/ci/tasks/dev-releases/generate-tag-files.yml b/deploy/ci/tasks/dev-releases/generate-tag-files.yml index 7e3876cc8e..aa28250ffe 100644 --- a/deploy/ci/tasks/dev-releases/generate-tag-files.yml +++ b/deploy/ci/tasks/dev-releases/generate-tag-files.yml @@ -60,6 +60,9 @@ run: cat > build-args << EOF { "stratos_version": "${LATEST_TAG}" } EOF + cat > image-labels << EOF + { "commit": "${COMMIT_HASH}" } + EOF echo "Created v2-alpha-tag, v2-version and build-args." @@ -73,7 +76,7 @@ run: cat v2-alpha-tag echo "Creating tag file tar..." - tar -cf ${FILENAME}.tar v2-alpha-tag v2-version v2-tag v2-commit build-args ui-build-args + tar -cf ${FILENAME}.tar v2-alpha-tag v2-version v2-tag v2-commit build-args ui-build-args image-labels echo "Created tag file tar as ${FILENAME}.tar" echo "Generate tag files complete!" From a6a4f3aa984098f81aca3f9d8fbd11d8c06c1a17 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 30 Apr 2019 13:51:55 +0100 Subject: [PATCH 016/648] Remove unused DC image --- deploy/ci/build-images-stable.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/deploy/ci/build-images-stable.yml b/deploy/ci/build-images-stable.yml index c87c42eb10..0f842fb7d5 100644 --- a/deploy/ci/build-images-stable.yml +++ b/deploy/ci/build-images-stable.yml @@ -38,19 +38,13 @@ resources: secret_access_key: ((minio-secret-access-key)) region_name: eu-central-1 +# Docker Images - name: aio-docker-image type: docker-image source: username: ((docker-username)) password: ((docker-password)) repository: splatform/stratos -# Docker Images -- name: ui-dc-image - type: docker-image - source: - username: ((docker-username)) - password: ((docker-password)) - repository: ((docker-organization))/stratos-dc-console jobs: - name: generate-tag-files From 1fc5d9bc53600cbae18a68e2093db81ce76e8e68 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 30 Apr 2019 13:59:18 +0100 Subject: [PATCH 017/648] Remove logging --- deploy/ci/build-images-stable.yml | 6 +++--- deploy/ci/tasks/dev-releases/check-docker-image.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/deploy/ci/build-images-stable.yml b/deploy/ci/build-images-stable.yml index 0f842fb7d5..9485aa1493 100644 --- a/deploy/ci/build-images-stable.yml +++ b/deploy/ci/build-images-stable.yml @@ -60,7 +60,7 @@ jobs: params: file: image-tag/*.tar acl: public-read -- name: check-github +- name: check-docker-image plan: - get: stratos passed: [generate-tag-files] @@ -85,10 +85,10 @@ jobs: serial: true plan: - get: stratos - passed: [check-github] + passed: [check-docker-image] trigger: true - get: image-tag - passed: [check-github] + passed: [check-docker-image] params: unpack: true - put: aio-docker-image diff --git a/deploy/ci/tasks/dev-releases/check-docker-image.yml b/deploy/ci/tasks/dev-releases/check-docker-image.yml index b5577bde56..436cdd1cc1 100644 --- a/deploy/ci/tasks/dev-releases/check-docker-image.yml +++ b/deploy/ci/tasks/dev-releases/check-docker-image.yml @@ -13,7 +13,7 @@ image_resource: run: path: sh args: - - -exc + - -ec - | # Check that an image with the same Commit DOES NOT exist ROOT_DIR=${PWD} @@ -24,7 +24,7 @@ run: # Make the Docker Auth token AUTH="${DOCKER_USERNAME}:${DOCKER_PASSWORD}" - TOKEN=`echo ${AUTH} | base64" + TOKEN=`echo "${AUTH}" | base64` URL=https://${DOCKER_REGISTRY}/v2/${DOCKER_ORG}/${IMAGE_NAME}/tags/list From 0f60f2e3efcf6076c840b22b5574721c168b838e Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 30 Apr 2019 14:34:24 +0100 Subject: [PATCH 018/648] Fix docker org --- deploy/ci/build-images-stable.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/ci/build-images-stable.yml b/deploy/ci/build-images-stable.yml index 9485aa1493..da3b200aa7 100644 --- a/deploy/ci/build-images-stable.yml +++ b/deploy/ci/build-images-stable.yml @@ -44,7 +44,7 @@ resources: source: username: ((docker-username)) password: ((docker-password)) - repository: splatform/stratos + repository: ((docker-organization))/stratos jobs: - name: generate-tag-files From 8d87a2a770598b0b45cd7ac586d87ac550312160 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 30 Apr 2019 15:15:17 +0100 Subject: [PATCH 019/648] Debug --- deploy/ci/build-images-stable.yml | 7 +++++++ deploy/ci/tasks/dev-releases/check-docker-image.yml | 6 +++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/deploy/ci/build-images-stable.yml b/deploy/ci/build-images-stable.yml index da3b200aa7..d65888db0b 100644 --- a/deploy/ci/build-images-stable.yml +++ b/deploy/ci/build-images-stable.yml @@ -45,6 +45,7 @@ resources: username: ((docker-username)) password: ((docker-password)) repository: ((docker-organization))/stratos + tag: stable jobs: - name: generate-tag-files @@ -60,6 +61,10 @@ jobs: params: file: image-tag/*.tar acl: public-read +- name: get-image-metadata + plan: + - get: stratos + trigger: true - name: check-docker-image plan: - get: stratos @@ -69,6 +74,8 @@ jobs: passed: [generate-tag-files] params: unpack: true + - get: aio-docker-image + passed: [generate-tag-files] - do: - task: build privileged: true diff --git a/deploy/ci/tasks/dev-releases/check-docker-image.yml b/deploy/ci/tasks/dev-releases/check-docker-image.yml index 436cdd1cc1..f8de49c5db 100644 --- a/deploy/ci/tasks/dev-releases/check-docker-image.yml +++ b/deploy/ci/tasks/dev-releases/check-docker-image.yml @@ -13,7 +13,7 @@ image_resource: run: path: sh args: - - -ec + - -c - | # Check that an image with the same Commit DOES NOT exist ROOT_DIR=${PWD} @@ -22,6 +22,10 @@ run: GIT_TAG=$(cat image-tag/v2-tag) STRATOS=${ROOT_DIR}/stratos + ls ${ROOT_DIR} + ls /aio-docker-image + cat /aio-docker-image/docker_inspect.json + # Make the Docker Auth token AUTH="${DOCKER_USERNAME}:${DOCKER_PASSWORD}" TOKEN=`echo "${AUTH}" | base64` From 8d5cea555a0b3c694223ac0f613aafb02c928766 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 30 Apr 2019 15:29:13 +0100 Subject: [PATCH 020/648] Add missing input --- deploy/ci/build-images-stable.yml | 5 ----- deploy/ci/tasks/dev-releases/check-gh-release.yml | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/deploy/ci/build-images-stable.yml b/deploy/ci/build-images-stable.yml index d65888db0b..1838762a0c 100644 --- a/deploy/ci/build-images-stable.yml +++ b/deploy/ci/build-images-stable.yml @@ -61,10 +61,6 @@ jobs: params: file: image-tag/*.tar acl: public-read -- name: get-image-metadata - plan: - - get: stratos - trigger: true - name: check-docker-image plan: - get: stratos @@ -75,7 +71,6 @@ jobs: params: unpack: true - get: aio-docker-image - passed: [generate-tag-files] - do: - task: build privileged: true diff --git a/deploy/ci/tasks/dev-releases/check-gh-release.yml b/deploy/ci/tasks/dev-releases/check-gh-release.yml index 84529c8419..55ad3f92f3 100644 --- a/deploy/ci/tasks/dev-releases/check-gh-release.yml +++ b/deploy/ci/tasks/dev-releases/check-gh-release.yml @@ -3,6 +3,7 @@ platform: linux inputs: - name: stratos - name: image-tag +- name: aio-docker-image image_resource: type: docker-image source: From 9ac8250347228012c179e7f4825acf5b34bb2e69 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 14:31:58 +0100 Subject: [PATCH 021/648] Add missing input --- deploy/ci/tasks/dev-releases/check-docker-image.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/deploy/ci/tasks/dev-releases/check-docker-image.yml b/deploy/ci/tasks/dev-releases/check-docker-image.yml index f8de49c5db..a5075db328 100644 --- a/deploy/ci/tasks/dev-releases/check-docker-image.yml +++ b/deploy/ci/tasks/dev-releases/check-docker-image.yml @@ -3,6 +3,7 @@ platform: linux inputs: - name: stratos - name: image-tag +- name: aio-docker-image image_resource: type: docker-image source: From 2f27d40aad09d3196c02f07f6c7a25547abf37d6 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 14:42:57 +0100 Subject: [PATCH 022/648] Fix docker image check --- .../tasks/dev-releases/check-docker-image.yml | 21 ++++---- .../tasks/dev-releases/docker-image-helper.sh | 52 +++++++++++++++++++ 2 files changed, 62 insertions(+), 11 deletions(-) create mode 100755 deploy/ci/tasks/dev-releases/docker-image-helper.sh diff --git a/deploy/ci/tasks/dev-releases/check-docker-image.yml b/deploy/ci/tasks/dev-releases/check-docker-image.yml index a5075db328..ca0cc1783f 100644 --- a/deploy/ci/tasks/dev-releases/check-docker-image.yml +++ b/deploy/ci/tasks/dev-releases/check-docker-image.yml @@ -3,7 +3,6 @@ platform: linux inputs: - name: stratos - name: image-tag -- name: aio-docker-image image_resource: type: docker-image source: @@ -22,20 +21,20 @@ run: FULL_VERSION=$(cat image-tag/v2-alpha-tag) GIT_TAG=$(cat image-tag/v2-tag) STRATOS=${ROOT_DIR}/stratos + COMMIT_HASH=$(cat image-tag/v2-commit) ls ${ROOT_DIR} - ls /aio-docker-image - cat /aio-docker-image/docker_inspect.json + + source ${STRATOS}/deploy/ci/tasks/dev-releases/docker-image-helper.sh - # Make the Docker Auth token - AUTH="${DOCKER_USERNAME}:${DOCKER_PASSWORD}" - TOKEN=`echo "${AUTH}" | base64` + # Get the Commit Label for the image + COMMIT=`getDockerImageCommitLabel $DOCKER_REGISTRY $DOCKER_USERNAME $DOCKER_PASSWORD $DOCKER_ORG $IMAGE $TAG_NAME` - URL=https://${DOCKER_REGISTRY}/v2/${DOCKER_ORG}/${IMAGE_NAME}/tags/list + echo "Current Docker Image has Commit $COMMIT" - # Fetch the image tags - curl -s -H "Authorization: Basic ${TOKEN}" ${URL} + cat $GIT_TAG + cat $FULL_VERSION + cat $VERSION + cat $COMMIT_HASH echo "okay" - - diff --git a/deploy/ci/tasks/dev-releases/docker-image-helper.sh b/deploy/ci/tasks/dev-releases/docker-image-helper.sh new file mode 100755 index 0000000000..c961844c36 --- /dev/null +++ b/deploy/ci/tasks/dev-releases/docker-image-helper.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# Get commit label for a docker image + +function dockerMakeCurl() { + local URL=$1 + local MANIFEST=$2 + + if [ "$MANIFEST" == "true" ]; then + + if [ "$TOKEN" != "" ]; then + curl --location -s -H "Accept: application/vnd.docker.distribution.manifest.v2+json" -H "Authorization: Bearer $TOKEN" $URL + else + curl --location -s -H "Accept: application/vnd.docker.distribution.manifest.v2+json" $USER_AUTH $URL + fi + else + if [ "$TOKEN" != "" ]; then + curl --location -s -H "Authorization: Bearer $TOKEN" $URL + else + curl --location -s $USER_AUTH $URL + fi + fi +} + +function getDockerImageCommitLabel() { + + local REG=$1 + local USER=$2 + local PASS=$3 + local ORG=$4 + local IMAGE=$5 + local TAG=$6 + + local USER_AUTH="" + + if [ "$REG" == "docker.io" ]; then + echo "Getting TOKEN for docker.io access" + TOKEN=`curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:$ORG/$IMAGE:pull" | jq -r .token` + AUTH_TYPE="Bearer" + REGISTRY_ADDRESS=https://registry.hub.docker.com + else + USER_AUTH="-u $USER:$PASS" + REGISTRY_ADDRESS=https://$REG + TOKEN="" + fi + + URL=$REGISTRY_ADDRESS/v2/$ORG/$IMAGE/manifests/$TAG + DIGEST=`dockerMakeCurl $URL "true" | jq -r '.config.digest'` + + COMMIT=`dockerMakeCurl "$REGISTRY_ADDRESS/v2/$ORG/$IMAGE/blobs/$DIGEST" "false" | jq -r .container_config.Labels.commit` + echo "$COMMIT" +} From 12fc0055882f381df5b35ae4e3ee0f06edf6b0ff Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 15:25:08 +0100 Subject: [PATCH 023/648] Fix helper --- deploy/ci/tasks/dev-releases/docker-image-helper.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/deploy/ci/tasks/dev-releases/docker-image-helper.sh b/deploy/ci/tasks/dev-releases/docker-image-helper.sh index c961844c36..3f64598821 100755 --- a/deploy/ci/tasks/dev-releases/docker-image-helper.sh +++ b/deploy/ci/tasks/dev-releases/docker-image-helper.sh @@ -34,7 +34,6 @@ function getDockerImageCommitLabel() { local USER_AUTH="" if [ "$REG" == "docker.io" ]; then - echo "Getting TOKEN for docker.io access" TOKEN=`curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:$ORG/$IMAGE:pull" | jq -r .token` AUTH_TYPE="Bearer" REGISTRY_ADDRESS=https://registry.hub.docker.com From d7ce1b16ceb72842a257e1a19a5fc38fc74bd8c8 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 15:41:43 +0100 Subject: [PATCH 024/648] Debugging --- deploy/ci/tasks/dev-releases/check-docker-image.yml | 11 +++++------ deploy/ci/tasks/dev-releases/docker-image-helper.sh | 9 +++++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/deploy/ci/tasks/dev-releases/check-docker-image.yml b/deploy/ci/tasks/dev-releases/check-docker-image.yml index ca0cc1783f..ab5610d942 100644 --- a/deploy/ci/tasks/dev-releases/check-docker-image.yml +++ b/deploy/ci/tasks/dev-releases/check-docker-image.yml @@ -23,18 +23,17 @@ run: STRATOS=${ROOT_DIR}/stratos COMMIT_HASH=$(cat image-tag/v2-commit) - ls ${ROOT_DIR} - source ${STRATOS}/deploy/ci/tasks/dev-releases/docker-image-helper.sh + cat $GIT_TAG + cat $FULL_VERSION + cat $VERSION + cat $COMMIT_HASH + # Get the Commit Label for the image COMMIT=`getDockerImageCommitLabel $DOCKER_REGISTRY $DOCKER_USERNAME $DOCKER_PASSWORD $DOCKER_ORG $IMAGE $TAG_NAME` echo "Current Docker Image has Commit $COMMIT" - cat $GIT_TAG - cat $FULL_VERSION - cat $VERSION - cat $COMMIT_HASH echo "okay" diff --git a/deploy/ci/tasks/dev-releases/docker-image-helper.sh b/deploy/ci/tasks/dev-releases/docker-image-helper.sh index 3f64598821..3598b2f2db 100755 --- a/deploy/ci/tasks/dev-releases/docker-image-helper.sh +++ b/deploy/ci/tasks/dev-releases/docker-image-helper.sh @@ -43,7 +43,16 @@ function getDockerImageCommitLabel() { TOKEN="" fi + echo $TOKEN + echo $REG + echo $ORG + echo $IMAGE + echo $TAG + URL=$REGISTRY_ADDRESS/v2/$ORG/$IMAGE/manifests/$TAG + + dockerMakeCurl $URL "true" | jq . + DIGEST=`dockerMakeCurl $URL "true" | jq -r '.config.digest'` COMMIT=`dockerMakeCurl "$REGISTRY_ADDRESS/v2/$ORG/$IMAGE/blobs/$DIGEST" "false" | jq -r .container_config.Labels.commit` From 10b1b72077265c4ae1afd90d3c456cd310d2ae43 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 15:48:11 +0100 Subject: [PATCH 025/648] Fix --- deploy/ci/tasks/dev-releases/check-docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/ci/tasks/dev-releases/check-docker-image.yml b/deploy/ci/tasks/dev-releases/check-docker-image.yml index ab5610d942..b2709892d7 100644 --- a/deploy/ci/tasks/dev-releases/check-docker-image.yml +++ b/deploy/ci/tasks/dev-releases/check-docker-image.yml @@ -31,7 +31,7 @@ run: cat $COMMIT_HASH # Get the Commit Label for the image - COMMIT=`getDockerImageCommitLabel $DOCKER_REGISTRY $DOCKER_USERNAME $DOCKER_PASSWORD $DOCKER_ORG $IMAGE $TAG_NAME` + getDockerImageCommitLabel $DOCKER_REGISTRY $DOCKER_USERNAME $DOCKER_PASSWORD $DOCKER_ORG $IMAGE $TAG_NAME echo "Current Docker Image has Commit $COMMIT" From 9176e46d4e01a85ba3d6d5808da59c8e2e0d8709 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 16:05:31 +0100 Subject: [PATCH 026/648] Fix --- deploy/ci/tasks/dev-releases/docker-image-helper.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/deploy/ci/tasks/dev-releases/docker-image-helper.sh b/deploy/ci/tasks/dev-releases/docker-image-helper.sh index 3598b2f2db..061cb0bc0b 100755 --- a/deploy/ci/tasks/dev-releases/docker-image-helper.sh +++ b/deploy/ci/tasks/dev-releases/docker-image-helper.sh @@ -6,10 +6,14 @@ function dockerMakeCurl() { local URL=$1 local MANIFEST=$2 + echo $URL + echo $MANIFEST + echo $TOKEN + if [ "$MANIFEST" == "true" ]; then if [ "$TOKEN" != "" ]; then - curl --location -s -H "Accept: application/vnd.docker.distribution.manifest.v2+json" -H "Authorization: Bearer $TOKEN" $URL + curl --location -s -H "Accept: application/vnd.docker.distribution.manifest.v2+json" -H "Authorization: Bearer $TOKEN" $URL else curl --location -s -H "Accept: application/vnd.docker.distribution.manifest.v2+json" $USER_AUTH $URL fi @@ -49,7 +53,8 @@ function getDockerImageCommitLabel() { echo $IMAGE echo $TAG - URL=$REGISTRY_ADDRESS/v2/$ORG/$IMAGE/manifests/$TAG + local URL="$REGISTRY_ADDRESS/v2/$ORG/$IMAGE/manifests/$TAG" + echo "URL: $URL" dockerMakeCurl $URL "true" | jq . From 715f18109be50cb4a83c9021f6f279902652696d Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 16:08:38 +0100 Subject: [PATCH 027/648] Fix --- deploy/ci/tasks/dev-releases/check-docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/ci/tasks/dev-releases/check-docker-image.yml b/deploy/ci/tasks/dev-releases/check-docker-image.yml index b2709892d7..fb1a5735bc 100644 --- a/deploy/ci/tasks/dev-releases/check-docker-image.yml +++ b/deploy/ci/tasks/dev-releases/check-docker-image.yml @@ -31,7 +31,7 @@ run: cat $COMMIT_HASH # Get the Commit Label for the image - getDockerImageCommitLabel $DOCKER_REGISTRY $DOCKER_USERNAME $DOCKER_PASSWORD $DOCKER_ORG $IMAGE $TAG_NAME + getDockerImageCommitLabel $DOCKER_REGISTRY $DOCKER_USERNAME $DOCKER_PASSWORD $DOCKER_ORG $IMAGE_NAME $TAG_NAME echo "Current Docker Image has Commit $COMMIT" From ca16246d1c8dda777b680c3698d37ff9ffcff09a Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 16:12:37 +0100 Subject: [PATCH 028/648] Fix --- deploy/ci/tasks/dev-releases/docker-image-helper.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/deploy/ci/tasks/dev-releases/docker-image-helper.sh b/deploy/ci/tasks/dev-releases/docker-image-helper.sh index 061cb0bc0b..096a9d045d 100755 --- a/deploy/ci/tasks/dev-releases/docker-image-helper.sh +++ b/deploy/ci/tasks/dev-releases/docker-image-helper.sh @@ -6,10 +6,6 @@ function dockerMakeCurl() { local URL=$1 local MANIFEST=$2 - echo $URL - echo $MANIFEST - echo $TOKEN - if [ "$MANIFEST" == "true" ]; then if [ "$TOKEN" != "" ]; then From 207feb2b6fef5914e109fd4616ec60ad7fa4b5ca Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 16:17:06 +0100 Subject: [PATCH 029/648] Fix --- deploy/ci/tasks/dev-releases/check-docker-image.yml | 4 ++++ deploy/ci/tasks/dev-releases/docker-image-helper.sh | 7 ------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/deploy/ci/tasks/dev-releases/check-docker-image.yml b/deploy/ci/tasks/dev-releases/check-docker-image.yml index fb1a5735bc..2b29ff9be4 100644 --- a/deploy/ci/tasks/dev-releases/check-docker-image.yml +++ b/deploy/ci/tasks/dev-releases/check-docker-image.yml @@ -35,5 +35,9 @@ run: echo "Current Docker Image has Commit $COMMIT" + if [ "$COMMIT" == "$COMMIT_HASH" ]; then + echo "Image has already been built and published for commit ${COMMIT_HASH}" + exit 1 + fi echo "okay" diff --git a/deploy/ci/tasks/dev-releases/docker-image-helper.sh b/deploy/ci/tasks/dev-releases/docker-image-helper.sh index 096a9d045d..66c62ba8de 100755 --- a/deploy/ci/tasks/dev-releases/docker-image-helper.sh +++ b/deploy/ci/tasks/dev-releases/docker-image-helper.sh @@ -43,14 +43,7 @@ function getDockerImageCommitLabel() { TOKEN="" fi - echo $TOKEN - echo $REG - echo $ORG - echo $IMAGE - echo $TAG - local URL="$REGISTRY_ADDRESS/v2/$ORG/$IMAGE/manifests/$TAG" - echo "URL: $URL" dockerMakeCurl $URL "true" | jq . From c6f1f8c404427225735303bb411d9b63aac82d63 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 16:25:56 +0100 Subject: [PATCH 030/648] Final fixes --- deploy/ci/build-images-stable.yml | 6 ++++-- deploy/ci/tasks/dev-releases/check-docker-image.yml | 6 ++++-- deploy/ci/tasks/dev-releases/docker-image-helper.sh | 2 -- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/deploy/ci/build-images-stable.yml b/deploy/ci/build-images-stable.yml index 1838762a0c..26b9c99bd7 100644 --- a/deploy/ci/build-images-stable.yml +++ b/deploy/ci/build-images-stable.yml @@ -1,7 +1,7 @@ # Stable image build pipeline # This pipeline builds the stable Docker image for the AIO when the stable tag is updated # It also tags this stable image with the tag of the release version - e.g. 2.3.0 -# The latest tag is also updated as this track 'stable' +# The latest tag is also updated as this tracks 'stable' --- resource_types: - name: docker-image @@ -45,7 +45,7 @@ resources: username: ((docker-username)) password: ((docker-password)) repository: ((docker-organization))/stratos - tag: stable + tag: "stable" jobs: - name: generate-tag-files @@ -82,6 +82,7 @@ jobs: DOCKER_USERNAME: ((docker-username)) DOCKER_PASSWORD: ((docker-password)) IMAGE_NAME: stratos + TAG_NAME: stable - name: build-aio-image public: true serial: true @@ -100,3 +101,4 @@ jobs: tag: stratos/deploy/ci/tasks/build-images/stable-tag tas_as_latest: true labels_file: image-tag/image-labels + additional_tags: image-tag/v2-version diff --git a/deploy/ci/tasks/dev-releases/check-docker-image.yml b/deploy/ci/tasks/dev-releases/check-docker-image.yml index 2b29ff9be4..9d93035a43 100644 --- a/deploy/ci/tasks/dev-releases/check-docker-image.yml +++ b/deploy/ci/tasks/dev-releases/check-docker-image.yml @@ -31,7 +31,7 @@ run: cat $COMMIT_HASH # Get the Commit Label for the image - getDockerImageCommitLabel $DOCKER_REGISTRY $DOCKER_USERNAME $DOCKER_PASSWORD $DOCKER_ORG $IMAGE_NAME $TAG_NAME + COMMIT=`getDockerImageCommitLabel $DOCKER_REGISTRY $DOCKER_USERNAME $DOCKER_PASSWORD $DOCKER_ORG $IMAGE_NAME $TAG_NAME` echo "Current Docker Image has Commit $COMMIT" @@ -40,4 +40,6 @@ run: exit 1 fi - echo "okay" + echo "Docker Image has not been build from this commit" + + echo "OK" diff --git a/deploy/ci/tasks/dev-releases/docker-image-helper.sh b/deploy/ci/tasks/dev-releases/docker-image-helper.sh index 66c62ba8de..12fb25a36b 100755 --- a/deploy/ci/tasks/dev-releases/docker-image-helper.sh +++ b/deploy/ci/tasks/dev-releases/docker-image-helper.sh @@ -45,8 +45,6 @@ function getDockerImageCommitLabel() { local URL="$REGISTRY_ADDRESS/v2/$ORG/$IMAGE/manifests/$TAG" - dockerMakeCurl $URL "true" | jq . - DIGEST=`dockerMakeCurl $URL "true" | jq -r '.config.digest'` COMMIT=`dockerMakeCurl "$REGISTRY_ADDRESS/v2/$ORG/$IMAGE/blobs/$DIGEST" "false" | jq -r .container_config.Labels.commit` From e19eae2504d2a4cb2d08f996ef02b3915341fafe Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 1 May 2019 17:03:27 +0100 Subject: [PATCH 031/648] Fix typo --- deploy/ci/build-images-stable.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/ci/build-images-stable.yml b/deploy/ci/build-images-stable.yml index 26b9c99bd7..e1db43fd95 100644 --- a/deploy/ci/build-images-stable.yml +++ b/deploy/ci/build-images-stable.yml @@ -99,6 +99,6 @@ jobs: build: stratos dockerfile: stratos/deploy/Dockerfile.all-in-one tag: stratos/deploy/ci/tasks/build-images/stable-tag - tas_as_latest: true + tag_as_latest: true labels_file: image-tag/image-labels additional_tags: image-tag/v2-version From d5dccf134a964efa5c9af1499b6c765c0bfa94cc Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 2 May 2019 10:00:51 +0100 Subject: [PATCH 032/648] Add canary tag --- deploy/ci/tasks/build-images/canary-tag | 1 + 1 file changed, 1 insertion(+) create mode 100644 deploy/ci/tasks/build-images/canary-tag diff --git a/deploy/ci/tasks/build-images/canary-tag b/deploy/ci/tasks/build-images/canary-tag new file mode 100644 index 0000000000..be1bd41848 --- /dev/null +++ b/deploy/ci/tasks/build-images/canary-tag @@ -0,0 +1 @@ +canary From 7df2a64f3bf495a66b3234c4f1e398d719ae07c2 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 2 May 2019 14:49:48 +0100 Subject: [PATCH 033/648] Re-enable Helm feature --- .../frontend/app/custom/custom.module.ts | 9 ++++----- src/jetstream/plugins/monocular/main.go | 18 +++++------------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/custom-src/frontend/app/custom/custom.module.ts b/custom-src/frontend/app/custom/custom.module.ts index cbcc12110c..e0dfea55d4 100644 --- a/custom-src/frontend/app/custom/custom.module.ts +++ b/custom-src/frontend/app/custom/custom.module.ts @@ -14,6 +14,8 @@ import { KubernetesSetupModule } from './kubernetes/kubernetes.setup.module'; import { KubeHealthCheck } from './kubernetes/store/kubernetes.actions'; import { SuseAboutInfoComponent } from './suse-about-info/suse-about-info.component'; import { SuseLoginComponent } from './suse-login/suse-login.component'; +import { HelmModule } from './helm/helm.module'; +import { HelmSetupModule } from './helm/helm.setup.module'; const SuseCustomizations: CustomizationsMetadata = { copyright: '© 2019 SUSE', @@ -27,11 +29,8 @@ const SuseCustomizations: CustomizationsMetadata = { SharedModule, MDAppModule, KubernetesSetupModule, - // #150 - Uncomment to enable helm plugin - // --------------------------------------- - // HelmModule, - // HelmSetupModule - // --------------------------------------- + HelmModule, + HelmSetupModule ], declarations: [ SuseLoginComponent, diff --git a/src/jetstream/plugins/monocular/main.go b/src/jetstream/plugins/monocular/main.go index a834164206..260c80ff9d 100644 --- a/src/jetstream/plugins/monocular/main.go +++ b/src/jetstream/plugins/monocular/main.go @@ -45,19 +45,11 @@ func (m *Monocular) GetChartStore() chartsvc.ChartSvcDatastore { // Init performs plugin initialization func (m *Monocular) Init() error { - return errors.New("Manually disabled") - // #150 - Uncomment to enable helm plugin - // --------------------------------------- - // m.ConfigureSQL() - - // m.chartSvcRoutes = chartsvc.GetRoutes() - - // m.InitSync() - - // m.syncOnStartup() - - // return nil - // --------------------------------------- + m.ConfigureSQL() + m.chartSvcRoutes = chartsvc.GetRoutes() + m.InitSync() + m.syncOnStartup() + return nil } func (m *Monocular) syncOnStartup() { From 6313b3d15f90036998e4f5db8c6c29374d935564 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 2 May 2019 15:45:51 +0100 Subject: [PATCH 034/648] Add logging during customization --- build/customize-build.js | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/build/customize-build.js b/build/customize-build.js index 35cfaf5a3d..3c78d51476 100644 --- a/build/customize-build.js +++ b/build/customize-build.js @@ -26,6 +26,7 @@ doShowVersions() doCustomize(false); doGenerateIndexHtml(true); + console.log('Finished applying customizations') cb(); }); @@ -33,6 +34,7 @@ gulp.task('customize-default', function (cb) { doCustomize(true); doGenerateIndexHtml(false); + console.log('Finished applying default customizations') cb(); }); @@ -40,6 +42,7 @@ gulp.task('customize-reset', function (cb) { doCustomize(true, true); doGenerateIndexHtml(false); + console.log('Finished resetting customizations') cb(); }); @@ -112,6 +115,7 @@ if (!reset) { fs.symlinkSync(srcFile, destFile); + console.log(' + Linking file : ' + srcFile + ' ==> ' + destFile); } }) @@ -133,6 +137,7 @@ } if (!reset && fs.existsSync(srcFolder)) { fs.symlinkSync(srcFolder, destFolder); + console.log(' + Linking folder : ' + srcFolder + ' ==> ' + destFolder); } }); } @@ -140,13 +145,10 @@ // Copy the correct custom module to either import the supplied custom module or provide an empty module function doCustomizeCreateModule(forceDefaults, reset, customConfig, baseFolder, customBaseFolder) { const defaultSrcFolder = path.resolve(__dirname, '../src/frontend/packages/core/misc/custom'); - console.log(baseFolder); const destFile = path.join(baseFolder, 'src/custom-import.module.ts'); const customModuleFile = path.join(baseFolder, 'src/custom/custom.module.ts'); const customRoutingModuleFile = path.join(baseFolder, 'src/custom/custom-routing.module.ts'); - console.log(customModuleFile); - // Delete the existing file if it exists if (fs.existsSync(destFile)) { fs.unlinkSync(destFile) @@ -158,9 +160,15 @@ srcFile = 'custom-src.module.ts_'; if (fs.existsSync(customRoutingModuleFile)) { srcFile = 'custom-src-routing.module.ts_'; + console.log(' + Found custom module with routing'); + } else { + console.log(' + Found custom module without routing'); } + } else { + console.log(' + No custom module found - linking empty custom module'); } fs.copySync(path.join(defaultSrcFolder, srcFile), destFile); + console.log(' + Copying file : ' + path.join(defaultSrcFolder, srcFile) + ' ==> ' + destFile); } } @@ -208,6 +216,8 @@ // Generate index.html from template function doGenerateIndexHtml(customize) { + + console.log(' + Generating index.html'); // Copy the default fs.copySync(INDEX_TEMPLATE, INDEX_HTML); @@ -223,6 +233,10 @@ } } + if (metadata.title) { + console.log(' + Overridding title to: "' + metadata.title + '"'); + } + // Patch different page title if there is one var title = metadata.title || 'Stratos'; replace.sync({ files: INDEX_HTML, from: /@@TITLE@@/g, to: title }); @@ -236,6 +250,7 @@ if (fs.existsSync(GIT_METADATA)) { gitMetadata = JSON.parse(fs.readFileSync(GIT_METADATA)); + console.log(" + Project Metadata: " + JSON.stringify(gitMetadata)); } // Git Information From 6663e2e951ef9ed75e5c53fada8cbf69dd7f50f4 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 3 May 2019 06:52:38 +0100 Subject: [PATCH 035/648] Fix typo --- .../kubernetes-gke-auth-form.component.html | 2 +- custom-src/frontend/assets/custom/help/en/connecting_gke.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/custom-src/frontend/app/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.html b/custom-src/frontend/app/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.html index ba7671b329..4d023829ec 100644 --- a/custom-src/frontend/app/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.html +++ b/custom-src/frontend/app/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.html @@ -1,6 +1,6 @@
    - Select an `Application Default Credentails' file: + Select an `Application Default Credentials' file: diff --git a/custom-src/frontend/assets/custom/help/en/connecting_gke.md b/custom-src/frontend/assets/custom/help/en/connecting_gke.md index dcdffdcf78..6940477366 100644 --- a/custom-src/frontend/assets/custom/help/en/connecting_gke.md +++ b/custom-src/frontend/assets/custom/help/en/connecting_gke.md @@ -18,4 +18,4 @@ This is the file that you should use when connecting in the endpoint connection > Note: You may need to copy this file to a non-hidden folder in order to be able to browse to it in the UI (or enable hidden files in your OS's file browser) -> Note: For more information on obtaining Application Default Credentails, refer to https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login \ No newline at end of file +> Note: For more information on obtaining Application Default Credentials, refer to https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login \ No newline at end of file From 807fc0c84ec5e4f9696d2c42cd5abacf5f00526f Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 3 May 2019 08:23:19 +0100 Subject: [PATCH 036/648] Docker AIO does not build with custom-src folder --- deploy/Dockerfile.all-in-one | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/deploy/Dockerfile.all-in-one b/deploy/Dockerfile.all-in-one index 5ee557343f..dbd4f0720e 100644 --- a/deploy/Dockerfile.all-in-one +++ b/deploy/Dockerfile.all-in-one @@ -1,12 +1,9 @@ # Docker build for all-in-one Stratos FROM splatform/stratos-aio-base:opensuse as builder -COPY --chown=stratos:users *.json ./ -COPY --chown=stratos:users gulpfile.js ./ -COPY --chown=stratos:users src ./src -COPY --chown=stratos:users build ./build/ +# Ensure that we copy the custom-src folder as well +COPY --chown=stratos:users . ./ COPY --chown=stratos:users deploy/tools/generate_cert.sh generate_cert.sh -COPY --chown=stratos:users deploy/db deploy/db COPY --chown=stratos:users deploy/all-in-one/config.all-in-one.properties config.properties RUN npm install \ From f2997df87e2f16f0a6405c38625ee358ef052647 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 3 May 2019 11:25:32 +0100 Subject: [PATCH 037/648] Allow database path to be set - support persistent DB with AIO --- deploy/all-in-one/config.all-in-one.properties | 1 + src/jetstream/datastore/datastore.go | 13 ++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/deploy/all-in-one/config.all-in-one.properties b/deploy/all-in-one/config.all-in-one.properties index 0720f40f1f..31db270dc0 100644 --- a/deploy/all-in-one/config.all-in-one.properties +++ b/deploy/all-in-one/config.all-in-one.properties @@ -11,3 +11,4 @@ SESSION_STORE_SECRET=wheeee! ENCRYPTION_KEY=B374A26A71490437AA024E4FADD5B497FDFF1A8EA6FF12F6FB65AF2720B59CCF STRATOS_DEPLOYMENT_DOCKER_AIO=true SKIP_SSL_VALIDATION=true +SQLITE_KEEP_DB=true \ No newline at end of file diff --git a/src/jetstream/datastore/datastore.go b/src/jetstream/datastore/datastore.go index 1b8110f8e0..5da7fb49fd 100644 --- a/src/jetstream/datastore/datastore.go +++ b/src/jetstream/datastore/datastore.go @@ -4,6 +4,7 @@ import ( "database/sql" "fmt" "os" + "path" "regexp" "strings" "time" @@ -64,8 +65,8 @@ const ( // SQLiteSchemaFile - SQLite schema file SQLiteSchemaFile = "./deploy/db/sqlite_schema.sql" // SQLiteDatabaseFile - SQLite database file - SQLiteDatabaseFile = "./console-database.db" - // Default database provider when not specified + SQLiteDatabaseFile = "console-database.db" + // DefaultDatabaseProvider is the efault database provider when not specified DefaultDatabaseProvider = MYSQL ) @@ -147,16 +148,18 @@ func GetConnection(dc DatabaseConfig, env *env.VarSet) (*sql.DB, error) { } // SQL Lite - return GetSQLLiteConnection(env.MustBool("SQLITE_KEEP_DB")) + return GetSQLLiteConnection(env.MustBool("SQLITE_KEEP_DB"), env.String("SQLITE_DB_DIR", ".")) } // GetSQLLiteConnection returns an SQLite DB Connection -func GetSQLLiteConnection(sqliteKeepDB bool) (*sql.DB, error) { +func GetSQLLiteConnection(sqliteKeepDB bool, sqlDbDir string) (*sql.DB, error) { if !sqliteKeepDB { os.Remove(SQLiteDatabaseFile) } - db, err := sql.Open("sqlite3", SQLiteDatabaseFile) + dbFilePath := path.Join(sqlDbDir, SQLiteDatabaseFile) + log.Infof("SQLite Database file: %s", dbFilePath) + db, err := sql.Open("sqlite3", dbFilePath) if err != nil { return nil, err } From fbea80e96fb61a7576591fe5c982551745fdda0b Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 3 May 2019 14:43:02 +0100 Subject: [PATCH 038/648] Update readme with extra docs --- deploy/all-in-one/README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/deploy/all-in-one/README.md b/deploy/all-in-one/README.md index 53b4ffdceb..58671b6d82 100644 --- a/deploy/all-in-one/README.md +++ b/deploy/all-in-one/README.md @@ -45,6 +45,29 @@ https://localhost:4443 You will be presented with the Stratos Setup welcome screen - you will need to enter your UAA information to configure Stratos. Once complete, you will be able to login with your credentials. +## Persisting the Database + +Each time you start and stop the Docker All-In-One container, you will lose any your UAA configuration, endpoints and connections that you have made in Stratos. + +In order to persist the Stratos database file between runs of the Docker container you can store the database file outside of the docker container. + +Create a folder where the database folder will be stored, e.g. + +``` +mkdir -p ~/stratos-db +``` + +When starting the Docker container, mount a volume for this folder and pass this via the `SQLITE_DB_DIR` environment variable, e.g. + +``` +docker run -p 4443:443 -v ~/stratos-db:/var/stratos-db -e SQLITE_DB_DIR=/var/stratos-db stratos-aio +``` + +Now each time you stop and start the container, Stratos will maintain the database file. + +> Note: You can validate that the environment variable has been correctly set and check the database file location by observing the log file +of the Docker container. You should see a log message similar to: `SQLite Database file: /var/stratos-db/console-database.db` + ## Pushing the All-In-One Docker Image to Cloud Foundry > Note: We recommend setting the session store secret - please use a manifest file for this and set the `SESSION_STORE_SECRET` environment variable. From 295c38cd37664ba7f795ce74955386a6ae9d9ab4 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 16 May 2019 16:43:54 +0100 Subject: [PATCH 039/648] Tidy up auth providers for Kubernetes, add OIDC support for Helm --- src/jetstream/oauth_requests.go | 4 +- src/jetstream/oauth_requests_test.go | 12 +- src/jetstream/passthrough.go | 2 +- .../{awsiam_requests.go => auth/awsiam.go} | 48 ++- .../plugins/kubernetes/auth/azure.go | 102 ++++++ .../{cert_requests.go => auth/cert.go} | 54 +-- .../kubernetes/{gke_auth.go => auth/gke.go} | 48 ++- .../plugins/kubernetes/auth/kubeconfig.go | 21 ++ src/jetstream/plugins/kubernetes/auth/oidc.go | 154 +++++++++ .../plugins/kubernetes/auth/types.go | 50 +++ .../plugins/kubernetes/auth_providers.go | 49 +++ .../plugins/kubernetes/config/kube_config.go | 154 +++++++++ .../plugins/kubernetes/endpoint_config.go | 64 +--- src/jetstream/plugins/kubernetes/go.mod | 2 +- src/jetstream/plugins/kubernetes/go.sum | 5 +- .../plugins/kubernetes/kube_config.go | 323 ------------------ .../plugins/kubernetes/kube_dashboard.go | 20 -- src/jetstream/plugins/kubernetes/main.go | 76 +---- .../repository/interfaces/portal_proxy.go | 5 +- 19 files changed, 682 insertions(+), 511 deletions(-) rename src/jetstream/plugins/kubernetes/{awsiam_requests.go => auth/awsiam.go} (68%) create mode 100644 src/jetstream/plugins/kubernetes/auth/azure.go rename src/jetstream/plugins/kubernetes/{cert_requests.go => auth/cert.go} (68%) rename src/jetstream/plugins/kubernetes/{gke_auth.go => auth/gke.go} (72%) create mode 100644 src/jetstream/plugins/kubernetes/auth/kubeconfig.go create mode 100644 src/jetstream/plugins/kubernetes/auth/oidc.go create mode 100644 src/jetstream/plugins/kubernetes/auth/types.go create mode 100644 src/jetstream/plugins/kubernetes/auth_providers.go create mode 100644 src/jetstream/plugins/kubernetes/config/kube_config.go delete mode 100644 src/jetstream/plugins/kubernetes/kube_config.go diff --git a/src/jetstream/oauth_requests.go b/src/jetstream/oauth_requests.go index f95970567a..b315e15a0e 100644 --- a/src/jetstream/oauth_requests.go +++ b/src/jetstream/oauth_requests.go @@ -47,8 +47,8 @@ func (p *portalProxy) OAuthHandlerFunc(cnsiRequest *interfaces.CNSIRequest, req } } -func (p *portalProxy) doOauthFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { - log.Debug("doOauthFlowRequest") +func (p *portalProxy) DoOAuthFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { + log.Debug("DoOAuthFlowRequest") authHandler := p.OAuthHandlerFunc(cnsiRequest, req, p.RefreshOAuthToken) return p.DoAuthFlowRequest(cnsiRequest, req, authHandler) diff --git a/src/jetstream/oauth_requests_test.go b/src/jetstream/oauth_requests_test.go index 926cf00c9b..caacf39751 100644 --- a/src/jetstream/oauth_requests_test.go +++ b/src/jetstream/oauth_requests_test.go @@ -113,7 +113,7 @@ func TestDoOauthFlowRequestWithValidToken(t *testing.T) { WithArgs(mockCNSIGUID). WillReturnRows(expectedCNSIRecordRow) - res, err := pp.doOauthFlowRequest(&interfaces.CNSIRequest{ + res, err := pp.DoOAuthFlowRequest(&interfaces.CNSIRequest{ GUID: mockCNSIGUID, UserGUID: mockUserGUID, }, req) @@ -255,7 +255,7 @@ func TestDoOauthFlowRequestWithExpiredToken(t *testing.T) { WillReturnResult(sqlmock.NewResult(1, 1)) // - res, err := pp.doOauthFlowRequest(&interfaces.CNSIRequest{ + res, err := pp.DoOAuthFlowRequest(&interfaces.CNSIRequest{ GUID: mockCNSIGUID, UserGUID: mockUserGUID, }, req) @@ -386,7 +386,7 @@ func TestDoOauthFlowRequestWithFailedRefreshMethod(t *testing.T) { WillReturnError(errors.New("Unknown Database Error")) // - _, err := pp.doOauthFlowRequest(&interfaces.CNSIRequest{ + _, err := pp.DoOAuthFlowRequest(&interfaces.CNSIRequest{ GUID: mockCNSIGUID, UserGUID: mockUserGUID, }, req) @@ -430,7 +430,7 @@ func TestDoOauthFlowRequestWithMissingCNSITokenRecord(t *testing.T) { } pp.setCNSITokenRecord("not-the-right-guid", mockUserGUID, mockTokenRecord) - _, err := pp.doOauthFlowRequest(&interfaces.CNSIRequest{ + _, err := pp.DoOAuthFlowRequest(&interfaces.CNSIRequest{ GUID: mockCNSIGUID, UserGUID: mockUserGUID, }, req) @@ -476,7 +476,7 @@ func TestDoOauthFlowRequestWithInvalidCNSIRequest(t *testing.T) { UserGUID: "", } - _, err := pp.doOauthFlowRequest(invalidCNSIRequest, req) + _, err := pp.DoOAuthFlowRequest(invalidCNSIRequest, req) Convey("Oauth flow request erroneously succeeded", func() { So(err, ShouldNotBeNil) @@ -642,7 +642,7 @@ func TestRefreshTokenWithDatabaseErrorOnSave(t *testing.T) { mock.ExpectExec(updateTokens). WillReturnError(errors.New("Unknown Database Error")) // - _, err := pp.doOauthFlowRequest(&interfaces.CNSIRequest{ + _, err := pp.DoOAuthFlowRequest(&interfaces.CNSIRequest{ GUID: mockCNSIGUID, UserGUID: mockUserGUID, }, req) diff --git a/src/jetstream/passthrough.go b/src/jetstream/passthrough.go index 604817f08d..604e034989 100644 --- a/src/jetstream/passthrough.go +++ b/src/jetstream/passthrough.go @@ -403,7 +403,7 @@ func (p *portalProxy) doRequest(cnsiRequest *interfaces.CNSIRequest, done chan<- if authHandler.Handler != nil { res, err = authHandler.Handler(cnsiRequest, req) } else { - res, err = p.doOauthFlowRequest(cnsiRequest, req) + res, err = p.DoOAuthFlowRequest(cnsiRequest, req) } if err != nil { diff --git a/src/jetstream/plugins/kubernetes/awsiam_requests.go b/src/jetstream/plugins/kubernetes/auth/awsiam.go similarity index 68% rename from src/jetstream/plugins/kubernetes/awsiam_requests.go rename to src/jetstream/plugins/kubernetes/auth/awsiam.go index 58c96ade4e..9c8e950605 100644 --- a/src/jetstream/plugins/kubernetes/awsiam_requests.go +++ b/src/jetstream/plugins/kubernetes/auth/awsiam.go @@ -1,4 +1,4 @@ -package kubernetes +package auth import ( "encoding/json" @@ -11,6 +11,7 @@ import ( "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/labstack/echo" log "github.com/sirupsen/logrus" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" @@ -25,6 +26,41 @@ type AWSIAMUserInfo struct { SecretKey string `json:"secretKey"` } +// AWSKubeAuth is AWS IAM Authentication for Kubernetes +type AWSKubeAuth struct { + portalProxy interfaces.PortalProxy +} + +const AuthConnectTypeAWSIAM = "aws-iam" + +// InitAWSKubeAuth creates a GKEKubeAuth +func InitAWSKubeAuth(portalProxy interfaces.PortalProxy) KubeAuthProvider { + return &AWSKubeAuth{portalProxy: portalProxy} +} + +func (c *AWSKubeAuth) GetName() string { + return AuthConnectTypeAWSIAM +} + +func (c *AWSKubeAuth) AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { + awsInfo := &AWSIAMUserInfo{} + err := json.Unmarshal([]byte(tokenRec.RefreshToken), &awsInfo) + if err != nil { + return err + } + + // NOTE: We really should check first to see if the token has expired before we try and get another + + // Get an access token + token, err := c.getTokenIAM(*awsInfo) + if err != nil { + return fmt.Errorf("Could not get new token using the IAM info: %v+", err) + } + + info.Token = token + return nil +} + func (c *AWSIAMUserInfo) Retrieve() (credentials.Value, error) { return credentials.Value{ AccessKeyID: c.AccessKey, @@ -36,7 +72,7 @@ func (c *AWSIAMUserInfo) IsExpired() bool { return true } -func (c *KubernetesSpecification) FetchIAMToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { +func (c *AWSKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { log.Debug("FetchIAMToken") // Place the IAM properties into a JSON Struct and store that in the Refresh Token @@ -73,14 +109,14 @@ func (c *KubernetesSpecification) FetchIAMToken(cnsiRecord interfaces.CNSIRecord return &tokenRecord, &cnsiRecord, nil } -func (c *KubernetesSpecification) GetCNSIUserFromIAMToken(cnsiGUID string, cfTokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) { +func (c *AWSKubeAuth) GetUserFromToken(cnsiGUID string, cfTokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) { return &interfaces.ConnectedUser{ GUID: "AWS IAM", Name: "IAM", }, true } -func (c *KubernetesSpecification) getTokenIAM(info AWSIAMUserInfo) (string, error) { +func (c *AWSKubeAuth) getTokenIAM(info AWSIAMUserInfo) (string, error) { generator, err := token.NewGenerator(false) if err != nil { return "", fmt.Errorf("AWS IAM: Failed to create generator due to %+v", err) @@ -105,14 +141,14 @@ func (c *KubernetesSpecification) getTokenIAM(info AWSIAMUserInfo) (string, erro return tok.Token, nil } -func (c *KubernetesSpecification) doAWSIAMFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { +func (c *AWSKubeAuth) DoFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { log.Debug("doAWSIAMFlowRequest") authHandler := c.portalProxy.OAuthHandlerFunc(cnsiRequest, req, c.RefreshIAMToken) return c.portalProxy.DoAuthFlowRequest(cnsiRequest, req, authHandler) } -func (c *KubernetesSpecification) RefreshIAMToken(skipSSLValidation bool, cnsiGUID, userGUID, client, clientSecret, tokenEndpoint string) (t interfaces.TokenRecord, err error) { +func (c *AWSKubeAuth) RefreshIAMToken(skipSSLValidation bool, cnsiGUID, userGUID, client, clientSecret, tokenEndpoint string) (t interfaces.TokenRecord, err error) { log.Debug("RefreshIAMToken") userToken, ok := c.portalProxy.GetCNSITokenRecordWithDisconnected(cnsiGUID, userGUID) diff --git a/src/jetstream/plugins/kubernetes/auth/azure.go b/src/jetstream/plugins/kubernetes/auth/azure.go new file mode 100644 index 0000000000..4f0f0ab41b --- /dev/null +++ b/src/jetstream/plugins/kubernetes/auth/azure.go @@ -0,0 +1,102 @@ +package auth + +import ( + "encoding/base64" + "errors" + "io/ioutil" + "time" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/config" + + "github.com/labstack/echo" +) + +const AuthConnectTypeKubeConfigAz = "kubeconfig-az" + + +// AzureKubeAuth is Azure Authentication with Certificates +type AzureKubeAuth struct { + CertKubeAuth +} + +// InitAzureKubeAuth creates a AzureKubeAuth +func InitAzureKubeAuth(portalProxy interfaces.PortalProxy) KubeAuthProvider { + return &AzureKubeAuth{*InitCertKubeAuth(portalProxy)} +} + +// GetName returns the provider name +func (c *AzureKubeAuth) GetName() string { + return AuthConnectTypeKubeConfigAz +} + +func (p *AzureKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { + req := ec.Request() + + // Need to extract the parameters from the request body + defer req.Body.Close() + body, err := ioutil.ReadAll(req.Body) + if err != nil { + return nil, nil, err + } + + kubeConfig, err := config.ParseKubeConfig(body) + + kubeConfigUser, err := kubeConfig.GetUserForCluster(cnsiRecord.APIEndpoint.String()) + if err != nil { + return nil, nil, errors.New("Unable to find cluster in kubeconfig") + } + + authConfig, err := p.getAKSAuthConfig(kubeConfigUser) + if err != nil { + return nil, nil, errors.New("User doesn't use AKS auth") + } + + jsonString, err := authConfig.GetJSON() + if err != nil { + return nil, nil, err + } + // Refresh token isn't required since the AccessToken will never expire + refreshToken := jsonString + + accessToken := jsonString + // Indefinite expiry + expiry := time.Now().Local().Add(time.Hour * time.Duration(100000)) + + tokenRecord := p.portalProxy.InitEndpointTokenRecord(expiry.Unix(), accessToken, refreshToken, false) + tokenRecord.AuthType = AuthConnectTypeKubeConfigAz + + return &tokenRecord, &cnsiRecord, nil +} + + +func (p *AzureKubeAuth) getAKSAuthConfig(k *config.KubeConfigUser) (*KubeCertificate, error) { + + if !isAKSAuth(k) { + return nil, errors.New("User doesn't use AKS") + } + + cert, err := base64.StdEncoding.DecodeString(k.User.ClientCertificate) + if err != nil { + return nil, errors.New("Unable to decode certificate") + } + certKey, err := base64.StdEncoding.DecodeString(k.User.ClientKeyData) + if err != nil { + return nil, errors.New("Unable to decode certificate key") + } + kubeCertAuth := &KubeCertificate{ + Certificate: string(cert), + CertificateKey: string(certKey), + Token: k.User.Token, + } + return kubeCertAuth, nil +} + +func isAKSAuth(k *config.KubeConfigUser) bool { + if k.User.ClientCertificate == "" || + k.User.ClientKeyData == "" || + k.User.Token == "" { + return false + } + return true +} diff --git a/src/jetstream/plugins/kubernetes/cert_requests.go b/src/jetstream/plugins/kubernetes/auth/cert.go similarity index 68% rename from src/jetstream/plugins/kubernetes/cert_requests.go rename to src/jetstream/plugins/kubernetes/auth/cert.go index de552389ea..9e7569587b 100644 --- a/src/jetstream/plugins/kubernetes/cert_requests.go +++ b/src/jetstream/plugins/kubernetes/auth/cert.go @@ -1,4 +1,4 @@ -package kubernetes +package auth import ( "bytes" @@ -16,33 +16,43 @@ import ( "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/labstack/echo" log "github.com/sirupsen/logrus" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" ) -type KubeCertAuth struct { - Certificate string `json:"cert"` - CertificateKey string `json:"certKey"` - Token string `json:"token,omitempty"` +const AuthConnectTypeCertAuth = "kube-cert-auth" + + +// CertKubeAuth is GKE Authentication with Certificates +type CertKubeAuth struct { + portalProxy interfaces.PortalProxy } -func (k *KubeCertAuth) GetJSON() (string, error) { - jsonString, err := json.Marshal(k) - if err != nil { - return "", err - } - return string(jsonString), nil +// InitCertKubeAuth creates a GKEKubeAuth +func InitCertKubeAuth(portalProxy interfaces.PortalProxy) *CertKubeAuth { + return &CertKubeAuth{portalProxy: portalProxy} } -func (k *KubeCertAuth) GetCerticate() (tls.Certificate, error) { - cert, err := tls.X509KeyPair([]byte(k.Certificate), []byte(k.CertificateKey)) +func (c *CertKubeAuth) GetName() string { + return AuthConnectTypeCertAuth +} + +func (c *CertKubeAuth) AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { + kubeAuthToken := &KubeCertificate{} + err := json.NewDecoder(strings.NewReader(tokenRec.AuthToken)).Decode(kubeAuthToken) if err != nil { - return tls.Certificate{}, err + return err } - return cert, nil + + info.ClientCertificateData = []byte(kubeAuthToken.Certificate) + info.ClientKeyData = []byte(kubeAuthToken.CertificateKey) + info.Token = kubeAuthToken.Token + + return nil } -func (c *KubernetesSpecification) extractCerts(ec echo.Context) (*KubeCertAuth, error) { +func (c *CertKubeAuth) extractCerts(ec echo.Context) (*KubeCertificate, error) { - kubeCertAuth := &KubeCertAuth{} + kubeCertAuth := &KubeCertificate{} bodyReader := ec.Request().Body defer bodyReader.Close() @@ -66,7 +76,7 @@ func (c *KubernetesSpecification) extractCerts(ec echo.Context) (*KubeCertAuth, } -func (c *KubernetesSpecification) FetchCertAuth(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { +func (c *CertKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { log.Info("FetchCerts") kubeCertAuth, err := c.extractCerts(ec) @@ -92,19 +102,19 @@ func (c *KubernetesSpecification) FetchCertAuth(cnsiRecord interfaces.CNSIRecord return &tokenRecord, &cnsiRecord, nil } -func (c *KubernetesSpecification) GetCNSIUserFromCertAuth(cnsiGUID string, cfTokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) { +func (c *CertKubeAuth) GetUserFromToken(cnsiGUID string, cfTokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) { return &interfaces.ConnectedUser{ GUID: "Kube Cert Auth", Name: "Cert Auth", }, true } -func (c *KubernetesSpecification) doCertAuthFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { +func (c *CertKubeAuth) DoFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { log.Debug("doCertAuthFlowRequest") authHandler := func(tokenRec interfaces.TokenRecord, cnsi interfaces.CNSIRecord) (*http.Response, error) { - kubeAuthToken := &KubeCertAuth{} + kubeAuthToken := &KubeCertificate{} err := json.NewDecoder(strings.NewReader(tokenRec.AuthToken)).Decode(kubeAuthToken) if err != nil { return nil, err @@ -154,7 +164,7 @@ func (c *KubernetesSpecification) doCertAuthFlowRequest(cnsiRequest *interfaces. return c.portalProxy.DoAuthFlowRequest(cnsiRequest, req, authHandler) } -func (c *KubernetesSpecification) RefreshCertAuth(skipSSLValidation bool, cnsiGUID, userGUID, client, clientSecret, tokenEndpoint string) (t interfaces.TokenRecord, err error) { +func (c *CertKubeAuth) RefreshCertAuth(skipSSLValidation bool, cnsiGUID, userGUID, client, clientSecret, tokenEndpoint string) (t interfaces.TokenRecord, err error) { log.Debug("RefreshCertAuth") // This shouldn't be called since cert-auth K8S shouldn't expire diff --git a/src/jetstream/plugins/kubernetes/gke_auth.go b/src/jetstream/plugins/kubernetes/auth/gke.go similarity index 72% rename from src/jetstream/plugins/kubernetes/gke_auth.go rename to src/jetstream/plugins/kubernetes/auth/gke.go index c25d8631da..3bdeced3b4 100644 --- a/src/jetstream/plugins/kubernetes/gke_auth.go +++ b/src/jetstream/plugins/kubernetes/auth/gke.go @@ -1,4 +1,4 @@ -package kubernetes +package auth import ( "encoding/json" @@ -13,6 +13,7 @@ import ( "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/labstack/echo" log "github.com/sirupsen/logrus" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "github.com/SermoDigital/jose/jws" ) @@ -31,9 +32,36 @@ type GKEConfig struct { Email string `json:"email"` } -// FetchGKEToken will create a token for the GKE Authentication using the POSTed data -func (c *KubernetesSpecification) FetchGKEToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { - log.Debug("FetchGKEToken") +// GKEKubeAuth is GKE Authentication for Kubernetes +type GKEKubeAuth struct { + portalProxy interfaces.PortalProxy +} + +const AuthConnectTypeGKE = "gke-auth" + +// InitGKEKubeAuth creates a GKEKubeAuth +func InitGKEKubeAuth(portalProxy interfaces.PortalProxy) KubeAuthProvider { + return &GKEKubeAuth{portalProxy: portalProxy} +} + +func (c *GKEKubeAuth) GetName() string { + return AuthConnectTypeGKE +} + +func (c *GKEKubeAuth) AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { + gkeInfo := &GKEConfig{} + err := json.Unmarshal([]byte(tokenRec.RefreshToken), &gkeInfo) + if err != nil { + return err + } + + info.Token = tokenRec.AuthToken + return nil +} + +// FetchToken will create a token for the GKE Authentication using the POSTed data +func (c *GKEKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { + log.Debug("FetchToken (GKE)") // We should already have the refresh token in the body sent to us req := ec.Request() @@ -83,9 +111,9 @@ func (c *KubernetesSpecification) FetchGKEToken(cnsiRecord interfaces.CNSIRecord return &tokenRecord, &cnsiRecord, nil } -// GetGKEUserFromToken gets the username from the GKE Token -func (c *KubernetesSpecification) GetGKEUserFromToken(cnsiGUID string, tokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) { - log.Debug("GetGKEUserFromToken") +// GetUserFromToken gets the username from the GKE Token +func (c *GKEKubeAuth) GetUserFromToken(cnsiGUID string, tokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) { + log.Debug("GetUserFromToken (GKE)") gkeInfo := &GKEConfig{} err := json.Unmarshal([]byte(tokenRecord.RefreshToken), &gkeInfo) @@ -99,7 +127,7 @@ func (c *KubernetesSpecification) GetGKEUserFromToken(cnsiGUID string, tokenReco }, true } -func (c *KubernetesSpecification) doGKEFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { +func (c *GKEKubeAuth) DoFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { log.Debug("doGKEFlowRequest") authHandler := c.portalProxy.OAuthHandlerFunc(cnsiRequest, req, c.RefreshGKEToken) @@ -107,7 +135,7 @@ func (c *KubernetesSpecification) doGKEFlowRequest(cnsiRequest *interfaces.CNSIR } // RefreshGKEToken will refresh a GKE token -func (c *KubernetesSpecification) RefreshGKEToken(skipSSLValidation bool, cnsiGUID, userGUID, client, clientSecret, tokenEndpoint string) (t interfaces.TokenRecord, err error) { +func (c *GKEKubeAuth) RefreshGKEToken(skipSSLValidation bool, cnsiGUID, userGUID, client, clientSecret, tokenEndpoint string) (t interfaces.TokenRecord, err error) { log.Debug("RefreshGKEToken") now := time.Now() @@ -137,7 +165,7 @@ func (c *KubernetesSpecification) RefreshGKEToken(skipSSLValidation bool, cnsiGU return userToken, nil } -func (c *KubernetesSpecification) refreshGKEToken(skipSSLValidation bool, clientID, clientSecret, refreshToken string) (u interfaces.UAAResponse, err error) { +func (c *GKEKubeAuth) refreshGKEToken(skipSSLValidation bool, clientID, clientSecret, refreshToken string) (u interfaces.UAAResponse, err error) { log.Debug("refreshGKEToken") tokenInfo := interfaces.UAAResponse{} diff --git a/src/jetstream/plugins/kubernetes/auth/kubeconfig.go b/src/jetstream/plugins/kubernetes/auth/kubeconfig.go new file mode 100644 index 0000000000..085a9d82ac --- /dev/null +++ b/src/jetstream/plugins/kubernetes/auth/kubeconfig.go @@ -0,0 +1,21 @@ +package auth + +import ( + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" +) + +const AuthConnectTypeKubeConfig = "KubeConfig" + +// KubeConfigAuth is same as OIDC with different name +type KubeConfigAuth struct { + OIDCKubeAuth +} + +// InitKubeConfigAuth +func InitKubeConfigAuth(portalProxy interfaces.PortalProxy) KubeAuthProvider { + return &KubeConfigAuth{*InitOIDCKubeAuth(portalProxy)} +} + +func (c *KubeConfigAuth) GetName() string { + return AuthConnectTypeKubeConfig +} diff --git a/src/jetstream/plugins/kubernetes/auth/oidc.go b/src/jetstream/plugins/kubernetes/auth/oidc.go new file mode 100644 index 0000000000..80ce00dda0 --- /dev/null +++ b/src/jetstream/plugins/kubernetes/auth/oidc.go @@ -0,0 +1,154 @@ +package auth + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "io/ioutil" + "time" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/config" + + log "github.com/sirupsen/logrus" + "github.com/labstack/echo" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "github.com/SermoDigital/jose/jws" +) + +type KubeConfigAuthProviderOIDC struct { + ClientID string `yaml:"client-id"` + ClientSecret string `yaml:"client-secret"` + IDToken string `yaml:"id-token"` + IdpIssuerURL string `yaml:"idp-issuer-url"` + RefreshToken string `yaml:"refresh-token"` + Expiry time.Time +} + +const AuthConnectTypeOIDC = "OIDC" + +// OIDCKubeAuth +type OIDCKubeAuth struct { + portalProxy interfaces.PortalProxy +} + +// InitOIDCKubeAuth +func InitOIDCKubeAuth(portalProxy interfaces.PortalProxy) *OIDCKubeAuth { + return &OIDCKubeAuth{portalProxy: portalProxy} +} + +func (c *OIDCKubeAuth) GetName() string { + return AuthConnectTypeOIDC +} + +func (c *OIDCKubeAuth) AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { + log.Info("AddAuthInfo") + + authInfo := &interfaces.OAuth2Metadata{} + err := json.Unmarshal([]byte(tokenRec.Metadata), &authInfo) + if err != nil { + return err + } + + info.AuthProvider = &clientcmdapi.AuthProviderConfig{} + info.AuthProvider.Name = "oidc" + info.AuthProvider.Config = make(map[string]string) + info.AuthProvider.Config["client-id"] = authInfo.ClientID + info.AuthProvider.Config["client-secret"] = authInfo.ClientSecret + info.AuthProvider.Config["idp-issuer-url"] = authInfo.IssuerURL + + info.AuthProvider.Config["id-token"] = tokenRec.AuthToken + info.AuthProvider.Config["refresh-token"] = tokenRec.RefreshToken + info.AuthProvider.Config["extra-scopes"] = "groups" + + return nil +} + +func (c *OIDCKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { + log.Debug("FetchToken (OIDC)") + + req := ec.Request() + + // Need to extract the parameters from the request body + defer req.Body.Close() + body, err := ioutil.ReadAll(req.Body) + if err != nil { + return nil, nil, err + } + + kubeConfig, err := config.ParseKubeConfig(body) + + kubeConfigUser, err := kubeConfig.GetUserForCluster(cnsiRecord.APIEndpoint.String()) + + if err != nil { + return nil, nil, fmt.Errorf("Unable to find cluster in kubeconfig") + } + + // We only support OIDC auth provider at the moment + if kubeConfigUser.User.AuthProvider.Name != "oidc" { + return nil, nil, errors.New("Unsupported authentication provider") + } + + oidcConfig, err := c.GetOIDCConfig(kubeConfigUser) + if err != nil { + log.Info(err) + return nil, nil, errors.New("Can not unmarshal OIDC Auth Provider configuration") + } + tokenRecord := c.portalProxy.InitEndpointTokenRecord(oidcConfig.Expiry.Unix(), oidcConfig.IDToken, oidcConfig.RefreshToken, false) + tokenRecord.AuthType = interfaces.AuthTypeOIDC + + oauthMetadata := &interfaces.OAuth2Metadata{} + oauthMetadata.ClientID = oidcConfig.ClientID + oauthMetadata.ClientSecret = oidcConfig.ClientSecret + oauthMetadata.IssuerURL = oidcConfig.IdpIssuerURL + + jsonString, err := json.Marshal(oauthMetadata) + if err == nil { + tokenRecord.Metadata = string(jsonString) + } + + // Could try and make a K8S Api call to validate the token + // Or, maybe we can verify the access token with the auth URL ? + + return &tokenRecord, &cnsiRecord, nil +} + +// GetUserFromToken gets the username from the GKE Token +func (c *OIDCKubeAuth) GetUserFromToken(cnsiGUID string, tokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) { + log.Debug("GetUserFromToken (OIDC)") + return c.portalProxy.GetCNSIUserFromOAuthToken(cnsiGUID, tokenRecord) +} + +func (c *OIDCKubeAuth) GetOIDCConfig(k *config.KubeConfigUser) (*KubeConfigAuthProviderOIDC, error) { + + if k.User.AuthProvider.Name != "oidc" { + return nil, errors.New("User doesn't use OIDC") + } + + OIDCConfig := &KubeConfigAuthProviderOIDC{} + err := config.UnMarshalHelper(k.User.AuthProvider.Config, OIDCConfig) + if err != nil { + log.Info(err) + return nil, errors.New("Can not unmarshal OIDC Auth Provider configuration") + } + + token, err := jws.ParseJWT([]byte(OIDCConfig.IDToken)) + if err != nil { + log.Info(err) + return nil, errors.New("Can not parse JWT Access token") + } + + expiry, ok := token.Claims().Expiration() + if !ok { + return nil, errors.New("Can not get Acces Token expiry time") + } + OIDCConfig.Expiry = expiry + + return OIDCConfig, nil +} + +func (c *OIDCKubeAuth) DoFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { + log.Debug("DoFlowRequest (OIDC)") + return c.portalProxy.DoOAuthFlowRequest(cnsiRequest, req) +} diff --git a/src/jetstream/plugins/kubernetes/auth/types.go b/src/jetstream/plugins/kubernetes/auth/types.go new file mode 100644 index 0000000000..127ac705b1 --- /dev/null +++ b/src/jetstream/plugins/kubernetes/auth/types.go @@ -0,0 +1,50 @@ +package auth + +import ( + "crypto/tls" + "encoding/json" + "net/http" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + + "github.com/labstack/echo" +) + +// KubeAuthProvider is the interface for Kubernetes Authentication Providers +type KubeAuthProvider interface { + GetName() string + AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error + FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) + + DoFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) + GetUserFromToken(cnsiGUID string, tokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) +} + + +// ------------------------------- + +// KubeCertificate represents certificate infor for Kube Authentication +type KubeCertificate struct { + Certificate string `json:"cert"` + CertificateKey string `json:"certKey"` + Token string `json:"token,omitempty"` +} + +// GetJSON persists the config to JSON +func (k *KubeCertificate) GetJSON() (string, error) { + jsonString, err := json.Marshal(k) + if err != nil { + return "", err + } + return string(jsonString), nil +} + +// GetCerticate gets a certiciate from the info available +func (k *KubeCertificate) GetCerticate() (tls.Certificate, error) { + cert, err := tls.X509KeyPair([]byte(k.Certificate), []byte(k.CertificateKey)) + if err != nil { + return tls.Certificate{}, err + } + return cert, nil +} \ No newline at end of file diff --git a/src/jetstream/plugins/kubernetes/auth_providers.go b/src/jetstream/plugins/kubernetes/auth_providers.go new file mode 100644 index 0000000000..c078f0f61a --- /dev/null +++ b/src/jetstream/plugins/kubernetes/auth_providers.go @@ -0,0 +1,49 @@ +package kubernetes + +import ( + "strings" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/auth" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" +) + +// Interface for Kubernetes Authentication Providers + +var kubeAuthProviders map[string]auth.KubeAuthProvider + +// AddAuthProvider adds a Kubernetes auth provider +func (c *KubernetesSpecification) AddAuthProvider(provider auth.KubeAuthProvider) { + if provider == nil { + return + } + + var name = provider.GetName() + if kubeAuthProviders == nil { + kubeAuthProviders = make(map[string]auth.KubeAuthProvider) + } + + kubeAuthProviders[name] = provider + + // Register auth type with Jetstream + c.portalProxy.AddAuthProvider(name, interfaces.AuthProvider{ + Handler: provider.DoFlowRequest, + UserInfo: provider.GetUserFromToken, + }) + +} + +// GetAuthProvider gets a Kubernetes auth provider by key +func (c *KubernetesSpecification) GetAuthProvider(name string) auth.KubeAuthProvider { + return kubeAuthProviders[name] +} + +// FindAuthProvider finds auth provider - case insensitive +func (c *KubernetesSpecification) FindAuthProvider(name string) auth.KubeAuthProvider { + for k, v := range kubeAuthProviders { + if strings.EqualFold(name, k) { + return v + } + } + + return nil +} diff --git a/src/jetstream/plugins/kubernetes/config/kube_config.go b/src/jetstream/plugins/kubernetes/config/kube_config.go new file mode 100644 index 0000000000..3a9ab7419b --- /dev/null +++ b/src/jetstream/plugins/kubernetes/config/kube_config.go @@ -0,0 +1,154 @@ +package config + +import ( + "errors" + "fmt" + "reflect" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces/config" + + "gopkg.in/yaml.v2" + +) + +type KubeConfigClusterDetail struct { + Server string `yaml:"server"` +} + +type KubeConfigCluster struct { + Name string `yaml:"name"` + Cluster struct { + Server string + } +} + +type KubeConfigUser struct { + Name string `yaml:"name"` + User struct { + AuthProvider struct { + Name string `yaml:"name"` + Config map[string]interface{} `yaml:"config"` + } `yaml:"auth-provider,omitempty"` + ClientCertificate string `yaml:"client-certificate-data,omitempty"` + ClientKeyData string `yaml:"client-key-data,omitempty"` + Token string `yaml:"token,omitempty"` + } +} + +//ExtraScopes string `yaml:"extra-scopes"` + +type KubeConfigContexts struct { + Context struct { + Cluster string + User string + } `yaml:"context"` +} + +type KubeConfigFile struct { + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Clusters []KubeConfigCluster `yaml:"clusters"` + Users []KubeConfigUser `yaml:"users"` + Contexts []KubeConfigContexts `yaml:"contexts"` +} + +func (k *KubeConfigFile) GetClusterByAPIEndpoint(endpoint string) (*KubeConfigCluster, error) { + for _, cluster := range k.Clusters { + if cluster.Cluster.Server == endpoint { + return &cluster, nil + } + } + return nil, fmt.Errorf("Unable to find cluster") +} + +func (k *KubeConfigFile) GetClusterContext(clusterName string) (*KubeConfigContexts, error) { + for _, context := range k.Contexts { + if context.Context.Cluster == clusterName { + return &context, nil + } + } + return nil, fmt.Errorf("Unable to find context") +} + +func (k *KubeConfigFile) GetUser(userName string) (*KubeConfigUser, error) { + for _, user := range k.Users { + if user.Name == userName { + return &user, nil + } + } + return nil, fmt.Errorf("Unable to find user") +} + +func (k *KubeConfigFile) GetUserForCluster(clusterEndpoint string) (*KubeConfigUser, error) { + cluster, err := k.GetClusterByAPIEndpoint(clusterEndpoint) + + if err != nil { + return nil, errors.New("Unable to find cluster in kubeconfig") + } + + clusterName := cluster.Name + + if clusterName == "" { + return nil, errors.New("Unable to find cluster") + } + + context, err := k.GetClusterContext(clusterName) + if err != nil { + return nil, errors.New("Unable to find cluster context") + } + + kubeConfigUser, err := k.GetUser(context.Context.User) + if err != nil { + return nil, errors.New("Can not find config for Kubernetes cluster") + } + + return kubeConfigUser, nil +} + +func ParseKubeConfig(kubeConfigData []byte) (*KubeConfigFile, error) { + + kubeConfig := &KubeConfigFile{} + err := yaml.Unmarshal(kubeConfigData, &kubeConfig) + if err != nil { + return nil, err + } + if kubeConfig.ApiVersion != "v1" || kubeConfig.Kind != "Config" { + return nil, errors.New("Not a valid Kubernetes Config file") + } + + return kubeConfig, nil +} + +func UnMarshalHelper(values map[string]interface{}, intf interface{}) error { + + value := reflect.ValueOf(intf) + + if value.Kind() != reflect.Ptr { + return errors.New("config: must provide pointer to struct value") + } + + value = value.Elem() + if value.Kind() != reflect.Struct { + return errors.New("config: must provide pointer to struct value") + } + + nFields := value.NumField() + typ := value.Type() + + for i := 0; i < nFields; i++ { + field := value.Field(i) + strField := typ.Field(i) + tag := strField.Tag.Get("yaml") + if tag == "" { + continue + } + + if tagValue, ok := values[tag].(string); ok { + if err := config.SetStructFieldValue(value, field, tagValue); err != nil { + return err + } + } + } + + return nil +} diff --git a/src/jetstream/plugins/kubernetes/endpoint_config.go b/src/jetstream/plugins/kubernetes/endpoint_config.go index 06893ebe52..f047d86898 100644 --- a/src/jetstream/plugins/kubernetes/endpoint_config.go +++ b/src/jetstream/plugins/kubernetes/endpoint_config.go @@ -1,10 +1,7 @@ package kubernetes import ( - "encoding/json" "errors" - "fmt" - "strings" log "github.com/sirupsen/logrus" @@ -19,7 +16,7 @@ import ( func (c *KubernetesSpecification) GetConfigForEndpoint(masterURL string, token interfaces.TokenRecord) (*restclient.Config, error) { return clientcmd.BuildConfigFromKubeconfigGetter(masterURL, func() (*clientcmdapi.Config, error) { - log.Debug("GetConfigForEndpoint") + log.Info("GetConfigForEndpoint") name := "cluster-0" @@ -55,61 +52,10 @@ func (c *KubernetesSpecification) addAuthInfoForEndpoint(info *clientcmdapi.Auth log.Debug("addAuthInfoForEndpoint") log.Warn(tokenRec.AuthType) - switch { - case tokenRec.AuthType == "gke-auth": - log.Warn("GKE AUTH") - return c.addGKEAuth(info, tokenRec) - case tokenRec.AuthType == AuthConnectTypeCertAuth, tokenRec.AuthType == AuthConnectTypeKubeConfigAz: - return c.addCertAuth(info, tokenRec) - case tokenRec.AuthType == AuthConnectTypeAWSIAM: - return c.addAWSAuth(info, tokenRec) - default: - log.Error("Unsupported auth type") - } - return errors.New("Unsupported auth type") -} - -func (c *KubernetesSpecification) addCertAuth(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { - kubeAuthToken := &KubeCertAuth{} - err := json.NewDecoder(strings.NewReader(tokenRec.AuthToken)).Decode(kubeAuthToken) - if err != nil { - return err - } - - info.ClientCertificateData = []byte(kubeAuthToken.Certificate) - info.ClientKeyData = []byte(kubeAuthToken.CertificateKey) - info.Token = kubeAuthToken.Token - - return nil -} - -func (c *KubernetesSpecification) addGKEAuth(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { - gkeInfo := &GKEConfig{} - err := json.Unmarshal([]byte(tokenRec.RefreshToken), &gkeInfo) - if err != nil { - return err - } - - info.Token = tokenRec.AuthToken - return nil -} - -func (c *KubernetesSpecification) addAWSAuth(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { - - awsInfo := &AWSIAMUserInfo{} - err := json.Unmarshal([]byte(tokenRec.RefreshToken), &awsInfo) - if err != nil { - return err - } - - // NOTE: We really should check first to see if the token has expired before we try and get another - - // Get an access token - token, err := c.getTokenIAM(*awsInfo) - if err != nil { - return fmt.Errorf("Could not get new token using the IAM info: %v+", err) + var authProvider = c.GetAuthProvider(tokenRec.AuthType) + if authProvider == nil { + return errors.New("Unsupported auth type") } - info.Token = token - return nil + return authProvider.AddAuthInfo(info, tokenRec) } diff --git a/src/jetstream/plugins/kubernetes/go.mod b/src/jetstream/plugins/kubernetes/go.mod index 172fbd9788..b9056ff45a 100644 --- a/src/jetstream/plugins/kubernetes/go.mod +++ b/src/jetstream/plugins/kubernetes/go.mod @@ -1,4 +1,4 @@ -module github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetetes +module github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes go 1.12 diff --git a/src/jetstream/plugins/kubernetes/go.sum b/src/jetstream/plugins/kubernetes/go.sum index 71244c83a1..836c47b4bc 100644 --- a/src/jetstream/plugins/kubernetes/go.sum +++ b/src/jetstream/plugins/kubernetes/go.sum @@ -27,6 +27,8 @@ github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1/go.mod h1:/iP1 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudfoundry-community/go-cfenv v1.17.0 h1:qfxEfn8qKkaHY3ZEk/Y2noY79HBASvNgmtHK9x4+6GY= github.com/cloudfoundry-community/go-cfenv v1.17.0/go.mod h1:2UgWvQTRXUuIZ/x3KnW6fk6CgPBhcV4UQb/UGIrUyyI= +github.com/cloudfoundry-incubator/stratos v2.0.0-beta-001+incompatible h1:UUxNbLjhv2cfymub5yNN1tjjqYkteHBBagb4jcbXEIQ= +github.com/cloudfoundry-incubator/stratos/src/jetstream v0.0.0-20190516104506-727fa3589a90 h1:IeIyBIgh2xEQm1CxHN2yFSBU7Ap+HQZleOs3TlAymI0= github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -91,6 +93,7 @@ github.com/helm/monocular v1.4.0 h1:g0sOpuMe+9u+aPfd9ZO8mWV+c8W0dfGyBG9Wl23nwec= github.com/helm/monocular v1.4.0/go.mod h1:PpkCN0v4zVVigsIHnsQdJytKFmaUkwfhxB7z33a9/gE= github.com/helm/monocular v1.5.0 h1:y8anOb2XLsCluYNOPx01G6EPqyBZ9fxDi+9BKbesULE= github.com/helm/monocular v1.6.0 h1:zE8OggduPEtnF2x1XgrQnMwnKFUGCvk/RNs0NX8hgjg= +github.com/helm/monocular v1.7.0 h1:SW2xr6KoVJtZT6ZAtON8jJVLqcRE3C8pBb9kyo8Icxw= github.com/heptio/authenticator v0.3.0 h1:Xh6XWkLZ+CksGuky+vsr77mHnI9C4L3nwj+xuZu28J0= github.com/heptio/authenticator v0.3.0/go.mod h1:Q86X8hc61JXhE5XxYLKmrSRWby/Oe8IIYZIBgmGVkTA= github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= @@ -151,9 +154,9 @@ github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nL github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday v2.0.0/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v2.0.0 h1:L7Oc72h7rDqGkbUorN/ncJ4N/y220/YRezHvBoKLOFA= -github.com/russross/blackfriday v2.0.0/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME= diff --git a/src/jetstream/plugins/kubernetes/kube_config.go b/src/jetstream/plugins/kubernetes/kube_config.go deleted file mode 100644 index 8d5c530883..0000000000 --- a/src/jetstream/plugins/kubernetes/kube_config.go +++ /dev/null @@ -1,323 +0,0 @@ -package kubernetes - -import ( - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "reflect" - "time" - - "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" - "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces/config" - "github.com/labstack/echo" - log "github.com/sirupsen/logrus" - - "gopkg.in/yaml.v2" - - "github.com/SermoDigital/jose/jws" -) - -type KubeConfigClusterDetail struct { - Server string `yaml:"server"` -} - -type KubeConfigCluster struct { - Name string `yaml:"name"` - Cluster struct { - Server string - } -} - -type KubeConfigAuthProviderOIDC struct { - ClientID string `yaml:"client-id"` - ClientSecret string `yaml:"client-secret"` - IDToken string `yaml:"id-token"` - IdpIssuerURL string `yaml:"idp-issuer-url"` - RefreshToken string `yaml:"refresh-token"` - Expiry time.Time -} - -type KubeConfigUser struct { - Name string `yaml:"name"` - User struct { - AuthProvider struct { - Name string `yaml:"name"` - Config map[string]interface{} `yaml:"config"` - } `yaml:"auth-provider,omitempty"` - ClientCertificate string `yaml:"client-certificate-data,omitempty"` - ClientKeyData string `yaml:"client-key-data,omitempty"` - Token string `yaml:"token,omitempty"` - } -} - -func (k *KubeConfigUser) isOIDCAuth() bool { - if k.User.AuthProvider.Name != "oidc" { - return false - } - return true -} -func (k *KubeConfigUser) isAKSAuth() bool { - if k.User.ClientCertificate == "" || - k.User.ClientKeyData == "" || - k.User.Token == "" { - return false - } - return true -} - -func (k *KubeConfigUser) getOIDCConfig() (*KubeConfigAuthProviderOIDC, error) { - - if !k.isOIDCAuth() { - return nil, errors.New("User doesn't use OIDC") - } - OIDCConfig := &KubeConfigAuthProviderOIDC{} - err := unMarshalHelper(k.User.AuthProvider.Config, OIDCConfig) - if err != nil { - log.Info(err) - return nil, errors.New("Can not unmarshal OIDC Auth Provider configuration") - } - - token, err := jws.ParseJWT([]byte(OIDCConfig.IDToken)) - if err != nil { - log.Info(err) - return nil, errors.New("Can not parse JWT Access token") - } - - expiry, ok := token.Claims().Expiration() - if !ok { - return nil, errors.New("Can not get Acces Token expiry time") - } - OIDCConfig.Expiry = expiry - - return OIDCConfig, nil -} - -func (k *KubeConfigUser) getAKSAuthConfig() (*KubeCertAuth, error) { - - if !k.isAKSAuth() { - return nil, errors.New("User doesn't use AKS") - } - - cert, err := base64.StdEncoding.DecodeString(k.User.ClientCertificate) - if err != nil { - return nil, errors.New("Unable to decode certificate") - } - certKey, err := base64.StdEncoding.DecodeString(k.User.ClientKeyData) - if err != nil { - return nil, errors.New("Unable to decode certificate key") - } - kubeCertAuth := &KubeCertAuth{ - Certificate: string(cert), - CertificateKey: string(certKey), - Token: k.User.Token, - } - return kubeCertAuth, nil -} - -//ExtraScopes string `yaml:"extra-scopes"` - -type KubeConfigContexts struct { - Context struct { - Cluster string - User string - } `yaml:"context"` -} - -type KubeConfigFile struct { - ApiVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` - Clusters []KubeConfigCluster `yaml:"clusters"` - Users []KubeConfigUser `yaml:"users"` - Contexts []KubeConfigContexts `yaml:"contexts"` -} - -func (k *KubeConfigFile) GetClusterByAPIEndpoint(endpoint string) (*KubeConfigCluster, error) { - for _, cluster := range k.Clusters { - if cluster.Cluster.Server == endpoint { - return &cluster, nil - } - } - return nil, fmt.Errorf("Unable to find cluster") -} - -func (k *KubeConfigFile) GetClusterContext(clusterName string) (*KubeConfigContexts, error) { - for _, context := range k.Contexts { - if context.Context.Cluster == clusterName { - return &context, nil - } - } - return nil, fmt.Errorf("Unable to find context") -} - -func (k *KubeConfigFile) GetUser(userName string) (*KubeConfigUser, error) { - for _, user := range k.Users { - if user.Name == userName { - return &user, nil - } - } - return nil, fmt.Errorf("Unable to find user") -} - -func (k *KubeConfigFile) GetUserForCluster(clusterEndpoint string) (*KubeConfigUser, error) { - cluster, err := k.GetClusterByAPIEndpoint(clusterEndpoint) - - if err != nil { - return nil, errors.New("Unable to find cluster in kubeconfig") - } - - clusterName := cluster.Name - - if clusterName == "" { - return nil, errors.New("Unable to find cluster") - } - - context, err := k.GetClusterContext(clusterName) - if err != nil { - return nil, errors.New("Unable to find cluster context") - } - - kubeConfigUser, err := k.GetUser(context.Context.User) - if err != nil { - return nil, errors.New("Can not find config for Kubernetes cluster") - } - - return kubeConfigUser, nil -} - -func (p *KubernetesSpecification) parseKubeConfig(kubeConfigData []byte) (*KubeConfigFile, error) { - - kubeConfig := &KubeConfigFile{} - err := yaml.Unmarshal(kubeConfigData, &kubeConfig) - if err != nil { - return nil, err - } - if kubeConfig.ApiVersion != "v1" || kubeConfig.Kind != "Config" { - return nil, errors.New("Not a valid Kubernetes Config file") - } - - return kubeConfig, nil -} - -func (p *KubernetesSpecification) FetchKubeConfigTokenOIDC(cnsiRecord interfaces.CNSIRecord, c echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { - - req := c.Request() - - // Need to extract the parameters from the request body - defer req.Body.Close() - body, err := ioutil.ReadAll(req.Body) - if err != nil { - return nil, nil, err - } - - kubeConfig, err := p.parseKubeConfig(body) - - kubeConfigUser, err := kubeConfig.GetUserForCluster(cnsiRecord.APIEndpoint.String()) - - if err != nil { - return nil, nil, fmt.Errorf("Unable to find cluster in kubeconfig") - } - - // We only support OIDC auth provider at the moment - if kubeConfigUser.User.AuthProvider.Name != "oidc" { - return nil, nil, errors.New("Unsupported authentication provider") - } - - oidcConfig, err := kubeConfigUser.getOIDCConfig() - if err != nil { - log.Info(err) - return nil, nil, errors.New("Can not unmarshal OIDC Auth Provider configuration") - } - tokenRecord := p.portalProxy.InitEndpointTokenRecord(oidcConfig.Expiry.Unix(), oidcConfig.IDToken, oidcConfig.RefreshToken, false) - tokenRecord.AuthType = interfaces.AuthTypeOIDC - - oauthMetadata := &interfaces.OAuth2Metadata{} - oauthMetadata.ClientID = oidcConfig.ClientID - oauthMetadata.ClientSecret = oidcConfig.ClientSecret - oauthMetadata.IssuerURL = oidcConfig.IdpIssuerURL - - jsonString, err := json.Marshal(oauthMetadata) - if err == nil { - tokenRecord.Metadata = string(jsonString) - } - - // Could try and make a K8S Api call to validate the token - // Or, maybe we can verify the access token with the auth URL ? - - return &tokenRecord, &cnsiRecord, nil -} - -func (p *KubernetesSpecification) FetchKubeConfigTokenAKS(cnsiRecord interfaces.CNSIRecord, c echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { - - req := c.Request() - - // Need to extract the parameters from the request body - defer req.Body.Close() - body, err := ioutil.ReadAll(req.Body) - if err != nil { - return nil, nil, err - } - - kubeConfig, err := p.parseKubeConfig(body) - - kubeConfigUser, err := kubeConfig.GetUserForCluster(cnsiRecord.APIEndpoint.String()) - if err != nil { - return nil, nil, errors.New("Unable to find cluster in kubeconfig") - } - - authConfig, err := kubeConfigUser.getAKSAuthConfig() - if err != nil { - return nil, nil, errors.New("User doesn't use AKS auth") - } - - jsonString, err := authConfig.GetJSON() - if err != nil { - return nil, nil, err - } - // Refresh token isn't required since the AccessToken will never expire - refreshToken := jsonString - - accessToken := jsonString - // Indefinite expiry - expiry := time.Now().Local().Add(time.Hour * time.Duration(100000)) - - tokenRecord := p.portalProxy.InitEndpointTokenRecord(expiry.Unix(), accessToken, refreshToken, false) - tokenRecord.AuthType = AuthConnectTypeKubeConfigAz - - return &tokenRecord, &cnsiRecord, nil -} - -func unMarshalHelper(values map[string]interface{}, intf interface{}) error { - - value := reflect.ValueOf(intf) - - if value.Kind() != reflect.Ptr { - return errors.New("config: must provide pointer to struct value") - } - - value = value.Elem() - if value.Kind() != reflect.Struct { - return errors.New("config: must provide pointer to struct value") - } - - nFields := value.NumField() - typ := value.Type() - - for i := 0; i < nFields; i++ { - field := value.Field(i) - strField := typ.Field(i) - tag := strField.Tag.Get("yaml") - if tag == "" { - continue - } - - if tagValue, ok := values[tag].(string); ok { - if err := config.SetStructFieldValue(value, field, tagValue); err != nil { - return err - } - } - } - - return nil -} diff --git a/src/jetstream/plugins/kubernetes/kube_dashboard.go b/src/jetstream/plugins/kubernetes/kube_dashboard.go index afcbb0b078..f2d37a4b65 100644 --- a/src/jetstream/plugins/kubernetes/kube_dashboard.go +++ b/src/jetstream/plugins/kubernetes/kube_dashboard.go @@ -56,26 +56,6 @@ func (k *KubernetesSpecification) getConfig(cnsiRecord *interfaces.CNSIRecord, t return k.GetConfigForEndpoint(masterURL, *tokenRecord) } -// Get the config for the certificate authentication -func __getConfig(cnsiRecord *interfaces.CNSIRecord, tokenRecord *interfaces.TokenRecord) (*rest.Config, error) { - - config := rest.Config{} - config.Host = cnsiRecord.APIEndpoint.String() - - // Only support certs for now - kubeAuthToken := &KubeCertAuth{} - err := json.NewDecoder(strings.NewReader(tokenRecord.AuthToken)).Decode(kubeAuthToken) - if err != nil { - return nil, err - } - - config.TLSClientConfig = rest.TLSClientConfig{} - config.TLSClientConfig.CertData = []byte(kubeAuthToken.Certificate) - config.TLSClientConfig.KeyData = []byte(kubeAuthToken.CertificateKey) - config.TLSClientConfig.Insecure = true - return &config, nil -} - // makeUpgradeTransport creates a transport that explicitly bypasses HTTP2 support // for proxy connections that must upgrade. func makeUpgradeTransport(config *rest.Config, keepalive time.Duration) (proxy.UpgradeRequestRoundTripper, error) { diff --git a/src/jetstream/plugins/kubernetes/main.go b/src/jetstream/plugins/kubernetes/main.go index b8b0598ed5..26a36b433d 100644 --- a/src/jetstream/plugins/kubernetes/main.go +++ b/src/jetstream/plugins/kubernetes/main.go @@ -2,13 +2,14 @@ package kubernetes import ( "net/url" - "strings" "errors" "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/labstack/echo" log "github.com/sirupsen/logrus" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/auth" ) type KubernetesSpecification struct { @@ -76,73 +77,30 @@ func (c *KubernetesSpecification) Connect(ec echo.Context, cnsiRecord interfaces connectType := ec.FormValue("connect_type") - // OIDC ? - if strings.EqualFold(connectType, AuthConnectTypeKubeConfig) { - tokenRecord, _, err := c.FetchKubeConfigTokenOIDC(cnsiRecord, ec) - if err != nil { - return nil, false, err - } - return tokenRecord, false, nil - } - - // AKS ? - if strings.EqualFold(connectType, AuthConnectTypeKubeConfigAz) { - tokenRecord, _, err := c.FetchKubeConfigTokenAKS(cnsiRecord, ec) - if err != nil { - return nil, false, err - } - return tokenRecord, false, nil - } - - // IAM Creds? - if strings.EqualFold(connectType, AuthConnectTypeAWSIAM) { - tokenRecord, _, err := c.FetchIAMToken(cnsiRecord, ec) - if err != nil { - return nil, false, err - } - return tokenRecord, false, nil - } - - // Cert Auth? - if strings.EqualFold(connectType, AuthConnectTypeCertAuth) { - tokenRecord, _, err := c.FetchCertAuth(cnsiRecord, ec) - if err != nil { - return nil, false, err - } - return tokenRecord, false, nil + var authProvider = c.FindAuthProvider(connectType) + if authProvider == nil { + return nil, false, errors.New("Unsupported Auth connection type for Kubernetes endpoint") } - // GKE ? - if strings.EqualFold(connectType, AuthConnectTypeGKE) { - tokenRecord, _, err := c.FetchGKEToken(cnsiRecord, ec) - if err != nil { - return nil, false, err - } - return tokenRecord, false, nil + tokenRecord, _, err := authProvider.FetchToken(cnsiRecord, ec) + if err != nil { + return nil, false, err } - return nil, false, errors.New("Unsupported Auth connection type for Kubernetes endpoint") + return tokenRecord, false, nil } // Init the Kubernetes Jetstream plugin func (c *KubernetesSpecification) Init() error { - c.portalProxy.AddAuthProvider(AuthConnectTypeAWSIAM, interfaces.AuthProvider{ - Handler: c.doAWSIAMFlowRequest, - UserInfo: c.GetCNSIUserFromIAMToken, - }) - c.portalProxy.AddAuthProvider(AuthConnectTypeCertAuth, interfaces.AuthProvider{ - Handler: c.doCertAuthFlowRequest, - UserInfo: c.GetCNSIUserFromCertAuth, - }) - c.portalProxy.AddAuthProvider(AuthConnectTypeKubeConfigAz, interfaces.AuthProvider{ - Handler: c.doCertAuthFlowRequest, - UserInfo: c.GetCNSIUserFromCertAuth, - }) - c.portalProxy.AddAuthProvider(AuthConnectTypeGKE, interfaces.AuthProvider{ - Handler: c.doGKEFlowRequest, - UserInfo: c.GetGKEUserFromToken, - }) + // Register all of the providers + c.AddAuthProvider(auth.InitGKEKubeAuth(c.portalProxy)) + c.AddAuthProvider(auth.InitAWSKubeAuth(c.portalProxy)) + c.AddAuthProvider(auth.InitCertKubeAuth(c.portalProxy)) + c.AddAuthProvider(auth.InitAzureKubeAuth(c.portalProxy)) + c.AddAuthProvider(auth.InitOIDCKubeAuth(c.portalProxy)) + c.AddAuthProvider(auth.InitKubeConfigAuth(c.portalProxy)) + c.portalProxy.GetConfig().PluginConfig[KubeDashboardPluginConfigSetting] = "false" return nil diff --git a/src/jetstream/repository/interfaces/portal_proxy.go b/src/jetstream/repository/interfaces/portal_proxy.go index cfde40db4f..f8ce8d86b4 100644 --- a/src/jetstream/repository/interfaces/portal_proxy.go +++ b/src/jetstream/repository/interfaces/portal_proxy.go @@ -56,7 +56,7 @@ type PortalProxy interface { RefreshUAALogin(username, password string, store bool) error GetUserTokenInfo(tok string) (u *JWTUserTokenInfo, err error) GetUAAUser(userGUID string) (*ConnectedUser, error) - + // Proxy API requests ProxyRequest(c echo.Context, uri *url.URL) (map[string]*CNSIRequest, error) DoProxyRequest(requests []ProxyRequestInfo) (map[string]*CNSIRequest, error) @@ -65,10 +65,13 @@ type PortalProxy interface { // Database Connection GetDatabaseConnection() *sql.DB + AddAuthProvider(name string, provider AuthProvider) GetAuthProvider(name string) AuthProvider DoAuthFlowRequest(cnsiRequest *CNSIRequest, req *http.Request, authHandler AuthHandlerFunc) (*http.Response, error) OAuthHandlerFunc(cnsiRequest *CNSIRequest, req *http.Request, refreshOAuthTokenFunc RefreshOAuthTokenFunc) AuthHandlerFunc + DoOAuthFlowRequest(cnsiRequest *CNSIRequest, req *http.Request) (*http.Response, error) + GetCNSIUserFromOAuthToken(cnsiGUID string, cfTokenRecord *TokenRecord) (*ConnectedUser, bool) // Tokens - lower-level access SaveEndpointToken(cnsiGUID string, userGUID string, tokenRecord TokenRecord) error From c22658384641b386c62377a0eb313e7c544c9dc5 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 17 May 2019 09:01:47 +0100 Subject: [PATCH 040/648] Moer tidy ups --- src/jetstream/plugins/kubernetes/api_proxy.go | 66 +++++++++++++++ .../plugins/kubernetes/auth/awsiam.go | 8 +- .../plugins/kubernetes/auth/azure.go | 10 +-- src/jetstream/plugins/kubernetes/auth/cert.go | 8 +- .../cert_tests.go} | 6 +- src/jetstream/plugins/kubernetes/auth/gke.go | 7 +- src/jetstream/plugins/kubernetes/auth/oidc.go | 13 +-- .../plugins/kubernetes/auth/types.go | 5 +- .../plugins/kubernetes/auth_providers.go | 2 - .../plugins/kubernetes/endpoint_config.go | 4 - src/jetstream/plugins/kubernetes/go.mod | 5 +- src/jetstream/plugins/kubernetes/go.sum | 10 ++- .../plugins/kubernetes/helm_client.go | 80 +------------------ .../plugins/kubernetes/helm_versions.go | 10 ++- .../plugins/kubernetes/install_release.go | 15 ++-- .../plugins/kubernetes/kube_dashboard.go | 4 +- .../plugins/kubernetes/list_releases.go | 10 +-- src/jetstream/plugins/kubernetes/main.go | 30 +++---- .../monocular/20190307115300_ChartStore.go | 8 +- src/jetstream/plugins/monocular/endpoint.go | 4 +- src/jetstream/plugins/monocular/main.go | 8 +- src/jetstream/plugins/monocular/repository.go | 2 +- src/jetstream/plugins/monocular/sync.go | 11 +-- 23 files changed, 161 insertions(+), 165 deletions(-) create mode 100644 src/jetstream/plugins/kubernetes/api_proxy.go rename src/jetstream/plugins/kubernetes/{cert_requests_test.go => auth/cert_tests.go} (98%) diff --git a/src/jetstream/plugins/kubernetes/api_proxy.go b/src/jetstream/plugins/kubernetes/api_proxy.go new file mode 100644 index 0000000000..bcd14dd99b --- /dev/null +++ b/src/jetstream/plugins/kubernetes/api_proxy.go @@ -0,0 +1,66 @@ +package kubernetes + +import ( + "fmt" + + // "k8s.io/client-go/rest" + + // Import the OIDC auth plugin + _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" +) + +// KubeProxyError represents error when a proxied request to the Kube API failes +type KubeProxyError struct { + Name string +} + +// KubeProxyFunc represents a function to proxy to the Kube API +type KubeProxyFunc func(*interfaces.ConnectedEndpoint, chan KubeProxyResponse) + +// KubeProxyResponse represents a response from a proxy request to the Kube API +type KubeProxyResponse struct { + Endpoint string + Result interface{} + Error *KubeProxyError +} + +// KubeProxyResponses represents response from multiple proxy requests to the Kube API +type KubeProxyResponses map[string]interface{} + +// ProxyKubernetesAPI proxies an API request to all of the user's connected Kubernetes endpoints +func (c *KubernetesSpecification) ProxyKubernetesAPI(userID string, f KubeProxyFunc) (KubeProxyResponses, error) { + + var p = c.portalProxy + k8sList := make([]*interfaces.ConnectedEndpoint, 0) + eps, err := p.ListEndpointsByUser(userID) + if err != nil { + return nil, fmt.Errorf("Could not get endpints Client for endpoint: %v+", err) + } + + for _, endpoint := range eps { + if endpoint.CNSIType == "k8s" { + k8sList = append(k8sList, endpoint) + } + } + + // Check that we actually have some + // TODO + done := make(chan KubeProxyResponse) + for _, endpoint := range k8sList { + go f(endpoint, done) + } + + responses := make(KubeProxyResponses) + for range k8sList { + res := <-done + if res.Error == nil { + responses[res.Endpoint] = res.Result + } else { + responses[res.Endpoint] = res.Error + } + } + + return responses, nil +} diff --git a/src/jetstream/plugins/kubernetes/auth/awsiam.go b/src/jetstream/plugins/kubernetes/auth/awsiam.go index 9c8e950605..5bdc813264 100644 --- a/src/jetstream/plugins/kubernetes/auth/awsiam.go +++ b/src/jetstream/plugins/kubernetes/auth/awsiam.go @@ -20,6 +20,7 @@ import ( "github.com/kubernetes-sigs/aws-iam-authenticator/pkg/token" ) +// AWSIAMUserInfo is the user info needed to connect to AWS Kubernetes type AWSIAMUserInfo struct { Cluster string `json:"cluster"` AccessKey string `json:"accessKey"` @@ -31,15 +32,16 @@ type AWSKubeAuth struct { portalProxy interfaces.PortalProxy } -const AuthConnectTypeAWSIAM = "aws-iam" +const authConnectTypeAWSIAM = "aws-iam" // InitAWSKubeAuth creates a GKEKubeAuth func InitAWSKubeAuth(portalProxy interfaces.PortalProxy) KubeAuthProvider { return &AWSKubeAuth{portalProxy: portalProxy} } +// GetName returns the Auth Provider name func (c *AWSKubeAuth) GetName() string { - return AuthConnectTypeAWSIAM + return authConnectTypeAWSIAM } func (c *AWSKubeAuth) AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { @@ -105,7 +107,7 @@ func (c *AWSKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Conte expiry := time.Now().Local().Add(time.Minute * time.Duration(15)) tokenRecord := c.portalProxy.InitEndpointTokenRecord(expiry.Unix(), accessToken, refreshToken, false) - tokenRecord.AuthType = AuthConnectTypeAWSIAM + tokenRecord.AuthType = authConnectTypeAWSIAM return &tokenRecord, &cnsiRecord, nil } diff --git a/src/jetstream/plugins/kubernetes/auth/azure.go b/src/jetstream/plugins/kubernetes/auth/azure.go index 4f0f0ab41b..0447b2e528 100644 --- a/src/jetstream/plugins/kubernetes/auth/azure.go +++ b/src/jetstream/plugins/kubernetes/auth/azure.go @@ -6,14 +6,13 @@ import ( "io/ioutil" "time" - "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/config" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/labstack/echo" ) -const AuthConnectTypeKubeConfigAz = "kubeconfig-az" - +const authConnectTypeKubeConfigAz = "kubeconfig-az" // AzureKubeAuth is Azure Authentication with Certificates type AzureKubeAuth struct { @@ -27,7 +26,7 @@ func InitAzureKubeAuth(portalProxy interfaces.PortalProxy) KubeAuthProvider { // GetName returns the provider name func (c *AzureKubeAuth) GetName() string { - return AuthConnectTypeKubeConfigAz + return authConnectTypeKubeConfigAz } func (p *AzureKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { @@ -64,12 +63,11 @@ func (p *AzureKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Con expiry := time.Now().Local().Add(time.Hour * time.Duration(100000)) tokenRecord := p.portalProxy.InitEndpointTokenRecord(expiry.Unix(), accessToken, refreshToken, false) - tokenRecord.AuthType = AuthConnectTypeKubeConfigAz + tokenRecord.AuthType = authConnectTypeKubeConfigAz return &tokenRecord, &cnsiRecord, nil } - func (p *AzureKubeAuth) getAKSAuthConfig(k *config.KubeConfigUser) (*KubeCertificate, error) { if !isAKSAuth(k) { diff --git a/src/jetstream/plugins/kubernetes/auth/cert.go b/src/jetstream/plugins/kubernetes/auth/cert.go index 9e7569587b..517ef8a780 100644 --- a/src/jetstream/plugins/kubernetes/auth/cert.go +++ b/src/jetstream/plugins/kubernetes/auth/cert.go @@ -19,8 +19,7 @@ import ( clientcmdapi "k8s.io/client-go/tools/clientcmd/api" ) -const AuthConnectTypeCertAuth = "kube-cert-auth" - +const authConnectTypeCertAuth = "kube-cert-auth" // CertKubeAuth is GKE Authentication with Certificates type CertKubeAuth struct { @@ -32,8 +31,9 @@ func InitCertKubeAuth(portalProxy interfaces.PortalProxy) *CertKubeAuth { return &CertKubeAuth{portalProxy: portalProxy} } +// GetName returns the provider name func (c *CertKubeAuth) GetName() string { - return AuthConnectTypeCertAuth + return authConnectTypeCertAuth } func (c *CertKubeAuth) AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { @@ -98,7 +98,7 @@ func (c *CertKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Cont expiry := time.Now().Local().Add(time.Hour * time.Duration(100000)) disconnected := false tokenRecord := c.portalProxy.InitEndpointTokenRecord(expiry.Unix(), accessToken, refreshToken, disconnected) - tokenRecord.AuthType = AuthConnectTypeCertAuth + tokenRecord.AuthType = authConnectTypeCertAuth return &tokenRecord, &cnsiRecord, nil } diff --git a/src/jetstream/plugins/kubernetes/cert_requests_test.go b/src/jetstream/plugins/kubernetes/auth/cert_tests.go similarity index 98% rename from src/jetstream/plugins/kubernetes/cert_requests_test.go rename to src/jetstream/plugins/kubernetes/auth/cert_tests.go index 223a08df53..9c1616fd10 100644 --- a/src/jetstream/plugins/kubernetes/cert_requests_test.go +++ b/src/jetstream/plugins/kubernetes/auth/cert_tests.go @@ -1,4 +1,4 @@ -package kubernetes +package auth import ( "encoding/json" @@ -11,7 +11,7 @@ import ( func TestFetchCertAuth(t *testing.T) { - kubeSpec := &KubernetesSpecification{} + kubeSpec := &CertKubeAuth{} mockConnectRequet := "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURBRENDQWVpZ0F3SUJBZ0lCQWpBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwdGFXNXAKYTNWaVpVTkJNQjRYRFRFNE1UQXhNakUyTlRnMU1Wb1hEVEU1TVRBeE16RTJOVGcxTVZvd01URVhNQlVHQTFVRQpDaE1PYzNsemRHVnRPbTFoYzNSbGNuTXhGakFVQmdOVkJBTVREVzFwYm1scmRXSmxMWFZ6WlhJd2dnRWlNQTBHCkNTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFEeFNtT2dORmV5VjkyRE5Sa0RmNVNSZlk5WHhGRVkKbFF2Z3lEcmFnVVpCcXFTaFBaZGdFdnBFWlVObkVoa1ZaYnc5WGUxelFMdVFuZnJEaERzOHlqTk8xWTV4ZUt2cwpIQlVBZEsxa2FQMlBXMVhSN3hxREh2SFdhQ3dRWVdHK1Bsa0NXMzg1YzBsNktXRUc5SVlFWFdkT1VDbEZuT3ZoCnJBN2RTNWR5eFViU3FCd0RSSDljMlhDVjQ4c1dLZnJhdmQvMTg5bWJENStQRk9ZditnRFFycHBTYnUzQytjTmYKbC93NDl0L0NQWlQ4bHFWU2FBUHhzOXFKbStKSFVFWHk3a1p2bGRZbG83NjJ3V1B6c0JoaXNkNU5ua2hmbGhrUApKSWNrWjN5eWF5bkdkOWJlKzdva2hwVEt1STNReGh5MTBrMWsxc1dyYmJTWVJDbmQ1akdVN3N2REFnTUJBQUdqClB6QTlNQTRHQTFVZER3RUIvd1FFQXdJRm9EQWRCZ05WSFNVRUZqQVVCZ2dyQmdFRkJRY0RBUVlJS3dZQkJRVUgKQXdJd0RBWURWUjBUQVFIL0JBSXdBREFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBQ2NNS2IreVNOeG9wYmRGNwpqcTBTN1EvQmFUbWhzWnVUWDRPL2V4ZllkZFpwK0didDc2VEVqYWdmUmQvbXFFazlheFAxWDVzWkRhQ283blkzCitDdnpMb1h2Y3NHdnVnRTJSWStsSjdBbTdLS0lTdGVGTFdsRnBDTXJBWXF6TG1Wb3NyeDlWM0ZTbWY2REdWRk4KSmVVVFBnYTFrNTltMUNFSjZDYTAzM2hEYmp6aWsxd0xtR3pLVmRDR29HT1N0Vm1tbTZFWWMza1hheGVXTUtuMQpoRDhmREV5R3p1Z2hhTkQyYjZGdnlha28yUVQ3dFd3L09yMXNhQWQ1S2N5Wk4xdDVtUHI1RHh0SmFKNHN2dzh4Ck01R0MySFVUM2pzK3dQdDVSUlpSRXd2MkNCUVNHUWtxcDFrWTNLV1pXTDB2WEQvbjJqYlZGUUtacHQ2dDZKT3EKaVQ2bnlRPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=:LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBOFVwam9EUlhzbGZkZ3pVWkEzK1VrWDJQVjhSUkdKVUw0TWc2Mm9GR1FhcWtvVDJYCllCTDZSR1ZEWnhJWkZXVzhQVjN0YzBDN2tKMzZ3NFE3UE1velR0V09jWGlyN0J3VkFIU3RaR2o5ajF0VjBlOGEKZ3g3eDFtZ3NFR0Zodmo1WkFsdC9PWE5KZWlsaEJ2U0dCRjFuVGxBcFJaenI0YXdPM1V1WGNzVkcwcWdjQTBSLwpYTmx3bGVQTEZpbjYycjNmOWZQWm13K2ZqeFRtTC9vQTBLNmFVbTd0d3ZuRFg1ZjhPUGJmd2oyVS9KYWxVbWdECjhiUGFpWnZpUjFCRjh1NUdiNVhXSmFPK3RzRmo4N0FZWXJIZVRaNUlYNVlaRHlTSEpHZDhzbXNweG5mVzN2dTYKSklhVXlyaU4wTVljdGRKTlpOYkZxMjIwbUVRcDNlWXhsTzdMd3dJREFRQUJBb0lCQVFESEVJd291NFl1U0hjagpyRWE2c0NLdDlWeXhGL0dmeWpkR2QycTJvamlJTEhRdDRsWmttTU9JY2RLdDBpeUhqcXRDSlora21oOGtMSEdaCnBCb0xDUFpUYjdSWXdTbDFYYVdsL3B5ZVhrL3lXWFB3QXNkb3JicnZISHBkK1RsZWJxbVlYRXdWNVpzVkFkWmUKbXBXR1BGamlMeGdkcWx5Z2pnYWxZNXZLd0I2eDR3MHZTczdaUVBoQkFqTSs3a2Q3ZUpBRUJHZUxRL1lRTEIvVgo1Qmtrci9mT2d6K1A3V2t5QUovRk1iRzY4K0E3Z1FHeTJvWGdnbHVsOXg5YjlkcXJDSm5NVHFkNENvbU5qTmFjCkpValhjMEVGeFJqTjdOR0djenhXREVnc1dicGMxeFpYK20zcEQrQnRDd3BSeXJ4Q3NDWGwrTllnT01Jb1FZaWIKaUxQZzhsM0JBb0dCQVBQa3BMdTM0U1NhQVJNOE1YMWxCckdMa0k2cjlLZW5MaVZNYU55bG9CMWtyM29DaEJueApiek5kMkdxaXNBbEJFb3JVWEdkOVBadVNqYmRGMnVxTGgvdVMvdlBBMUpmR3drakZERmNyTVlXeDExWGVMcy9RCmYrdjF2dmQ5RUp5b0w2ekIxQkhaelZuenFIQ2w4OWlvNEY3Wkl3MmlKQ0NjWlJiN2JlOUdzbkdiQW9HQkFQMUUKckR3bldQNG5XN00wdlJYcFVkY3Q1UE96dHZIVFpoNTFJaC93NklOQzladXl3eFJoMi9XTEs0ZlZBRDYxeFloTgpVR0FvU2FUQzRrZWdoYlUvTWY5aWtBS3c3MWRPdk5HSzBSdjROTlZwbEJWbFhhcnF5OEpTeGJIcnNNU25rSTI4CjhNZ21YdlYvNkYwZTUxWFl1bHlYUXNqVWpCbVRXUTUzZnRWcXRhVDVBb0dCQU9vRkpod0pJRHNpbW8xK1lHNVYKbGNxZWhDS2gxS3RadXVtSEc4YzhGUnFmRmRFWXdQQ3p2V09vVkpSZGJsUXk0RHZkOEp4TWkrVFBCclFvanhvbQpzR0F3ZC9vanVObTVtWXFCcUltcnBHVUljL3FzcW5ZMU5jbVBqNkdobTJMMTdtanh3eTh0c2VEeDcxbkhvdWJ0CmcveitsS2ZzUUlZYUN0VzJnNUhvWUNpcEFvR0JBTTNtcHAvQTNYakNScXJLbFc3YTRNNHZZWk0rNTl4eUlQTmkKQnZ3d3Z0YjMrUFU3djUweWNjQ09CRFhKMVFrbWZoRHh5Z1ppdW54WWM5NEhncXgzVkE1cjh1ZzlNRmVxaTVkUApZL0Y1T0hySCtydnFUTnhIUnFBVTZ1UmEyTHNILzEwNzNnVGFMUmtwZzU4eElLR0tNUGhWZ05ZRTltRlVpWEpaCmM2UE52UjhCQW9HQU0zNTlZSUNUalNNYlYvSjJUbUx1Zjh2ejh0WVJDeXlla0h6Z2tTcmpqWUZWNmoxUTllWGQKa1ZFWitaYW1xYkZvUmxqK1lHS21XQ0w0OXJTWkYxNmVYL1V3OW5haEErcmtYL21aaERkOXIrVE90enpycUVYSQpUQVlld3R5ZGEzUzkyaytOWU8zZE5kbWZLQVdYV2JzdjZUKzBBTXR6VTFkMlNaaDlsTU5aVkUwPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=" e := echo.New() @@ -41,7 +41,7 @@ func TestFetchCertAuth(t *testing.T) { t.Fail() } - testKubeCertAuth := &KubeCertAuth{} + testKubeCertAuth := &KubeCertificate{} err = json.NewDecoder(strings.NewReader(jsonString)).Decode(testKubeCertAuth) if err != nil { t.Fail() diff --git a/src/jetstream/plugins/kubernetes/auth/gke.go b/src/jetstream/plugins/kubernetes/auth/gke.go index 3bdeced3b4..9a1193d4ad 100644 --- a/src/jetstream/plugins/kubernetes/auth/gke.go +++ b/src/jetstream/plugins/kubernetes/auth/gke.go @@ -37,15 +37,16 @@ type GKEKubeAuth struct { portalProxy interfaces.PortalProxy } -const AuthConnectTypeGKE = "gke-auth" +const authConnectTypeGKE = "gke-auth" // InitGKEKubeAuth creates a GKEKubeAuth func InitGKEKubeAuth(portalProxy interfaces.PortalProxy) KubeAuthProvider { return &GKEKubeAuth{portalProxy: portalProxy} } +// GetName returns the provider name func (c *GKEKubeAuth) GetName() string { - return AuthConnectTypeGKE + return authConnectTypeGKE } func (c *GKEKubeAuth) AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { @@ -107,7 +108,7 @@ func (c *GKEKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Conte // Create a new token record - we need to store the client ID and secret as well, so cheekily use the refresh token for this tokenRecord := c.portalProxy.InitEndpointTokenRecord(0, oauthToken.AccessToken, string(tokenInfo), false) - tokenRecord.AuthType = AuthConnectTypeGKE + tokenRecord.AuthType = authConnectTypeGKE return &tokenRecord, &cnsiRecord, nil } diff --git a/src/jetstream/plugins/kubernetes/auth/oidc.go b/src/jetstream/plugins/kubernetes/auth/oidc.go index 80ce00dda0..199deee5ca 100644 --- a/src/jetstream/plugins/kubernetes/auth/oidc.go +++ b/src/jetstream/plugins/kubernetes/auth/oidc.go @@ -4,17 +4,17 @@ import ( "encoding/json" "errors" "fmt" - "net/http" "io/ioutil" + "net/http" "time" - "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/config" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" - log "github.com/sirupsen/logrus" + "github.com/SermoDigital/jose/jws" "github.com/labstack/echo" + log "github.com/sirupsen/logrus" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - "github.com/SermoDigital/jose/jws" ) type KubeConfigAuthProviderOIDC struct { @@ -26,7 +26,7 @@ type KubeConfigAuthProviderOIDC struct { Expiry time.Time } -const AuthConnectTypeOIDC = "OIDC" +const authConnectTypeOIDC = "OIDC" // OIDCKubeAuth type OIDCKubeAuth struct { @@ -38,8 +38,9 @@ func InitOIDCKubeAuth(portalProxy interfaces.PortalProxy) *OIDCKubeAuth { return &OIDCKubeAuth{portalProxy: portalProxy} } +// GetName returns the provider name func (c *OIDCKubeAuth) GetName() string { - return AuthConnectTypeOIDC + return authConnectTypeOIDC } func (c *OIDCKubeAuth) AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { diff --git a/src/jetstream/plugins/kubernetes/auth/types.go b/src/jetstream/plugins/kubernetes/auth/types.go index 127ac705b1..9a32914bc9 100644 --- a/src/jetstream/plugins/kubernetes/auth/types.go +++ b/src/jetstream/plugins/kubernetes/auth/types.go @@ -21,9 +21,6 @@ type KubeAuthProvider interface { GetUserFromToken(cnsiGUID string, tokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) } - -// ------------------------------- - // KubeCertificate represents certificate infor for Kube Authentication type KubeCertificate struct { Certificate string `json:"cert"` @@ -47,4 +44,4 @@ func (k *KubeCertificate) GetCerticate() (tls.Certificate, error) { return tls.Certificate{}, err } return cert, nil -} \ No newline at end of file +} diff --git a/src/jetstream/plugins/kubernetes/auth_providers.go b/src/jetstream/plugins/kubernetes/auth_providers.go index c078f0f61a..dcd73d2ca3 100644 --- a/src/jetstream/plugins/kubernetes/auth_providers.go +++ b/src/jetstream/plugins/kubernetes/auth_providers.go @@ -7,8 +7,6 @@ import ( "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" ) -// Interface for Kubernetes Authentication Providers - var kubeAuthProviders map[string]auth.KubeAuthProvider // AddAuthProvider adds a Kubernetes auth provider diff --git a/src/jetstream/plugins/kubernetes/endpoint_config.go b/src/jetstream/plugins/kubernetes/endpoint_config.go index f047d86898..22f3521f10 100644 --- a/src/jetstream/plugins/kubernetes/endpoint_config.go +++ b/src/jetstream/plugins/kubernetes/endpoint_config.go @@ -16,8 +16,6 @@ import ( func (c *KubernetesSpecification) GetConfigForEndpoint(masterURL string, token interfaces.TokenRecord) (*restclient.Config, error) { return clientcmd.BuildConfigFromKubeconfigGetter(masterURL, func() (*clientcmdapi.Config, error) { - log.Info("GetConfigForEndpoint") - name := "cluster-0" // Create a config @@ -50,8 +48,6 @@ func (c *KubernetesSpecification) GetConfigForEndpoint(masterURL string, token i func (c *KubernetesSpecification) addAuthInfoForEndpoint(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { log.Debug("addAuthInfoForEndpoint") - log.Warn(tokenRec.AuthType) - var authProvider = c.GetAuthProvider(tokenRec.AuthType) if authProvider == nil { return errors.New("Unsupported auth type") diff --git a/src/jetstream/plugins/kubernetes/go.mod b/src/jetstream/plugins/kubernetes/go.mod index b9056ff45a..5393fae8ef 100644 --- a/src/jetstream/plugins/kubernetes/go.mod +++ b/src/jetstream/plugins/kubernetes/go.mod @@ -18,7 +18,6 @@ require ( github.com/evanphx/json-patch v4.1.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/ghodss/yaml v1.0.0 // indirect - github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 // indirect github.com/go-openapi/spec v0.18.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.2.1 // indirect @@ -31,6 +30,7 @@ require ( github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/hashicorp/golang-lru v0.5.0 // indirect github.com/helm/monocular v1.4.0 + github.com/helm/monocular/chartsvc v0.0.0-00010101000000-000000000000 github.com/heptio/authenticator v0.3.0 // indirect github.com/huandu/xstrings v1.2.0 // indirect github.com/imdario/mergo v0.3.7 // indirect @@ -45,14 +45,12 @@ require ( github.com/opencontainers/go-digest v1.0.0-rc1 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.8.1 // indirect - github.com/prometheus/client_golang v0.9.2 // indirect github.com/russross/blackfriday v2.0.0+incompatible // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/sirupsen/logrus v1.3.0 github.com/spf13/cobra v0.0.3 // indirect github.com/spf13/pflag v1.0.3 // indirect github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245 // indirect - github.com/unrolled/render v1.0.0 // indirect golang.org/x/net v0.0.0-20190225153610-fe579d43d832 // indirect golang.org/x/oauth2 v0.0.0-20190226191147-529b322ea346 // indirect golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect @@ -78,6 +76,7 @@ require ( replace ( github.com/SermoDigital/jose => github.com/SermoDigital/jose v0.9.2-0.20180104203859-803625baeddc github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces => ../../repository/interfaces + github.com/helm/monocular/chartsvc => ../monocular/chartsvc github.com/kubernetes-sigs/aws-iam-authenticator => github.com/kubernetes-sigs/aws-iam-authenticator v0.3.1-0.20190111160901-390d9087a4bc github.com/russross/blackfriday v2.0.0+incompatible => github.com/russross/blackfriday v1.5.2 github.com/sergi/go-diff => github.com/sergi/go-diff v1.0.0 diff --git a/src/jetstream/plugins/kubernetes/go.sum b/src/jetstream/plugins/kubernetes/go.sum index 836c47b4bc..69a959d32c 100644 --- a/src/jetstream/plugins/kubernetes/go.sum +++ b/src/jetstream/plugins/kubernetes/go.sum @@ -33,6 +33,7 @@ github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrP github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo= @@ -75,6 +76,8 @@ github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhp github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= +github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU= @@ -96,6 +99,8 @@ github.com/helm/monocular v1.6.0 h1:zE8OggduPEtnF2x1XgrQnMwnKFUGCvk/RNs0NX8hgjg= github.com/helm/monocular v1.7.0 h1:SW2xr6KoVJtZT6ZAtON8jJVLqcRE3C8pBb9kyo8Icxw= github.com/heptio/authenticator v0.3.0 h1:Xh6XWkLZ+CksGuky+vsr77mHnI9C4L3nwj+xuZu28J0= github.com/heptio/authenticator v0.3.0/go.mod h1:Q86X8hc61JXhE5XxYLKmrSRWby/Oe8IIYZIBgmGVkTA= +github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40 h1:GT4RsKmHh1uZyhmTkWJTDALRjSHYQp6FRKrotf0zhAs= +github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40/go.mod h1:NtmN9h8vrTveVQRLHcX2HQ5wIPBDCsZ351TGbZWgg38= github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= @@ -154,9 +159,9 @@ github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nL github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday v2.0.0/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v2.0.0 h1:L7Oc72h7rDqGkbUorN/ncJ4N/y220/YRezHvBoKLOFA= +github.com/russross/blackfriday v2.0.0/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME= @@ -172,12 +177,15 @@ github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245 h1:DNVk+NIkGS github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245/go.mod h1:O1c8HleITsZqzNZDjSNzirUGsMT0oGu9LhHKoJrqO+A= github.com/unrolled/render v1.0.0 h1:XYtvhA3UkpB7PqkvhUFYmpKD55OudoIeygcfus4vcd4= github.com/unrolled/render v1.0.0/go.mod h1:tu82oB5W2ykJRVioYsB+IQKcft7ryBr7w12qMBUPyXg= +github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 h1:gKMu1Bf6QINDnvyZuTaACm9ofY+PRh+5vFz4oxBZeF8= github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4/go.mod h1:50wTf68f99/Zt14pr046Tgt3Lp2vLyFZKzbFXTOabXw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/src/jetstream/plugins/kubernetes/helm_client.go b/src/jetstream/plugins/kubernetes/helm_client.go index 2bbb9e8148..7c04053da1 100644 --- a/src/jetstream/plugins/kubernetes/helm_client.go +++ b/src/jetstream/plugins/kubernetes/helm_client.go @@ -7,31 +7,15 @@ import ( log "github.com/sirupsen/logrus" "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" "k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm/portforwarder" "k8s.io/helm/pkg/kube" // Import the OIDC auth plugin _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" - - "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" ) -type KubeProxyError struct { - Name string -} - -type KubeProxyFunc func(*interfaces.ConnectedEndpoint, chan KubeProxyResponse) - -type KubeProxyResponse struct { - Endpoint string - Result interface{} - Error *KubeProxyError -} - -type KubeProxyResponses map[string]interface{} - +// GetHelmClient gets a client that can be used to talk to Tiller func (c *KubernetesSpecification) GetHelmClient(endpointGUID, userID string) (helm.Interface, *kubernetes.Clientset, *kube.Tunnel, error) { // Need to get a config object for the target endpoint var p = c.portalProxy @@ -64,73 +48,13 @@ func (c *KubernetesSpecification) GetHelmClient(endpointGUID, userID string) (he return nil, nil, nil, err } - log.Warnf("Tiller tunnel is using Port: %d", tillerTunnel.Local) - - // TODO: Concurrency? + log.Debugf("Tiller tunnel is using Port: %d", tillerTunnel.Local) tillerHost := fmt.Sprintf("127.0.0.1:%d", tillerTunnel.Local) - client := newClient(tillerHost) return client, kubeClient, tillerTunnel, nil } -func (c *KubernetesSpecification) ProxyKubernetesAPI(userID string, f KubeProxyFunc) (KubeProxyResponses, error) { - - var p = c.portalProxy - k8sList := make([]*interfaces.ConnectedEndpoint, 0) - eps, err := p.ListEndpointsByUser(userID) - if err != nil { - return nil, fmt.Errorf("Could not get endpints Client for endpoint: %v+", err) - } - - for _, endpoint := range eps { - if endpoint.CNSIType == "k8s" { - k8sList = append(k8sList, endpoint) - } - } - - // Check that we actually have some - // TODO - done := make(chan KubeProxyResponse) - for _, endpoint := range k8sList { - go f(endpoint, done) - } - - responses := make(KubeProxyResponses) - for range k8sList { - res := <-done - if res.Error == nil { - responses[res.Endpoint] = res.Result - } else { - responses[res.Endpoint] = res.Error - } - } - - return responses, nil -} - -// configForContext creates a Kubernetes REST client configuration for a given kubeconfig context. -func configForContext(context string, kubeconfig string) (*rest.Config, error) { - config, err := kube.GetConfig(context, kubeconfig).ClientConfig() - if err != nil { - return nil, fmt.Errorf("could not get Kubernetes config for context %q: %s", context, err) - } - return config, nil -} - -// getKubeClient creates a Kubernetes config and client for a given kubeconfig context. -func getKubeClient(context string, kubeconfig string) (*rest.Config, kubernetes.Interface, error) { - config, err := configForContext(context, kubeconfig) - if err != nil { - return nil, nil, err - } - client, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, nil, fmt.Errorf("could not get Kubernetes client: %s", err) - } - return config, client, nil -} - func newClient(tillerHost string) helm.Interface { options := []helm.Option{helm.Host(tillerHost), helm.ConnectTimeout(20)} return helm.NewClient(options...) diff --git a/src/jetstream/plugins/kubernetes/helm_versions.go b/src/jetstream/plugins/kubernetes/helm_versions.go index c3bc1ae91f..1a21f5dbae 100644 --- a/src/jetstream/plugins/kubernetes/helm_versions.go +++ b/src/jetstream/plugins/kubernetes/helm_versions.go @@ -1,6 +1,8 @@ package kubernetes import ( + "net/http" + "github.com/labstack/echo" log "github.com/sirupsen/logrus" @@ -23,7 +25,13 @@ func (c *KubernetesSpecification) GetHelmVersions(ec echo.Context) error { userID := ec.Get("user_id").(string) resp, err := c.ProxyKubernetesAPI(userID, c.fetchHelmVersion) - log.Warn(err) + if err != nil { + return interfaces.NewHTTPShadowError( + http.StatusInternalServerError, + "Error fetching Helm Tiller Versions", + "Error fetching Helm Tiller Versions: %v", err, + ) + } return ec.JSON(200, resp) } diff --git a/src/jetstream/plugins/kubernetes/install_release.go b/src/jetstream/plugins/kubernetes/install_release.go index 1625e5600e..b6fc1ff755 100644 --- a/src/jetstream/plugins/kubernetes/install_release.go +++ b/src/jetstream/plugins/kubernetes/install_release.go @@ -51,24 +51,19 @@ func (c *KubernetesSpecification) InstallRelease(ec echo.Context) error { log.Info("Installing release") log.Info(chartID) - downloadURL, err := c.GetChart(chartID, params.Chart.Version) + downloadURL, err := c.getChart(chartID, params.Chart.Version) if err != nil { return fmt.Errorf("Could not get the Download URL") } log.Debugf("Chart Download URL: %s", downloadURL) - // Get IO reader for the Chart - // Should we ignore SSL certs? // TODO: Look up Helm Repository endpoiint and use the value from that http := c.portalProxy.GetHttpClient(true) resp, err := http.Get(downloadURL) - // // Check StatusCode is 200 - // // Body is a io.ReadCloser - if resp.StatusCode != 200 { return fmt.Errorf("Could not download Chart Archive: %s", resp.Status) } @@ -79,7 +74,7 @@ func (c *KubernetesSpecification) InstallRelease(ec echo.Context) error { return fmt.Errorf("Could not load chart from archive: %v+", err) } - log.Debug("Loaded chart") + log.Debug("Loaded helm chart") endpointGUID := params.Endpoint userGUID := ec.Get("user_id").(string) @@ -94,7 +89,7 @@ func (c *KubernetesSpecification) InstallRelease(ec echo.Context) error { if _, err := chartutil.LoadRequirements(chart); err == nil { log.Debug("Chart requirements loaded") } else if err != chartutil.ErrRequirementsNotFound { - log.Error("Can not load requirements") + log.Error("Can not load requirements for helm chart") } else { log.Error(err) } @@ -120,7 +115,7 @@ func (c *KubernetesSpecification) InstallRelease(ec echo.Context) error { return ec.JSON(200, installResponse) } -func (c *KubernetesSpecification) GetChart(chartID, version string) (string, error) { +func (c *KubernetesSpecification) getChart(chartID, version string) (string, error) { helm := c.portalProxy.GetPlugin("monocular") if helm == nil { @@ -167,7 +162,7 @@ func (c *KubernetesSpecification) DeleteRelease(ec echo.Context) error { deleteResponse, err := client.DeleteRelease(releaseName, helm.DeletePurge(true)) if err != nil { return fmt.Errorf("Could not delete Helm Release: %v+", err) - } + return ec.JSON(200, deleteResponse) } diff --git a/src/jetstream/plugins/kubernetes/kube_dashboard.go b/src/jetstream/plugins/kubernetes/kube_dashboard.go index f2d37a4b65..76e9dc4c69 100644 --- a/src/jetstream/plugins/kubernetes/kube_dashboard.go +++ b/src/jetstream/plugins/kubernetes/kube_dashboard.go @@ -149,7 +149,7 @@ func (k *KubernetesSpecification) kubeDashboardProxy(c echo.Context) error { log.Info("Making request") req := c.Request() w := c.Response().Writer - log.Info("%v+", req) + log.Infof("%v+", req) // if h.tryUpgrade(w, req) { // return @@ -220,7 +220,7 @@ func (k *KubernetesSpecification) kubeDashboardProxy(c echo.Context) error { log.Debugf("%v+", response.Header) response.Header.Del("X-FRAME-OPTIONS") response.Header.Set("X-FRAME-OPTIONS", "sameorigin") - log.Debug("%v+", response) + log.Debugf("%v+", response) return nil } diff --git a/src/jetstream/plugins/kubernetes/list_releases.go b/src/jetstream/plugins/kubernetes/list_releases.go index ef0f45e250..c920c888a5 100644 --- a/src/jetstream/plugins/kubernetes/list_releases.go +++ b/src/jetstream/plugins/kubernetes/list_releases.go @@ -52,30 +52,30 @@ func (c *KubernetesSpecification) listReleases(ep *interfaces.ConnectedEndpoint, Result: nil, } - log.Warnf("listReleases: START: %s", ep.GUID) + log.Debugf("listReleases: START: %s", ep.GUID) client, _, tiller, err := c.GetHelmClient(ep.GUID, ep.Account) if err != nil { - log.Warnf("listReleases: CLIENT_ERROR: %s", ep.GUID) + log.Debugf("listReleases: CLIENT_ERROR: %s", ep.GUID) done <- response return } defer tiller.Close() - log.Warnf("listReleases: REQUEST: %s", ep.GUID) + log.Debugf("listReleases: REQUEST: %s", ep.GUID) res, err := client.ListReleases( helm.ReleaseListStatuses(nil), ) if err != nil { - log.Warnf("listReleases: ERROR: %s", ep.GUID) + log.Debugf("listReleases: ERROR: %s", ep.GUID) log.Error(err) done <- response return } - log.Warnf("listReleases: OK: %s", ep.GUID) + log.Debugf("listReleases: OK: %s", ep.GUID) response.Result = res done <- response } diff --git a/src/jetstream/plugins/kubernetes/main.go b/src/jetstream/plugins/kubernetes/main.go index 26a36b433d..736ddfa35d 100644 --- a/src/jetstream/plugins/kubernetes/main.go +++ b/src/jetstream/plugins/kubernetes/main.go @@ -12,26 +12,22 @@ import ( "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/auth" ) +// KubernetesSpecification is the endpoint that adds Kubernetes support to the backend type KubernetesSpecification struct { portalProxy interfaces.PortalProxy endpointType string } -// KubeDashboardPluginConfigSetting is config value send back to the client to indicate if the kube dashboard can be navigated to -const KubeDashboardPluginConfigSetting = "kubeDashboardEnabled" - const ( - EndpointType = "k8s" - CLIENT_ID_KEY = "K8S_CLIENT" - AuthConnectTypeKubeConfig = "KubeConfig" - AuthConnectTypeKubeConfigAz = "kubeconfig-az" - AuthConnectTypeAWSIAM = "aws-iam" - AuthConnectTypeCertAuth = "kube-cert-auth" - AuthConnectTypeGKE = "gke-auth" + kubeEndpointType = "k8s" + defaultKubeClientID = "K8S_CLIENT" + + // kubeDashboardPluginConfigSetting is config value send back to the client to indicate if the kube dashboard can be navigated to + kubeDashboardPluginConfigSetting = "kubeDashboardEnabled" ) func Init(portalProxy interfaces.PortalProxy) (interfaces.StratosPlugin, error) { - return &KubernetesSpecification{portalProxy: portalProxy, endpointType: EndpointType}, nil + return &KubernetesSpecification{portalProxy: portalProxy, endpointType: kubeEndpointType}, nil } func (c *KubernetesSpecification) GetEndpointPlugin() (interfaces.EndpointPlugin, error) { @@ -47,11 +43,11 @@ func (c *KubernetesSpecification) GetMiddlewarePlugin() (interfaces.MiddlewarePl } func (c *KubernetesSpecification) GetType() string { - return EndpointType + return kubeEndpointType } func (c *KubernetesSpecification) GetClientId() string { - return c.portalProxy.Env().String(CLIENT_ID_KEY, "k8s") + return c.portalProxy.Env().String(defaultKubeClientID, "k8s") } func (c *KubernetesSpecification) Register(echoContext echo.Context) error { @@ -72,7 +68,7 @@ func (c *KubernetesSpecification) Validate(userGUID string, cnsiRecord interface return nil } -func (c *KubernetesSpecification) Connect(ec echo.Context, cnsiRecord interfaces.CNSIRecord, userId string) (*interfaces.TokenRecord, bool, error) { +func (c *KubernetesSpecification) Connect(ec echo.Context, cnsiRecord interfaces.CNSIRecord, userID string) (*interfaces.TokenRecord, bool, error) { log.Debug("Kubernetes Connect...") connectType := ec.FormValue("connect_type") @@ -100,8 +96,8 @@ func (c *KubernetesSpecification) Init() error { c.AddAuthProvider(auth.InitAzureKubeAuth(c.portalProxy)) c.AddAuthProvider(auth.InitOIDCKubeAuth(c.portalProxy)) c.AddAuthProvider(auth.InitKubeConfigAuth(c.portalProxy)) - - c.portalProxy.GetConfig().PluginConfig[KubeDashboardPluginConfigSetting] = "false" + + c.portalProxy.GetConfig().PluginConfig[kubeDashboardPluginConfigSetting] = "false" return nil } @@ -129,7 +125,7 @@ func (c *KubernetesSpecification) Info(apiEndpoint string, skipSSLValidation boo var v2InfoResponse interfaces.V2Info var newCNSI interfaces.CNSIRecord - newCNSI.CNSIType = EndpointType + newCNSI.CNSIType = kubeEndpointType _, err := url.Parse(apiEndpoint) if err != nil { diff --git a/src/jetstream/plugins/monocular/20190307115300_ChartStore.go b/src/jetstream/plugins/monocular/20190307115300_ChartStore.go index 9eed7d37d7..409fb3691b 100644 --- a/src/jetstream/plugins/monocular/20190307115300_ChartStore.go +++ b/src/jetstream/plugins/monocular/20190307115300_ChartStore.go @@ -2,6 +2,7 @@ package monocular import ( "database/sql" + "strings" "bitbucket.org/liamstask/goose/lib/goose" @@ -31,6 +32,11 @@ func init() { return err } + binaryDataType := "BYTEA" + if strings.Contains(conf.Driver.Name, "mysql") { + binaryDataType = "BLOB" + } + createChartFilesTable := "CREATE TABLE IF NOT EXISTS chart_files (" createChartFilesTable += "id VARCHAR(255) NOT NULL," createChartFilesTable += "filename VARCHAR(64) NOT NULL," @@ -38,7 +44,7 @@ func init() { createChartFilesTable += "name VARCHAR(255) NOT NULL," createChartFilesTable += "repo_name VARCHAR(255) NOT NULL," createChartFilesTable += "digest VARCHAR(255) NOT NULL," - createChartFilesTable += "content BLOB," + createChartFilesTable += "content " + binaryDataType + "," createChartFilesTable += "PRIMARY KEY (id, filename) );" _, err = txn.Exec(createChartFilesTable) diff --git a/src/jetstream/plugins/monocular/endpoint.go b/src/jetstream/plugins/monocular/endpoint.go index 66ef809295..8bf5ca906a 100644 --- a/src/jetstream/plugins/monocular/endpoint.go +++ b/src/jetstream/plugins/monocular/endpoint.go @@ -10,7 +10,7 @@ import ( ) func (m *Monocular) GetType() string { - return EndpointType + return helmEndpointType } func (m *Monocular) GetClientId() string { @@ -37,7 +37,7 @@ func (m *Monocular) Info(apiEndpoint string, skipSSLValidation bool) (interfaces var v2InfoResponse interfaces.V2Info var newCNSI interfaces.CNSIRecord - newCNSI.CNSIType = EndpointType + newCNSI.CNSIType = helmEndpointType _, err := url.Parse(apiEndpoint) if err != nil { diff --git a/src/jetstream/plugins/monocular/main.go b/src/jetstream/plugins/monocular/main.go index 260c80ff9d..2336033fa0 100644 --- a/src/jetstream/plugins/monocular/main.go +++ b/src/jetstream/plugins/monocular/main.go @@ -15,7 +15,7 @@ import ( ) const ( - EndpointType = "helm" + helmEndpointType = "helm" ) const prefix = "/pp/v1/chartsvc/" @@ -70,7 +70,7 @@ func (m *Monocular) syncOnStartup() { helmRepos := make([]string, 0) for _, ep := range endpoints { - if ep.CNSIType == "helm" { + if ep.CNSIType == helmEndpointType { helmRepos = append(helmRepos, ep.Name) // Is this an endpoint that we don't have charts for ? @@ -88,7 +88,7 @@ func (m *Monocular) syncOnStartup() { endpoint := &interfaces.CNSIRecord{ GUID: repo, Name: repo, - CNSIType: "helm", + CNSIType: helmEndpointType, } m.Sync(interfaces.EndpointUnregisterAction, endpoint) } @@ -155,7 +155,7 @@ func (m *Monocular) ConfigureSQL() error { } func (m *Monocular) OnEndpointNotification(action interfaces.EndpointAction, endpoint *interfaces.CNSIRecord) { - if endpoint.CNSIType == EndpointType { + if endpoint.CNSIType == helmEndpointType { m.Sync(action, endpoint) } } diff --git a/src/jetstream/plugins/monocular/repository.go b/src/jetstream/plugins/monocular/repository.go index c1d3424fba..3bb93d5f04 100644 --- a/src/jetstream/plugins/monocular/repository.go +++ b/src/jetstream/plugins/monocular/repository.go @@ -26,7 +26,7 @@ func (m *Monocular) ListRepos(c echo.Context) error { repos := make([]HelmRepoInfo, 0) for _, ep := range endpoints { - if ep.CNSIType == EndpointType { + if ep.CNSIType == helmEndpointType { // Helm endpoint repo := HelmRepoInfo{ ID: ep.Name, diff --git a/src/jetstream/plugins/monocular/sync.go b/src/jetstream/plugins/monocular/sync.go index 29f93525f5..9513c243e4 100644 --- a/src/jetstream/plugins/monocular/sync.go +++ b/src/jetstream/plugins/monocular/sync.go @@ -2,6 +2,7 @@ package monocular import ( "encoding/json" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/helm/monocular/chartrepo" log "github.com/sirupsen/logrus" @@ -20,10 +21,12 @@ type SyncMetadata struct { // Sync Chanel var syncChan = make(chan SyncJob, 100) +// InitSync starts the go routine that will sync repositories in the background func (m *Monocular) InitSync() { go m.processSyncRequests() } +// Sync shceudles a sync action for the given endpoint func (m *Monocular) Sync(action interfaces.EndpointAction, endpoint *interfaces.CNSIRecord) { job := SyncJob{ @@ -31,19 +34,17 @@ func (m *Monocular) Sync(action interfaces.EndpointAction, endpoint *interfaces. Endpoint: endpoint, } - log.Warn("Scheduling Sync job") syncChan <- job } func (m *Monocular) processSyncRequests() { log.Info("Helm Repository Sync init") for job := range syncChan { - log.Info("Processing Job") - log.Info(job.Endpoint.Name) + log.Debugf("Processing Helm Repository Sync Job: %s", job.Endpoint.Name) // Could be delete or sync if job.Action == 0 { - log.Info("Syncing new repository") + log.Debug("Syncing new repository") metadata := SyncMetadata{ Status: "Synchronizing", Busy: true, @@ -66,7 +67,7 @@ func (m *Monocular) processSyncRequests() { } } - log.Info("processSyncRequests finished") + log.Debug("processSyncRequests finished") } func marshalSyncMetadata(metadata SyncMetadata) string { From 9c9722d7efca8592cfaa5df1a758488b78c92131 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 17 May 2019 15:29:37 +0100 Subject: [PATCH 041/648] Fix helm sql datastore to return values.yaml --- .../plugins/monocular/chartsvc/datastore.go | 1 + .../plugins/monocular/chartsvc/handler.go | 20 +++++++++++-------- .../plugins/monocular/chartsvc/mongodb.go | 6 ++++++ .../plugins/monocular/sql_datastore.go | 11 ++++++++++ 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/jetstream/plugins/monocular/chartsvc/datastore.go b/src/jetstream/plugins/monocular/chartsvc/datastore.go index 98e0306e71..63cb377fe5 100644 --- a/src/jetstream/plugins/monocular/chartsvc/datastore.go +++ b/src/jetstream/plugins/monocular/chartsvc/datastore.go @@ -12,4 +12,5 @@ type ChartSvcDatastore interface { GetChartVersion(chartID, version string) (models.Chart, error) GetChartIcon(chartID string) ([]byte, error) GetChartVersionReadme(chartID, version string) ([]byte, error) + GetChartVersionValuesYaml(chartID, version string) ([]byte, error) } diff --git a/src/jetstream/plugins/monocular/chartsvc/handler.go b/src/jetstream/plugins/monocular/chartsvc/handler.go index 96ce07bf2e..402bd12588 100644 --- a/src/jetstream/plugins/monocular/chartsvc/handler.go +++ b/src/jetstream/plugins/monocular/chartsvc/handler.go @@ -173,17 +173,21 @@ func getChartVersionReadme(w http.ResponseWriter, req *http.Request, params Para // getChartVersionValues returns the values.yaml for a given chart func getChartVersionValues(w http.ResponseWriter, req *http.Request, params Params) { - db, closer := dbSession.DB() - defer closer() - var files models.ChartFiles - fileID := fmt.Sprintf("%s/%s-%s", params["repo"], params["chartName"], params["version"]) - if err := db.C(filesCollection).FindId(fileID).One(&files); err != nil { - log.WithError(err).Errorf("could not find values.yaml with id %s", fileID) + chartID := fmt.Sprintf("%s/%s", params["repo"], params["chartName"]) + version := params["version"] + fileID := fmt.Sprintf("%s-%s", chartID, version) + values, err := dataStore.GetChartVersionValuesYaml(chartID, version) + if err != nil { + log.WithError(err).Errorf("could not find files with id %s", fileID) http.NotFound(w, req) return } - - w.Write([]byte(files.Values)) + if len(values) == 0 { + log.Errorf("could not find a values.yaml for id %s", fileID) + http.NotFound(w, req) + return + } + w.Write(values) } // listChartsWithFilters returns the list of repos that contains the given chart and the latest version found diff --git a/src/jetstream/plugins/monocular/chartsvc/mongodb.go b/src/jetstream/plugins/monocular/chartsvc/mongodb.go index bcb0aac1d9..b9b2f70388 100644 --- a/src/jetstream/plugins/monocular/chartsvc/mongodb.go +++ b/src/jetstream/plugins/monocular/chartsvc/mongodb.go @@ -2,7 +2,9 @@ package chartsvc import ( //"bytes" + "errors" "fmt" + "github.com/globalsign/mgo/bson" "github.com/helm/monocular/chartsvc/models" "github.com/kubeapps/common/datastore" @@ -86,3 +88,7 @@ func (m *MongoDBChartSvcDatastore) GetChartVersionReadme(chartID, version string readme := []byte(files.Readme) return readme, nil } + +func (m *MongoDBChartSvcDatastore) GetChartVersionValuesYaml(chartID, version string) ([]byte, error) { + return nil, errors.New("Not implemented") +} diff --git a/src/jetstream/plugins/monocular/sql_datastore.go b/src/jetstream/plugins/monocular/sql_datastore.go index ccb09dc339..9fdf161069 100644 --- a/src/jetstream/plugins/monocular/sql_datastore.go +++ b/src/jetstream/plugins/monocular/sql_datastore.go @@ -322,6 +322,17 @@ func (s *SQLDBCMonocularDatastore) GetChartVersionReadme(chartID, version string return content, nil } +func (s *SQLDBCMonocularDatastore) GetChartVersionValuesYaml(chartID, version string) ([]byte, error) { + var content []byte + fileID := fmt.Sprintf("%s-%s", chartID, version) + err := s.db.QueryRow(getChartFileByID, fileID, "values").Scan(&content) + if err != nil { + return nil, fmt.Errorf("Unable to scan chart file record: %v", err) + } + + return content, nil +} + // ListRepositories gets all repository names func (s *SQLDBCMonocularDatastore) ListRepositories() ([]string, error) { rows, err := s.db.Query(getRepositories) From 764bd8627a086e30922926963a869b385f43d4ad Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 17 May 2019 16:33:11 +0100 Subject: [PATCH 042/648] Minor bug fixes and improvements --- .../create-release.component.html | 14 +++-- .../create-release.component.scss | 28 ++++++++- .../create-release.component.ts | 63 +++++++++++++++++-- .../chart-details-readme.component.ts | 6 +- .../chart-details.component.html | 2 +- 5 files changed, 97 insertions(+), 16 deletions(-) diff --git a/custom-src/frontend/app/custom/helm/create-release/create-release.component.html b/custom-src/frontend/app/custom/helm/create-release/create-release.component.html index c4c3d50c9f..7171022700 100644 --- a/custom-src/frontend/app/custom/helm/create-release/create-release.component.html +++ b/custom-src/frontend/app/custom/helm/create-release/create-release.component.html @@ -15,7 +15,7 @@
    Specify name and namespace for the installation - + @@ -25,12 +25,14 @@ -
    -

    Enter YAML Value Overrides

    - + +
    +

    Enter YAML Value Overrides

    + +
    + Values - +
    diff --git a/custom-src/frontend/app/custom/helm/create-release/create-release.component.scss b/custom-src/frontend/app/custom/helm/create-release/create-release.component.scss index b1b37d0268..03ca214f4e 100644 --- a/custom-src/frontend/app/custom/helm/create-release/create-release.component.scss +++ b/custom-src/frontend/app/custom/helm/create-release/create-release.component.scss @@ -3,7 +3,18 @@ } .helm-create-release { - font-size: 14px; + &__heading { + align-items: center; + display: flex; + } + + &__title { + flex: 1; + font-size: 14px; + } + &__button { + height: 36px; + } } form { @@ -14,5 +25,20 @@ form { &__yaml { background-color: rgba(0, 0, 0, .1); font-family: 'Source Code Pro', monospace; + height: 400px; + } + + &_form { + max-width: 100%; + } + + &_form-field { + flex: 1; + height: 100%; + width: 100%; } } + +form.overrides_form { + max-width: 100%; +} \ No newline at end of file diff --git a/custom-src/frontend/app/custom/helm/create-release/create-release.component.ts b/custom-src/frontend/app/custom/helm/create-release/create-release.component.ts index d4c84f53d1..ba1999cdef 100644 --- a/custom-src/frontend/app/custom/helm/create-release/create-release.component.ts +++ b/custom-src/frontend/app/custom/helm/create-release/create-release.component.ts @@ -1,11 +1,11 @@ import { HttpClient } from '@angular/common/http'; -import { Component, ElementRef, ViewChild } from '@angular/core'; +import { Component, ElementRef, ViewChild, OnInit } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { MatTextareaAutosize } from '@angular/material'; import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; import { Observable, of, Subscription } from 'rxjs'; -import { delay, filter, map, pairwise, switchMap } from 'rxjs/operators'; +import { delay, filter, map, pairwise, switchMap, first, tap } from 'rxjs/operators'; import { AppState } from '../../../../../store/src/app-state'; import { selectUpdateInfo } from '../../../../../store/src/selectors/api.selectors'; @@ -14,13 +14,22 @@ import { StepOnNextFunction } from '../../../shared/components/stepper/step/step import { HelmInstall } from '../store/helm.actions'; import { helmReleaseSchemaKey } from '../store/helm.entities'; import { HELM_INSTALLING_KEY, HelmInstallValues } from '../store/helm.types'; +import { ConfirmationDialogConfig } from '../../../shared/components/confirmation-dialog.config'; +import { ConfirmationDialogService } from '../../../shared/components/confirmation-dialog.service'; @Component({ selector: 'app-create-release', templateUrl: './create-release.component.html', styleUrls: ['./create-release.component.scss'], }) -export class CreateReleaseComponent { +export class CreateReleaseComponent implements OnInit { + + // Confirmation dialog + overwriteValuesConfirmation = new ConfirmationDialogConfig( + 'Overwrite Values?', + 'Are you sure you want to replace your values with those from valuyes.yaml?', + 'Overwrite' + ); // isLoading$ = observableOf(false); paginationStateSub: Subscription; @@ -32,14 +41,18 @@ export class CreateReleaseComponent { details: FormGroup; overrides: FormGroup; + @ViewChild('releaseNameInputField') releaseNameInputField: ElementRef; @ViewChild('overridesYamlTextArea') overridesYamlTextArea: ElementRef; @ViewChild(MatTextareaAutosize) overridesYamlAutosize: MatTextareaAutosize; + private valuesYaml = ''; + constructor( private route: ActivatedRoute, public endpointsService: EndpointsService, private store: Store, - private httpClient: HttpClient + private httpClient: HttpClient, + private confirmDialog: ConfirmationDialogService, ) { const chart = this.route.snapshot.params; this.cancelUrl = `/monocular/charts/${chart.repo}/${chart.chartName}/${chart.version}`; @@ -59,11 +72,51 @@ export class CreateReleaseComponent { this.validate$ = this.details.statusChanges.pipe( map(() => this.details.valid) ); + + // Fetch the values.yaml for the Chart + const valuesYamlUrl = `/pp/v1/chartsvc/v1/assets/${chart.repo}/${chart.chartName}/versions/${chart.version}/values.yaml`; + + this.httpClient.get(valuesYamlUrl, { responseType: 'text' }).subscribe(response => { + this.valuesYaml = response; + }); + } + + public useValuesYaml() { + + + if (this.overrides.value.values.length !== 0) { + this.confirmDialog.open(this.overwriteValuesConfirmation, () => { + this.replaceWithValuesYaml(); + }); + + } else { + this.replaceWithValuesYaml(); + } + } + + private replaceWithValuesYaml() { + this.overrides.controls.values.setValue(this.valuesYaml, {onlySelf: true}); + } + + ngOnInit() { + // Auto select endpoint if there is only one + this.kubeEndpoints$.pipe( + first(), + tap(ep => { + console.log(ep); + if (ep.length === 1) { + this.details.controls.endpoint.setValue(ep[0].guid, {onlySelf: true}); + setTimeout(() => { + this.releaseNameInputField.nativeElement.focus(); + }, 1); + } + }) + ).subscribe(); } onEnterOverrides = () => { setTimeout(() => { - this.overridesYamlAutosize.resizeToFitContent(true); + // this.overridesYamlAutosize.resizeToFitContent(true); this.overridesYamlTextArea.nativeElement.focus(); }, 1); } diff --git a/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.ts b/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.ts index a323bef157..eb62b9eff1 100644 --- a/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.ts +++ b/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.ts @@ -1,6 +1,6 @@ import { Component, Input } from '@angular/core'; import * as markdown from 'marked'; -import { Observable } from 'rxjs'; +import { Observable, of as observableOf } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; import { ChartVersion } from '../../shared/models/chart-version'; @@ -40,9 +40,9 @@ export class ChartDetailsReadmeComponent { catchError((error) => { this.loading = false; if (error.status === 404) { - return '

    No Readme available for this chart

    '; + return observableOf('

    No Readme available for this chart

    '); } else { - return '

    An error occurred retrieving Readme

    '; + return observableOf('

    An error occurred retrieving Readme

    '); } })); } diff --git a/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details.component.html b/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details.component.html index 77c63c4118..499d896079 100644 --- a/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details.component.html +++ b/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details.component.html @@ -5,7 +5,7 @@

    Sorry, we couldn't find the chart

    From ad64cc3365581e9b79f8ecf39ecd2de32dc7853a Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Mon, 20 May 2019 15:46:09 +0100 Subject: [PATCH 043/648] Fix OIDC Token refresh --- src/jetstream/auth_providers.go | 6 ++++++ src/jetstream/main.go | 2 +- src/jetstream/oidc_requests.go | 6 +++--- src/jetstream/passthrough.go | 2 +- src/jetstream/plugins/kubernetes/auth/awsiam.go | 8 ++++++++ src/jetstream/plugins/kubernetes/auth/azure.go | 10 ++++++++++ src/jetstream/plugins/kubernetes/auth/cert.go | 8 ++++++++ src/jetstream/plugins/kubernetes/auth/gke.go | 8 ++++++++ .../plugins/kubernetes/auth/kubeconfig.go | 10 +++++++++- src/jetstream/plugins/kubernetes/auth/oidc.go | 16 +++++++++++++++- src/jetstream/plugins/kubernetes/auth/types.go | 5 +++++ .../plugins/kubernetes/auth_providers.go | 9 ++------- .../repository/interfaces/portal_proxy.go | 7 ++++--- src/jetstream/repository/interfaces/structs.go | 4 +--- 14 files changed, 81 insertions(+), 20 deletions(-) diff --git a/src/jetstream/auth_providers.go b/src/jetstream/auth_providers.go index 77a33e30b1..83de6a3ce8 100644 --- a/src/jetstream/auth_providers.go +++ b/src/jetstream/auth_providers.go @@ -13,3 +13,9 @@ func (p *portalProxy) AddAuthProvider(name string, provider interfaces.AuthProvi func (p *portalProxy) GetAuthProvider(name string) interfaces.AuthProvider { return p.AuthProviders[name] } + +func (p *portalProxy) HasAuthProvider(name string) bool { + _, ok := p.AuthProviders[name] + return ok +} + diff --git a/src/jetstream/main.go b/src/jetstream/main.go index 47bd7dccb6..f31b701aef 100644 --- a/src/jetstream/main.go +++ b/src/jetstream/main.go @@ -586,7 +586,7 @@ func newPortalProxy(pc interfaces.PortalConfig, dcp *sql.DB, ss HttpSessionStore // OIDC pp.AddAuthProvider(interfaces.AuthTypeOIDC, interfaces.AuthProvider{ - Handler: pp.doOidcFlowRequest, + Handler: pp.DoOidcFlowRequest, }) return pp diff --git a/src/jetstream/oidc_requests.go b/src/jetstream/oidc_requests.go index cff3a76a80..67615548a7 100644 --- a/src/jetstream/oidc_requests.go +++ b/src/jetstream/oidc_requests.go @@ -9,15 +9,15 @@ import ( log "github.com/sirupsen/logrus" ) -func (p *portalProxy) doOidcFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { - log.Debug("doOidcFlowRequest") +func (p *portalProxy) DoOidcFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { + log.Debug("DoOidcFlowRequest") authHandler := p.OAuthHandlerFunc(cnsiRequest, req, p.RefreshOidcToken) return p.DoAuthFlowRequest(cnsiRequest, req, authHandler) } func (p *portalProxy) RefreshOidcToken(skipSSLValidation bool, cnsiGUID, userGUID, client, clientSecret, tokenEndpoint string) (t interfaces.TokenRecord, err error) { - log.Debug("refreshToken") + log.Debug("RefreshOidcToken") userToken, ok := p.GetCNSITokenRecordWithDisconnected(cnsiGUID, userGUID) if !ok { return t, fmt.Errorf("Info could not be found for user with GUID %s", userGUID) diff --git a/src/jetstream/passthrough.go b/src/jetstream/passthrough.go index 604e034989..ea6499475f 100644 --- a/src/jetstream/passthrough.go +++ b/src/jetstream/passthrough.go @@ -398,7 +398,7 @@ func (p *portalProxy) doRequest(cnsiRequest *interfaces.CNSIRequest, done chan<- // Copy original headers through, except custom portal-proxy Headers fwdCNSIStandardHeaders(cnsiRequest, req) - // Find the auth provider for the auth type - default ot oauthflow + // Find the auth provider for the auth type - default to oauthflow authHandler := p.GetAuthProvider(tokenRec.AuthType) if authHandler.Handler != nil { res, err = authHandler.Handler(cnsiRequest, req) diff --git a/src/jetstream/plugins/kubernetes/auth/awsiam.go b/src/jetstream/plugins/kubernetes/auth/awsiam.go index 5bdc813264..8ff7f6f862 100644 --- a/src/jetstream/plugins/kubernetes/auth/awsiam.go +++ b/src/jetstream/plugins/kubernetes/auth/awsiam.go @@ -143,6 +143,14 @@ func (c *AWSKubeAuth) getTokenIAM(info AWSIAMUserInfo) (string, error) { return tok.Token, nil } +func (c *AWSKubeAuth) RegisterJetstreamAuthType(portal interfaces.PortalProxy) { + // Register auth type with Jetstream + c.portalProxy.AddAuthProvider(c.GetName(), interfaces.AuthProvider{ + Handler: c.DoFlowRequest, + UserInfo: c.GetUserFromToken, + }) +} + func (c *AWSKubeAuth) DoFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { log.Debug("doAWSIAMFlowRequest") diff --git a/src/jetstream/plugins/kubernetes/auth/azure.go b/src/jetstream/plugins/kubernetes/auth/azure.go index 0447b2e528..88f239d1f4 100644 --- a/src/jetstream/plugins/kubernetes/auth/azure.go +++ b/src/jetstream/plugins/kubernetes/auth/azure.go @@ -98,3 +98,13 @@ func isAKSAuth(k *config.KubeConfigUser) bool { } return true } + + +func (c *AzureKubeAuth) RegisterJetstreamAuthType(portal interfaces.PortalProxy) { + // Register auth type with Jetstream + c.portalProxy.AddAuthProvider(c.GetName(), interfaces.AuthProvider{ + Handler: c.DoFlowRequest, + UserInfo: c.GetUserFromToken, + }) +} + diff --git a/src/jetstream/plugins/kubernetes/auth/cert.go b/src/jetstream/plugins/kubernetes/auth/cert.go index 517ef8a780..9a067a755d 100644 --- a/src/jetstream/plugins/kubernetes/auth/cert.go +++ b/src/jetstream/plugins/kubernetes/auth/cert.go @@ -175,3 +175,11 @@ func (c *CertKubeAuth) RefreshCertAuth(skipSSLValidation bool, cnsiGUID, userGUI return userToken, nil } + +func (c *CertKubeAuth) RegisterJetstreamAuthType(portal interfaces.PortalProxy) { + // Register auth type with Jetstream + c.portalProxy.AddAuthProvider(c.GetName(), interfaces.AuthProvider{ + Handler: c.DoFlowRequest, + UserInfo: c.GetUserFromToken, + }) +} diff --git a/src/jetstream/plugins/kubernetes/auth/gke.go b/src/jetstream/plugins/kubernetes/auth/gke.go index 9a1193d4ad..a05a794365 100644 --- a/src/jetstream/plugins/kubernetes/auth/gke.go +++ b/src/jetstream/plugins/kubernetes/auth/gke.go @@ -193,3 +193,11 @@ func (c *GKEKubeAuth) refreshGKEToken(skipSSLValidation bool, clientID, clientSe err = json.Unmarshal(respBody, &tokenInfo) return tokenInfo, err } + +func (c *GKEKubeAuth) RegisterJetstreamAuthType(portal interfaces.PortalProxy) { + // Register auth type with Jetstream + c.portalProxy.AddAuthProvider(c.GetName(), interfaces.AuthProvider{ + Handler: c.DoFlowRequest, + UserInfo: c.GetUserFromToken, + }) +} diff --git a/src/jetstream/plugins/kubernetes/auth/kubeconfig.go b/src/jetstream/plugins/kubernetes/auth/kubeconfig.go index 085a9d82ac..0af53ac0b5 100644 --- a/src/jetstream/plugins/kubernetes/auth/kubeconfig.go +++ b/src/jetstream/plugins/kubernetes/auth/kubeconfig.go @@ -4,7 +4,7 @@ import ( "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" ) -const AuthConnectTypeKubeConfig = "KubeConfig" +const AuthConnectTypeKubeConfig = "KubeConfig" // KubeConfigAuth is same as OIDC with different name type KubeConfigAuth struct { @@ -19,3 +19,11 @@ func InitKubeConfigAuth(portalProxy interfaces.PortalProxy) KubeAuthProvider { func (c *KubeConfigAuth) GetName() string { return AuthConnectTypeKubeConfig } + +func (c *KubeConfigAuth) RegisterJetstreamAuthType(portal interfaces.PortalProxy) { + // Register auth type with Jetstream + c.portalProxy.AddAuthProvider(c.GetName(), interfaces.AuthProvider{ + Handler: c.portalProxy.DoOidcFlowRequest, + UserInfo: nil, + }) +} diff --git a/src/jetstream/plugins/kubernetes/auth/oidc.go b/src/jetstream/plugins/kubernetes/auth/oidc.go index 199deee5ca..c61dd0ae16 100644 --- a/src/jetstream/plugins/kubernetes/auth/oidc.go +++ b/src/jetstream/plugins/kubernetes/auth/oidc.go @@ -151,5 +151,19 @@ func (c *OIDCKubeAuth) GetOIDCConfig(k *config.KubeConfigUser) (*KubeConfigAuthP func (c *OIDCKubeAuth) DoFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) { log.Debug("DoFlowRequest (OIDC)") - return c.portalProxy.DoOAuthFlowRequest(cnsiRequest, req) + return c.portalProxy.DoOidcFlowRequest(cnsiRequest, req) +} + +func (c *OIDCKubeAuth) RegisterJetstreamAuthType(portal interfaces.PortalProxy) { + // No need to register OIDC, as its already built in + existing := c.portalProxy.HasAuthProvider(c.GetName()) + if existing { + log.Errorf("Auth Provider: %s already registered", c.GetName()) + } else { + // Register auth type with Jetstream + c.portalProxy.AddAuthProvider(c.GetName(), interfaces.AuthProvider{ + Handler: c.portalProxy.DoOidcFlowRequest, + UserInfo: nil, + }) + } } diff --git a/src/jetstream/plugins/kubernetes/auth/types.go b/src/jetstream/plugins/kubernetes/auth/types.go index 9a32914bc9..e459d270c4 100644 --- a/src/jetstream/plugins/kubernetes/auth/types.go +++ b/src/jetstream/plugins/kubernetes/auth/types.go @@ -17,6 +17,11 @@ type KubeAuthProvider interface { AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) + RegisterJetstreamAuthType(portal interfaces.PortalProxy) +} + +// KubeJetstreamAuthProvider is the optional interface that can be implemented if you want to control Jetstream Auth Registration +type KubeJetstreamAuthProvider interface { DoFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) GetUserFromToken(cnsiGUID string, tokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) } diff --git a/src/jetstream/plugins/kubernetes/auth_providers.go b/src/jetstream/plugins/kubernetes/auth_providers.go index dcd73d2ca3..eaaf6c8a83 100644 --- a/src/jetstream/plugins/kubernetes/auth_providers.go +++ b/src/jetstream/plugins/kubernetes/auth_providers.go @@ -4,7 +4,6 @@ import ( "strings" "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/auth" - "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" ) var kubeAuthProviders map[string]auth.KubeAuthProvider @@ -22,12 +21,8 @@ func (c *KubernetesSpecification) AddAuthProvider(provider auth.KubeAuthProvider kubeAuthProviders[name] = provider - // Register auth type with Jetstream - c.portalProxy.AddAuthProvider(name, interfaces.AuthProvider{ - Handler: provider.DoFlowRequest, - UserInfo: provider.GetUserFromToken, - }) - + // Get the auth provider to register itself with Stratos, if needed + provider.RegisterJetstreamAuthType(c.portalProxy) } // GetAuthProvider gets a Kubernetes auth provider by key diff --git a/src/jetstream/repository/interfaces/portal_proxy.go b/src/jetstream/repository/interfaces/portal_proxy.go index f8ce8d86b4..9815f8f610 100644 --- a/src/jetstream/repository/interfaces/portal_proxy.go +++ b/src/jetstream/repository/interfaces/portal_proxy.go @@ -56,7 +56,7 @@ type PortalProxy interface { RefreshUAALogin(username, password string, store bool) error GetUserTokenInfo(tok string) (u *JWTUserTokenInfo, err error) GetUAAUser(userGUID string) (*ConnectedUser, error) - + // Proxy API requests ProxyRequest(c echo.Context, uri *url.URL) (map[string]*CNSIRequest, error) DoProxyRequest(requests []ProxyRequestInfo) (map[string]*CNSIRequest, error) @@ -68,9 +68,11 @@ type PortalProxy interface { AddAuthProvider(name string, provider AuthProvider) GetAuthProvider(name string) AuthProvider + HasAuthProvider(name string) bool DoAuthFlowRequest(cnsiRequest *CNSIRequest, req *http.Request, authHandler AuthHandlerFunc) (*http.Response, error) OAuthHandlerFunc(cnsiRequest *CNSIRequest, req *http.Request, refreshOAuthTokenFunc RefreshOAuthTokenFunc) AuthHandlerFunc DoOAuthFlowRequest(cnsiRequest *CNSIRequest, req *http.Request) (*http.Response, error) + DoOidcFlowRequest(cnsiRequest *CNSIRequest, req *http.Request) (*http.Response, error) GetCNSIUserFromOAuthToken(cnsiGUID string, cfTokenRecord *TokenRecord) (*ConnectedUser, bool) // Tokens - lower-level access @@ -79,8 +81,7 @@ type PortalProxy interface { AddLoginHook(priority int, function LoginHookFunc) error ExecuteLoginHooks(c echo.Context) error - + // Plugins GetPlugin(name string) interface{} - } diff --git a/src/jetstream/repository/interfaces/structs.go b/src/jetstream/repository/interfaces/structs.go index c3ba4fecfb..65783024e5 100644 --- a/src/jetstream/repository/interfaces/structs.go +++ b/src/jetstream/repository/interfaces/structs.go @@ -67,12 +67,10 @@ type ConnectedEndpoint struct { const ( // AuthTypeOAuth2 means OAuth2 AuthTypeOAuth2 = "OAuth2" - // AuthTypeOIDC means no OIDC + // AuthTypeOIDC means OIDC AuthTypeOIDC = "OIDC" // AuthTypeHttpBasic means HTTP Basic auth AuthTypeHttpBasic = "HttpBasic" - // AuthTypeAKS means AKS - AuthTypeAKS = "AKS" ) const ( From 8d256ce36d5226ff791c8cd58aa2fb9983589b9b Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Mon, 20 May 2019 15:48:56 +0100 Subject: [PATCH 044/648] Fix logging msg --- src/jetstream/plugins/kubernetes/auth/oidc.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/jetstream/plugins/kubernetes/auth/oidc.go b/src/jetstream/plugins/kubernetes/auth/oidc.go index c61dd0ae16..70518c16b0 100644 --- a/src/jetstream/plugins/kubernetes/auth/oidc.go +++ b/src/jetstream/plugins/kubernetes/auth/oidc.go @@ -157,9 +157,7 @@ func (c *OIDCKubeAuth) DoFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *h func (c *OIDCKubeAuth) RegisterJetstreamAuthType(portal interfaces.PortalProxy) { // No need to register OIDC, as its already built in existing := c.portalProxy.HasAuthProvider(c.GetName()) - if existing { - log.Errorf("Auth Provider: %s already registered", c.GetName()) - } else { + if !existing { // Register auth type with Jetstream c.portalProxy.AddAuthProvider(c.GetName(), interfaces.AuthProvider{ Handler: c.portalProxy.DoOidcFlowRequest, From 27e508223a56a8dafbd431900b2651e525df3be5 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 6 Jun 2019 13:33:31 +0100 Subject: [PATCH 045/648] Add product version metadata (used in Helm Chart) --- custom-src/stratos.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/custom-src/stratos.yaml b/custom-src/stratos.yaml index 7bd90a7fce..91aded8cec 100644 --- a/custom-src/stratos.yaml +++ b/custom-src/stratos.yaml @@ -1 +1,2 @@ -title: SUSE CAP \ No newline at end of file +title: SUSE CAP +productVersion: 1.4 From b795ca8443c8784312e939a8967933df4c5a4b6f Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Mon, 15 Jul 2019 14:45:47 +0100 Subject: [PATCH 046/648] Cloud Console WIP --- .../kube-console/kube-console.component.html | 18 + .../kube-console/kube-console.component.scss | 0 .../kube-console.component.spec.ts | 41 ++ .../kube-console/kube-console.component.ts | 125 ++++ .../custom/kubernetes/kubernetes.module.ts | 4 +- .../custom/kubernetes/kubernetes.routing.ts | 8 + .../kubernetes-summary.component.html | 4 + .../kubernetes-summary.component.ts | 3 + deploy/Dockerfile.kubeconsole.yml | 18 + deploy/ci/build-aio-image-canary.yml | 1 + deploy/kubeconsole.bashrc | 42 ++ .../ssh-viewer/ssh-viewer.component.html | 9 +- .../ssh-viewer/ssh-viewer.component.scss | 2 + .../ssh-viewer/ssh-viewer.component.ts | 25 +- .../plugins/kubernetes/endpoint_config.go | 68 ++ src/jetstream/plugins/kubernetes/go.mod | 1 + src/jetstream/plugins/kubernetes/go.sum | 4 +- .../plugins/kubernetes/kube_console.go | 582 ++++++++++++++++++ src/jetstream/plugins/kubernetes/main.go | 3 + 19 files changed, 952 insertions(+), 6 deletions(-) create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-console/kube-console.component.html create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-console/kube-console.component.scss create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-console/kube-console.component.spec.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-console/kube-console.component.ts create mode 100644 deploy/Dockerfile.kubeconsole.yml create mode 100644 deploy/kubeconsole.bashrc create mode 100644 src/jetstream/plugins/kubernetes/kube_console.go diff --git a/custom-src/frontend/app/custom/kubernetes/kube-console/kube-console.component.html b/custom-src/frontend/app/custom/kubernetes/kube-console/kube-console.component.html new file mode 100644 index 0000000000..e8cfda74c0 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-console/kube-console.component.html @@ -0,0 +1,18 @@ + +

    Kubernetes Console

    +
    + + + + + +
    +
    + + diff --git a/custom-src/frontend/app/custom/kubernetes/kube-console/kube-console.component.scss b/custom-src/frontend/app/custom/kubernetes/kube-console/kube-console.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/custom-src/frontend/app/custom/kubernetes/kube-console/kube-console.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kube-console/kube-console.component.spec.ts new file mode 100644 index 0000000000..030ec22e95 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-console/kube-console.component.spec.ts @@ -0,0 +1,41 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { TabNavService } from '../../../../tab-nav.service'; +import { ApplicationServiceMock } from '../../../../test-framework/application-service-helper'; +import { createBasicStoreModule } from '../../../../test-framework/store-test-helper'; +import { CoreModule } from '../../../core/core.module'; +import { SharedModule } from '../../../shared/shared.module'; +import { KubeConsoleComponent } from './kube-console.component'; + +describe('KubeConsoleComponent', () => { + let component: KubeConsoleComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [KubeConsoleComponent], + imports: [ + CoreModule, + SharedModule, + RouterTestingModule, + createBasicStoreModule() + ], + providers: [ + { provide: ApplicationService, useClass: ApplicationServiceMock }, + TabNavService + ], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubeConsoleComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/custom-src/frontend/app/custom/kubernetes/kube-console/kube-console.component.ts b/custom-src/frontend/app/custom/kubernetes/kube-console/kube-console.component.ts new file mode 100644 index 0000000000..32481eb139 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-console/kube-console.component.ts @@ -0,0 +1,125 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Store } from '@ngrx/store'; +import { NEVER, Observable, Subject, Subscription } from 'rxjs'; +import websocketConnect from 'rxjs-websockets'; +import { catchError, first, map } from 'rxjs/operators'; + +import { AppState } from '../../../../../store/src/app-state'; +import { IApp } from '../../../core/cf-api.types'; +import { IHeaderBreadcrumb } from '../../../shared/components/page-header/page-header.types'; +import { SshViewerComponent } from '../../../shared/components/ssh-viewer/ssh-viewer.component'; +import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; +import { BaseKubeGuid } from '../kubernetes-page.types'; +import { KubernetesService } from '../services/kubernetes.service'; + + +@Component({ + selector: 'app-kube-console', + templateUrl: './kube-console.component.html', + styleUrls: ['./kube-console.component.scss'], + providers: [ + { + provide: BaseKubeGuid, + useFactory: (activatedRoute: ActivatedRoute) => { + return { + guid: activatedRoute.snapshot.params.endpointId + }; + }, + deps: [ + ActivatedRoute + ] + }, + KubernetesService, + KubernetesEndpointService, + ] +}) +export class KubeConsoleComponent implements OnInit { + + public messages: Observable; + + public connectionStatus: Observable; + + public sshInput: Subject; + + public errorMessage: string; + + public sshRoute: string; + + public connected: boolean; + + public kubeSummaryLink: string; + + private connection: Subscription; + + public instanceId: string; + + public breadcrumbs$: Observable; + + @ViewChild('sshViewer') sshViewer: SshViewerComponent; + + private getBreadcrumbs( + application: IApp, + ) { + return [ + { + breadcrumbs: [ + { value: application.name, routerLink: `/kubernetes/${application.cfGuid}/${application.guid}/instances` } + ] + }, + ]; + } + + constructor( + public kubeEndpointService: KubernetesEndpointService, + // private activatedRoute: ActivatedRoute, + // private store: Store, + ) { } + + ngOnInit() { + + const guid = this.kubeEndpointService.baseKube.guid; + + console.log('HERE'); + console.log(guid); + // const routeParams = this.activatedRoute.snapshot.params; + // this.instanceId = routeParams.index; + + this.kubeSummaryLink = ( + `/kubernetes/${guid}/summary` + ); + + if (!guid) { + this.messages = NEVER; + this.connectionStatus = NEVER; + } else { + const host = window.location.host; + const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws'; + const streamUrl = ( + `${protocol}://${host}/pp/v1/kubeconsole/${guid}` + ); + this.sshInput = new Subject(); + const connection = websocketConnect( + streamUrl, + this.sshInput + ); + + console.log(streamUrl); + + this.messages = connection.messages.pipe( + catchError(e => { + if (e.type === 'error') { + this.errorMessage = 'Error connecting to web socket'; + } + return []; + })); + + this.connectionStatus = connection.connectionStatus; + + // this.breadcrumbs$ = this.applicationService.waitForAppEntity$.pipe( + // map(app => this.getBreadcrumbs(app.entity.entity)), + // first() + // ); + } + } +} diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes.module.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes.module.ts index 5eb62e9208..13a2ac8495 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes.module.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes.module.ts @@ -91,6 +91,7 @@ import { KubernetesNamespacesTabComponent } from './tabs/kubernetes-namespaces-t import { KubernetesNodesTabComponent } from './tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component'; import { KubernetesPodsTabComponent } from './tabs/kubernetes-pods-tab/kubernetes-pods-tab.component'; import { KubernetesSummaryTabComponent } from './tabs/kubernetes-summary-tab/kubernetes-summary.component'; +import { KubeConsoleComponent } from './kube-console/kube-console.component'; /* tslint:enable */ @@ -145,7 +146,8 @@ import { KubernetesSummaryTabComponent } from './tabs/kubernetes-summary-tab/kub KubeNamespacePodCountComponent, PodNameLinkComponent, NodePodCountComponent, - KubernetesServicePortsComponent + KubernetesServicePortsComponent, + KubeConsoleComponent, ], exports: [ KubernetesServicePortsComponent diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes.routing.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes.routing.ts index f2d3839458..1ab7fe2376 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes.routing.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes.routing.ts @@ -1,3 +1,4 @@ +import { KubeConsoleComponent } from './kube-console/kube-console.component'; import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; @@ -155,6 +156,13 @@ const kubernetes: Routes = [{ } } ] +}, +{ + path: ':endpointId/console', + component: KubeConsoleComponent, + data: { + uiNoMargin: true + } } ]; diff --git a/custom-src/frontend/app/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.html b/custom-src/frontend/app/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.html index 7198ba3c46..dc35b490cb 100644 --- a/custom-src/frontend/app/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.html +++ b/custom-src/frontend/app/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.html @@ -4,6 +4,10 @@ dashboard View Dashboard + diff --git a/custom-src/frontend/app/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts b/custom-src/frontend/app/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts index d542073359..c1575fd95c 100644 --- a/custom-src/frontend/app/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts +++ b/custom-src/frontend/app/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts @@ -60,6 +60,8 @@ export class KubernetesSummaryTabComponent implements OnInit, OnDestroy { source: SafeResourceUrl; dashboardLink: string; + kubeConsoleLink: string; + public podCapacity$: Observable; public diskPressure$: Observable; public memoryPressure$: Observable; @@ -240,6 +242,7 @@ export class KubernetesSummaryTabComponent implements OnInit, OnDestroy { warningText: `Nodes with unknown ready status found` }); this.dashboardLink = `/kubernetes/${guid}/dashboard`; + this.kubeConsoleLink = `/kubernetes/${guid}/console`; this.kubeNodeVersions$ = this.getNodeKubeVersions(nodes$).pipe(startWith('-')); diff --git a/deploy/Dockerfile.kubeconsole.yml b/deploy/Dockerfile.kubeconsole.yml new file mode 100644 index 0000000000..48c3f21443 --- /dev/null +++ b/deploy/Dockerfile.kubeconsole.yml @@ -0,0 +1,18 @@ +FROM splatform/stratos-bk-base:opensuse as prod-build +USER root +RUN zypper in -y wget tar unzip nano vim curl + +RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && \ + chmod +x ./kubectl && \ + mv ./kubectl /usr/local/bin/kubectl + +# Install Helm + +RUN curl -LO https://git.io/get_helm.sh && \ + chmod 700 get_helm.sh && \ + ./get_helm.sh + +ADD ./kubeconsole.bashrc /root/.bashrc +WORKDIR /root + +CMD exec /bin/bash -c "trap : TERM INT; sleep infinity & wait" diff --git a/deploy/ci/build-aio-image-canary.yml b/deploy/ci/build-aio-image-canary.yml index 83df27b500..c8bc0485a7 100644 --- a/deploy/ci/build-aio-image-canary.yml +++ b/deploy/ci/build-aio-image-canary.yml @@ -75,3 +75,4 @@ jobs: tag: "stratos/deploy/ci/tasks/build-images/canary-tag" tag_as_latest: false labels_file: image-tag/image-labels + squash: true diff --git a/deploy/kubeconsole.bashrc b/deploy/kubeconsole.bashrc new file mode 100644 index 0000000000..2ac7e17679 --- /dev/null +++ b/deploy/kubeconsole.bashrc @@ -0,0 +1,42 @@ + +CYAN="\033[96m" +YELLOW="\033[93m" +GREEN="\033[92m" +RESET="\033[0m" +BOLD="\033[1m" +DIM="\033[2m" + +echo -e "${BOLD}${GREEN}SUSE Cloud Application Platform${RESET}" +echo "" +echo -e "${CYAN}Kubernetes Console${RESET}" +echo "" + +export KUBECONFIG=/root/.stratos/kubeconfig +export PS1="\033[92mstratos>\033[0m" +alias k=kubectl + +helm init --client-only > /dev/null +helm repo remove local > /dev/null +helm repo remove stable > /dev/null + +if [ -f "/root/.stratos/helm-setup" ]; then + echo "Setting up Helm repositories ..." + source "/root/.stratos/helm-setup" > /dev/null + helm repo update 2>&1 > /dev/null + echo "" +fi + + +if [ -f "/root/.stratos/history" ]; then + cat /root/.stratos/history > /root/.bash_history +fi + +# Make Bash append rather than overwrite the history on disk: +shopt -s histappend +# A new shell gets the history lines from all previous shells +PROMPT_COMMAND='history -a' +# Don't put duplicate lines in the history. +export HISTCONTROL=ignoredups + +echo "Ready" +echo "" diff --git a/src/frontend/packages/core/src/shared/components/ssh-viewer/ssh-viewer.component.html b/src/frontend/packages/core/src/shared/components/ssh-viewer/ssh-viewer.component.html index 3d71c6e1a8..44cac219ee 100644 --- a/src/frontend/packages/core/src/shared/components/ssh-viewer/ssh-viewer.component.html +++ b/src/frontend/packages/core/src/shared/components/ssh-viewer/ssh-viewer.component.html @@ -1,12 +1,17 @@
    -
    +
    Error occurred establishing SSH connection
    Disconnected -
    +
    +
    +
    + {{ message }} +
    +
    diff --git a/src/frontend/packages/core/src/shared/components/ssh-viewer/ssh-viewer.component.scss b/src/frontend/packages/core/src/shared/components/ssh-viewer/ssh-viewer.component.scss index 000e0340e2..3adb5a5af4 100644 --- a/src/frontend/packages/core/src/shared/components/ssh-viewer/ssh-viewer.component.scss +++ b/src/frontend/packages/core/src/shared/components/ssh-viewer/ssh-viewer.component.scss @@ -19,6 +19,8 @@ display: flex; flex: 1; flex-direction: column; + max-height: 100%; + overflow: hidden; } } diff --git a/src/frontend/packages/core/src/shared/components/ssh-viewer/ssh-viewer.component.ts b/src/frontend/packages/core/src/shared/components/ssh-viewer/ssh-viewer.component.ts index f3c9382c26..911c75c9d3 100644 --- a/src/frontend/packages/core/src/shared/components/ssh-viewer/ssh-viewer.component.ts +++ b/src/frontend/packages/core/src/shared/components/ssh-viewer/ssh-viewer.component.ts @@ -38,6 +38,8 @@ export class SshViewerComponent implements OnInit, OnDestroy, AfterViewChecked { public isConnecting = false; private isDestroying = false; + public message = ''; + @ViewChild('terminal') container: ElementRef; private xterm: Terminal; @@ -111,8 +113,11 @@ export class SshViewerComponent implements OnInit, OnDestroy, AfterViewChecked { this.msgSubscription = this.sshStream .subscribe( (data: string) => { - for (const c of data.split(' ')) { - this.xterm.write(String.fromCharCode(parseInt(c, 16))); + // Check for a window title message + if (!this.isWindowTitle(data)) { + for (const c of data.split(' ')) { + this.xterm.write(String.fromCharCode(parseInt(c, 16))); + } } }, (err) => { @@ -138,4 +143,20 @@ export class SshViewerComponent implements OnInit, OnDestroy, AfterViewChecked { this.sshInput.next(JSON.stringify({ cols: size.cols, rows: size.rows })); } } + + private isWindowTitle(data: string): boolean { + const chars = data.split(' '); + if (chars.length > 4 && + parseInt(chars[0], 16) === 27 && + parseInt(chars[1], 16) === 93 && + parseInt(chars[2], 16) === 50 && + parseInt(chars[3], 16) === 59) { + let title = ''; + for (let i = 4; i < chars.length - 1; i++) { + title += String.fromCharCode(parseInt(chars[i], 16)); + } + this.message = title; + } + return false; + } } diff --git a/src/jetstream/plugins/kubernetes/endpoint_config.go b/src/jetstream/plugins/kubernetes/endpoint_config.go index 22f3521f10..0a1329234d 100644 --- a/src/jetstream/plugins/kubernetes/endpoint_config.go +++ b/src/jetstream/plugins/kubernetes/endpoint_config.go @@ -1,7 +1,10 @@ package kubernetes import ( + "encoding/base64" "errors" + "fmt" + "regexp" log "github.com/sirupsen/logrus" @@ -45,6 +48,71 @@ func (c *KubernetesSpecification) GetConfigForEndpoint(masterURL string, token i } +func (c *KubernetesSpecification) GetKubeConfigForEndpoint(masterURL string, token interfaces.TokenRecord) (string, error) { + + name := "config-0" + clusterName := "cluster-0" + userName := "user-0" + + // Create a config + + // Initialize a new config + context := clientcmdapi.NewContext() + context.Cluster = clusterName + context.AuthInfo = userName + + // Configure the cluster + cluster := clientcmdapi.NewCluster() + cluster.Server = masterURL + cluster.InsecureSkipTLSVerify = true + + // Configure auth information + authInfo := clientcmdapi.NewAuthInfo() + err := c.addAuthInfoForEndpoint(authInfo, token) + + config := clientcmdapi.NewConfig() + config.Clusters[clusterName] = cluster + config.Kind = "Config" + config.Contexts[name] = context + config.AuthInfos[userName] = authInfo + config.CurrentContext = context.Cluster + + // Convert to string + str := `apiVersion: v1 +kind: Config +contexts: +- context: + cluster: kube + user: kube + name: kube +clusters: +- cluster: + insecure-skip-tls-verify: true + server: %s + name: kube +current-context: kube +preferences: {} +users: +- name: kube + user: +` + + space := regexp.MustCompile(`\t`) + s := space.ReplaceAllString(str, " ") + + // Now append the auth details + log.Infof("%+v", authInfo) + + if authInfo.ClientCertificateData != nil { + s = fmt.Sprintf("%s client-certificate-data: %s\n", s, base64.StdEncoding.EncodeToString(authInfo.ClientCertificateData)) + } + if authInfo.ClientKeyData != nil { + s = fmt.Sprintf("%s client-key-data: %s\n", s, base64.StdEncoding.EncodeToString(authInfo.ClientKeyData)) + } + + return fmt.Sprintf(s, masterURL), err +} + func (c *KubernetesSpecification) addAuthInfoForEndpoint(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { log.Debug("addAuthInfoForEndpoint") diff --git a/src/jetstream/plugins/kubernetes/go.mod b/src/jetstream/plugins/kubernetes/go.mod index 5393fae8ef..be7ed920b0 100644 --- a/src/jetstream/plugins/kubernetes/go.mod +++ b/src/jetstream/plugins/kubernetes/go.mod @@ -46,6 +46,7 @@ require ( github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.8.1 // indirect github.com/russross/blackfriday v2.0.0+incompatible // indirect + github.com/satori/go.uuid v1.2.0 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/sirupsen/logrus v1.3.0 github.com/spf13/cobra v0.0.3 // indirect diff --git a/src/jetstream/plugins/kubernetes/go.sum b/src/jetstream/plugins/kubernetes/go.sum index 69a959d32c..6243abc37b 100644 --- a/src/jetstream/plugins/kubernetes/go.sum +++ b/src/jetstream/plugins/kubernetes/go.sum @@ -159,9 +159,11 @@ github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nL github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v2.0.0 h1:L7Oc72h7rDqGkbUorN/ncJ4N/y220/YRezHvBoKLOFA= github.com/russross/blackfriday v2.0.0/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME= diff --git a/src/jetstream/plugins/kubernetes/kube_console.go b/src/jetstream/plugins/kubernetes/kube_console.go new file mode 100644 index 0000000000..7f54775d01 --- /dev/null +++ b/src/jetstream/plugins/kubernetes/kube_console.go @@ -0,0 +1,582 @@ +package kubernetes + +import ( + "bytes" + "errors" + "fmt" + "crypto/tls" + //"encoding/base64" + "encoding/json" + "net" + "net/http" + "time" + "strings" + //"io/ioutil" + //yaml "gopkg.in/yaml.v2" + + uuid "github.com/satori/go.uuid" + "github.com/labstack/echo" + log "github.com/sirupsen/logrus" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/auth" + restclient "k8s.io/client-go/rest" + //"k8s.io/kubernetes/pkg/client/unversioned/remotecommand" + "github.com/gorilla/websocket" + "k8s.io/client-go/kubernetes" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + + "k8s.io/client-go/tools/remotecommand" + scheme "k8s.io/client-go/kubernetes/scheme" +) + +const ( + consoleContainerName = "kube-console" +) + +var history = "" + +// TTY Resize, see: https://gitlab.cncf.ci/kubernetes/kubernetes/commit/3b21a9901bcd48bb452d3bf1a0cddc90dae142c4#9691a2f9b9c30711f0397221db0b9ac55ab0e2d1 + +// Allow connections from any Origin +var upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { return true }, +} + +// PodCreationData stores the clients and names used ot create pod and secret +type PodCreationData struct { + ClientSet *kubernetes.Clientset + Config *restclient.Config + Namespace string + PodClient corev1.PodInterface + SecretClient corev1.SecretInterface + PodName string + SecretName string +} + +// KeyCode - JSON object that is passed from the front-end to notify of a key press or a term resize +type KeyCode struct { + Key string `json:"key"` + Cols int `json:"cols"` + Rows int `json:"rows"` +} + +type TermianlSize struct { + Width uint16 + Height uint16 +} + +const ( + // Time allowed to write a message to the peer. + writeWait = 10 * time.Second +) + +func (k *KubernetesSpecification) KubeConsole(c echo.Context) error { + + c.Response().Status = 500 + + log.Info("Kube Console backend request") + + endpointGUID := c.Param("guid") + userGUID := c.Get("user_id").(string) + + ///api/v1/namespaces/project-1/pods/pod-1-lmlzj/exec?command=/bin/bash&stdin=true&stderr=true&stdout=true&tty=true + + namespace := "stratos" + + // TODO: Refresh auth token + + // Upgrade the web socket for the incoming request + ws, pingTicker, err := interfaces.UpgradeToWebSocket(c) + if err != nil { + return err + } + defer ws.Close() + defer pingTicker.Stop() + + // Send a message to say that we are creating the pod + sendProgressMessage(ws, "Launching Kubernetes Console ... one moment please") + + var p = k.portalProxy + + cnsiRecord, err := p.GetCNSIRecord(endpointGUID) + if err != nil { + return errors.New("Could not get endpoint information") + } + + // Get token for this users + tokenRec, ok := p.GetCNSITokenRecord(endpointGUID, userGUID) + if !ok { + return errors.New("Could not get token") + } + + podData, err := k.createPod(c, cnsiRecord, tokenRec, namespace, ws) + // Clear progress message + sendProgressMessage(ws, "") + + if err != nil { + log.Error("ERROR creating secret or pod") + log.Info(err) + k.cleanupPodAndSecret(podData) + return err + } + + log.Info(podData.PodName) + + log.Info(tokenRec.AuthToken) + log.Info(tokenRec.AuthType) + + // Make the info call to the SSH endpoint info + // Currently this is not cached, so we must get it each time + apiEndpoint := cnsiRecord.APIEndpoint + log.Info(apiEndpoint) + // target := fmt.Sprintf("%s/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/", apiEndpoint) + //target := fmt.Sprintf("%s/api/v1/namespaces/kube-system/services/http:kubernetes-dashboard:/proxy/", apiEndpoint) + // target := http://localhost:8001/api/v1/namespaces/kube-system/services/kubernetes-dashboard/proxy + target := fmt.Sprintf("%s/api/v1/namespaces/%s/pods/%s/exec?command=/bin/bash&stdin=true&stderr=true&stdout=true&tty=true", apiEndpoint, namespace, podData.PodName) + log.Info(target) + + // config, err := k.getConfig(&cnsiRecord, &tokenRec) + // if err != nil { + // return errors.New("Could not get config for this auth type") + // } + + + req, err := http.NewRequest("POST", target, nil) + if err != nil { + k.cleanupPodAndSecret(podData) + return errors.New("Could not create new HTTP request") + } + + // Set auth header so we log in if needed + if len(tokenRec.AuthToken) > 0 { + //req.Header.Add("Authorization", "Bearer "+tokenRec.AuthToken) + log.Info("Setting auth header") + } + + //req.Header.Add("Accept", "*/*") + + log.Info("Config") + log.Info("Making request") + log.Info(req) + + // endpointRequest := &interfaces.CNSIRequest{ + // GUID: endpointGUID, + // } + + kubeAuthToken := &auth.KubeCertificate{} + err = json.NewDecoder(strings.NewReader(tokenRec.AuthToken)).Decode(kubeAuthToken) + if err != nil { + k.cleanupPodAndSecret(podData) + return err + } + cert, err := kubeAuthToken.GetCerticate() + if err != nil { + k.cleanupPodAndSecret(podData) + return err + } + dial := (&net.Dialer{ + Timeout: time.Duration(30) * time.Second, + KeepAlive: 30 * time.Second, + }).Dial + + sslTransport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + Dial: dial, + TLSHandshakeTimeout: 10 * time.Second, // 10 seconds is a sound default value (default is 0) + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + Certificates: []tls.Certificate{cert}, + }, + MaxIdleConnsPerHost: 6, // (default is 2) + } + + kubeCertClient := http.Client{} + kubeCertClient.Transport = sslTransport + kubeCertClient.Timeout = time.Duration(30) * time.Second + + dialer := &websocket.Dialer{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + Certificates: []tls.Certificate{cert}, + }, + } + + if strings.HasPrefix(target ,"https://") { + target = "wss://" + target[8:] + } else { + target = "ws://" + target[7:] + } + + header := &http.Header{} + wsConn, res, err := dialer.Dial(target, *header) + + if err == nil { + defer wsConn.Close() + } + + // log.Info(err) + // log.Info(res) + // log.Info(wsConn) + + // if kubeAuthToken.Token != "" { + // req.Header.Set("Authorization", "bearer "+kubeAuthToken.Token) + // } + + //res, err := kubeCertClient.Do(req) + + + kubeCertClient.CloseIdleConnections() + + //var res *http.Response + + // var client http.Client + // client = p.GetHttpClientForRequest(req, cnsiRecord.SkipSSLValidation) + // res, err = client.Do(req) + + // Find the auth provider for the auth type - default to oauthflow + // authHandler := p.GetAuthProvider(tokenRec.AuthType) + // if authHandler.Handler != nil { + // res, err = authHandler.Handler(endpointRequest, req) + // } else { + // res, err = p.DoOAuthFlowRequest(endpointRequest, req) + // } + log.Error(err) +// log.Error(res) + + if err != nil { + log.Error("Failed to make request") + k.cleanupPodAndSecret(podData) + return errors.New("Could not make request") + } + + log.Error("=== Made request to exec endpoint OK") + log.Error(res) + + // Websockets next + //log.Info(wsConn) + + //done := make(chan struct{}) + stdoutDone := make(chan struct{}) + go pumpStdout(ws, wsConn, stdoutDone) + + // Read the input from the web socket and pipe it to the SSH client + for { + _, r, err := ws.ReadMessage() + if err != nil { + log.Error("Error reading message from web socket") + log.Warnf("%v+", err) + k.cleanupPodAndSecret(podData) + return err + } + + res := KeyCode{} + json.Unmarshal(r, &res) + + if res.Cols == 0 { + + + slice := make([]byte, 1) + slice[0] = 0 + slice = append(slice, []byte(res.Key)...) + wsConn.WriteMessage(websocket.TextMessage, slice) + } else { + // Terminal resize request + // if err := windowChange(session, res.Rows, res.Cols); err != nil { + // log.Error("Can not resize the PTY") + // } + log.Error("Terminal resize receieved") + + size := TermianlSize{ + Width: uint16(res.Cols), + Height: uint16(res.Rows), + } + j, _ := json.Marshal(size) + log.Info(j) + + resizeStream := []byte{4} + slice := append(resizeStream, j...) + log.Info(slice) + wsConn.WriteMessage(websocket.TextMessage, slice) + } + } + + // Cleanup + log.Info("*** Cleaning up.... ***") + + return k.cleanupPodAndSecret(podData) +} + +func pumpStdout(ws *websocket.Conn, source *websocket.Conn, done chan struct{}) { + //buffer := make([]byte, 32768) + for { + _, r, err := source.ReadMessage() + if err != nil { + log.Info(err) + ws.Close() + break + } + ws.SetWriteDeadline(time.Now().Add(writeWait)) + bytes := fmt.Sprintf("% x\n", r[1:]) + if err := ws.WriteMessage(websocket.TextMessage, []byte(bytes)); err != nil { + log.Error("App SSH Failed to write nessage") + ws.Close() + break + } + } +} + +func (k *KubernetesSpecification) createPod(c echo.Context, cnsiRecord interfaces.CNSIRecord, tokenRecord interfaces.TokenRecord, namespace string, ws *websocket.Conn) (*PodCreationData, error) { + + id := uuid.NewV4().String() + secretName := fmt.Sprintf("k8s-s-console-%s", id) + podName := fmt.Sprintf("k8s-p-console-%s", id) + + result := &PodCreationData{} + + config, err := k.GetConfigForEndpoint(cnsiRecord.APIEndpoint.String(), tokenRecord) + if err != nil { + return result, errors.New("Can not get Kubernetes config for specified endpoint") + } + + result.Config = config + + kubeConfig, err := k.GetKubeConfigForEndpoint(cnsiRecord.APIEndpoint.String(), tokenRecord) + if err != nil { + return result, errors.New("Can not get Kubernetes config for specified endpoint") + } + + kubeClient, err := kubernetes.NewForConfig(config) + if err != nil { + log.Error("Could not get kube client") + return result, err + } + + result.Namespace = namespace + result.ClientSet = kubeClient + + // Create the secret + secretSpec := &v1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: namespace, + }, + Type: "Opaque", + } + + secretSpec.Data = make(map[string][]byte) + secretSpec.Data["kubeconfig"] = []byte(kubeConfig) + secretSpec.Data["history"] = []byte(history) + + // Get Helm repository script if we have Helm repositories + helmSetup := getHelmRepoSetupScript(k.portalProxy) + if len(helmSetup) > 0 { + secretSpec.Data["helm-setup"] = []byte(helmSetup) + } + + secretsClient := kubeClient.CoreV1().Secrets(namespace) + _, err = secretsClient.Create(secretSpec) + + if err != nil { + log.Warn("Unable to create Secret") + return result, err + } + + result.SecretClient = secretsClient + result.SecretName = secretName + + podClient := kubeClient.CoreV1().Pods(namespace) + + podSpec := &v1.Pod{ + TypeMeta: metav1.TypeMeta{ + Kind: "pod", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + Namespace: namespace, + }, + } + + podSpec.ObjectMeta.Annotations = make(map[string]string) + + // Record the session ID + session, err :=k.portalProxy.GetSession(c) + if err == nil { + podSpec.ObjectMeta.Annotations["stratos-session"] = session.ID + log.Infof("Session ID: %s", session.ID) + } + + automount := false + + podSpec.Spec.AutomountServiceAccountToken = &automount + podSpec.Spec.RestartPolicy = "Never" + + volumeMountsSpec := make([]v1.VolumeMount, 1) + volumeMountsSpec[0].Name = "kubeconfig" + volumeMountsSpec[0].MountPath = "/root/.stratos" + volumeMountsSpec[0].ReadOnly = true + + containerSpec := make([]v1.Container, 1) + containerSpec[0].Name = consoleContainerName + containerSpec[0].Image = "nwmac/kubeconsole" + containerSpec[0].ImagePullPolicy = "Always" + containerSpec[0].VolumeMounts = volumeMountsSpec + podSpec.Spec.Containers = containerSpec + + volumesSpec := make([]v1.Volume, 1) + volumesSpec[0].Name = "kubeconfig" + volumesSpec[0].Secret = &v1.SecretVolumeSource{ + SecretName: secretName, + } + podSpec.Spec.Volumes = volumesSpec + + // Create a new pod + pod, err := podClient.Create(podSpec) + if err != nil { + return result, err + } + + result.PodClient = podClient + result.PodName = podName + + sendProgressMessage(ws, "Waiting for Kubernetes Console to start up ...") + + statusOptions := metav1.GetOptions{} + + // Wait for the pod to be running + ready := false + for { + status, err := podClient.Get(pod.Name, statusOptions) + + if err != nil { + break + } + + //log.Info(status.Status.Phase) + if status.Status.Phase == "Running" { + ready = true + } + + if ready { + break + } + + // Sleep + time.Sleep(2500 * time.Millisecond) + } + + return result, err +} + +func (k *KubernetesSpecification) cleanupPodAndSecret(podData *PodCreationData) error { + + if len(podData.PodName) > 0 { + captureBashHistory(podData) + podData.PodClient.Delete(podData.PodName, nil) + } + + if len(podData.SecretName) > 0 { + podData.SecretClient.Delete(podData.SecretName, nil) + } + + return nil +} + +func getHelmRepoSetupScript(portalProxy interfaces.PortalProxy) string { + + str := "" + + // Get all of the helm endpoints + endpoints, err := portalProxy.ListEndpoints() + if err != nil { + log.Error("Can not list Helm Repository endpoints") + return str + } + + for _, ep := range endpoints { + if ep.CNSIType == "helm" { + str += fmt.Sprintf("helm repo add %s %s > /dev/null\n", ep.Name, ep.APIEndpoint) + } + } + + return str +} + +func sendProgressMessage(ws *websocket.Conn, progressMsg string) { + // Send a message to say that we are creating the pod + msg := fmt.Sprintf("\033]2;%s\007", progressMsg) + bytes := fmt.Sprintf("% x\n", []byte(msg)) + if err := ws.WriteMessage(websocket.TextMessage, []byte(bytes)); err != nil { + log.Error("Could not send message to client to indicate console is starting") + } +} + +func captureBashHistory(podData *PodCreationData) error { + + log.Warn("**** Trying to capture bash history from pod ****") + + // Can we capture the history? + + // returning stdout, stderr and error. `options` allowed for + // additional parameters to be passed. + + //cmd := []string{"bash", "-c", "\"cat $HISTFILE\""} + cmd := []string{"cat", "/root/.bash_history"} + + + req := podData.ClientSet.Core().RESTClient().Post(). + Resource("pods"). + Name(podData.PodName). + Namespace(podData.Namespace). + SubResource("exec"). + Param("container", consoleContainerName) + + req.VersionedParams(&v1.PodExecOptions{ + Container: consoleContainerName, + Command: cmd, + Stdin: false, + Stdout: true, + Stderr: false, + TTY: true, + }, scheme.ParameterCodec) + + var stdout bytes.Buffer + //err := execute("POST", req.URL(), nil, nil, &stdout, nil, false) + + exec, err := remotecommand.NewSPDYExecutor(podData.Config, "POST", req.URL()) + if err != nil { + log.Error("Could not exec") + log.Error(err) + return err + } + + log.Warn("Attempting stream ......") + + err = exec.Stream(remotecommand.StreamOptions{ + //SupportedProtocols: remotecommandserver.SupportedStreamingProtocols, + Stdin: nil, + Stdout: &stdout, + Stderr: nil, + Tty: true, + }) + + log.Error("Get Bash History") + log.Error(err) + log.Error(stdout.String()) + + history = stdout.String() + + // if options.PreserveWhitespace { + // return stdout.String(), stderr.String(), err + // } + // return strings.TrimSpace(stdout.String()), strings.TrimSpace(stderr.String()), err + + return nil +} \ No newline at end of file diff --git a/src/jetstream/plugins/kubernetes/main.go b/src/jetstream/plugins/kubernetes/main.go index 736ddfa35d..a61c0e7548 100644 --- a/src/jetstream/plugins/kubernetes/main.go +++ b/src/jetstream/plugins/kubernetes/main.go @@ -112,6 +112,9 @@ func (c *KubernetesSpecification) AddSessionGroupRoutes(echoGroup *echo.Group) { echoGroup.GET("/kubedash/ui/:guid/*", c.kubeDashboardProxy) echoGroup.GET("/kubedash/:guid/status", c.kubeDashboardStatus) + // Kube Console + echoGroup.GET("/kubeconsole/:guid", c.KubeConsole) + // Helm Routes echoGroup.GET("/helm/releases", c.ListReleases) echoGroup.POST("/helm/install", c.InstallRelease) From a697fbef2f9193fe534af0863af4fa1c4cb14beb Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 17 Jul 2019 08:49:42 +0100 Subject: [PATCH 047/648] Update eksdeployment.md --- docs/eksdeployment.md | 75 ++----------------------------------------- 1 file changed, 2 insertions(+), 73 deletions(-) diff --git a/docs/eksdeployment.md b/docs/eksdeployment.md index cbf1eccf4e..74399f12d9 100644 --- a/docs/eksdeployment.md +++ b/docs/eksdeployment.md @@ -1,4 +1,4 @@ -# Stratos, SCF and metrics on EKS +# Stratos on EKS ## EKS Setup @@ -46,27 +46,7 @@ $ helm init --service-account tiller ## Storage Classes -Two storage classes are required, one general storage class that will be marked as the `default` storage class in the cluster, and a storage class that is scoped to a single `AZ`. This scoped storage class will be used to deploy Stratos and Metrics, that make use of shared storage volumes. - -To setup the `default` storage class save the following to a file `default_storage_class.yaml` -``` -apiVersion: storage.k8s.io/v1 -kind: StorageClass -metadata: - name: gp2 - annotations: - storageclass.kubernetes.io/is-default-class: "true" - labels: - kubernetes.io/cluster-service: "true" -provisioner: kubernetes.io/aws-ebs -parameters: - type: gp2 -``` - -To create the storage class, execute the following: -``` -$ kubectl apply -f default_storage_class.yaml -``` +Stratos requires a storage class that is scoped to a single `AZ`. To setup the scoped storage class, save the following to `scoped_storage_class.yaml`. ``` @@ -88,57 +68,6 @@ $ kubectl apply -f scoped_storage_class.yaml ``` In this guide, the scoped storage class will be referred to as `gp2scoped`. -## SCF Installation - -To deploy SCF, follow instructions detailed in https://github.com/SUSE/scf/wiki/Deployment-on-Amazon-EKS, starting from the [Deploy Cloud Foundry](https://github.com/SUSE/scf/wiki/Deployment-on-Amazon-EKS#deploy-cloud-foundry) section. - -The initial steps can be skipped, (changing storage volume size, helm and storage class configuration). - -The SCF config values file, will be referred to as `scf-config-values.yaml` in the guide. - - -## Metrics Installation - -Download the latest metrics chart from [here](). Or use the latest published `devel` release. - -Save the following configuration overrides to a file named `metrics-values.yaml`. - -``` -useLb: true -kubernetes: - authEndpoint: < EKS Cluster endpoint > -prometheus: - kubeStateMetrics: - enabled: true - server: - persistentVolume: - storageClass: gp2scoped -kube: - external_metrics_port: 443 - storage_class: - persistent: "gp2scoped" - shared: "gp2scoped" -``` -Deploy `metrics` to your cluster: - -``` - -$ helm install stratos/metrics --namespace metrics --name metrics -f metrics-values.yaml -f scf-config-values.yaml -``` - -Once all pods are `Ready`, retrieve the load balancer address for the `metrics` Endpoint. - -``` -$ kubectl get services --namespace metrics -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -metrics-f-exp-service ClusterIP 10.100.219.223 9186/TCP 43m -metrics-metrics-nginx LoadBalancer 10.100.31.139 a98a9c6f3d5e111e8b51d0eeeba70c15-724925591.us-east-1.elb.amazonaws.com,172.31.34.100 443:30943/TCP 43m -metrics-prometheus-kube-state-metrics ClusterIP None 80/TCP 43m -prometheus-service ClusterIP 10.100.32.254 9090/TCP 43m - -``` - -The address in this example is `https://a98a9c6f3d5e111e8b51d0eeeofba70c15-724925591.us-east-1.elb.amazonaws.com`. Make a note it. ## Stratos Deployment From c8d6667253494d7743fd3f97fad50f81e467a0ff Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 16 Aug 2019 15:10:29 +0100 Subject: [PATCH 048/648] Fix merge issue, update go.mod --- src/jetstream/datastore/datastore.go | 8 ++++---- src/jetstream/go.mod | 5 ----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/jetstream/datastore/datastore.go b/src/jetstream/datastore/datastore.go index ba8ed35199..26c2917bcc 100644 --- a/src/jetstream/datastore/datastore.go +++ b/src/jetstream/datastore/datastore.go @@ -4,6 +4,7 @@ import ( "database/sql" "fmt" "os" + "path" "regexp" "strings" "time" @@ -147,16 +148,15 @@ func GetConnection(dc DatabaseConfig, env *env.VarSet) (*sql.DB, error) { } // SQL Lite - return GetSQLLiteConnection(env.MustBool("SQLITE_KEEP_DB")) + return GetSQLLiteConnection(env.MustBool("SQLITE_KEEP_DB"), env.String("SQLITE_DB_DIR", ".")) } // GetSQLLiteConnection returns an SQLite DB Connection -func GetSQLLiteConnection(sqliteKeepDB bool) (*sql.DB, error) { +func GetSQLLiteConnection(sqliteKeepDB bool, sqlDbDir string) (*sql.DB, error) { - sqlDbDir := env.String("SQLITE_DB_DIR", ".") dbFilePath := path.Join(sqlDbDir, SQLiteDatabaseFile) log.Infof("SQLite Database file: %s", dbFilePath) - + return GetSQLLiteConnectionWithPath(SQLiteDatabaseFile, sqliteKeepDB) } diff --git a/src/jetstream/go.mod b/src/jetstream/go.mod index 35963b7546..991aaba261 100644 --- a/src/jetstream/go.mod +++ b/src/jetstream/go.mod @@ -25,13 +25,11 @@ require ( github.com/cloudfoundry/noaa v2.1.0+incompatible github.com/cloudfoundry/sonde-go v0.0.0-20171206171820-b33733203bb4 github.com/cppforlife/go-patch v0.2.0 // indirect - github.com/cyphar/filepath-securejoin v0.2.2 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect github.com/domodwyer/mailyak v3.1.1+incompatible github.com/dsnet/compress v0.0.0-20171208185109-cc9eb1d7ad76 // indirect github.com/fatih/color v1.7.0 // indirect github.com/go-sql-driver/mysql v1.4.1 - github.com/gogo/protobuf v1.2.1 // indirect github.com/golang/snappy v0.0.1 // indirect github.com/google/go-querystring v1.0.0 // indirect github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e // indirect @@ -44,7 +42,6 @@ require ( github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/kat-co/vala v0.0.0-20170210184112-42e1d8b61f12 github.com/kubeapps/common v0.0.0-20181107174310-61d8eb6f11b4 - github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28 // indirect github.com/labstack/echo v3.3.10+incompatible github.com/lib/pq v1.1.0 // indirect github.com/lunixbochs/vtclean v1.0.0 // indirect @@ -56,7 +53,6 @@ require ( github.com/nwaples/rardecode v1.0.0 // indirect github.com/nwmac/sqlitestore v0.0.0-20180824125213-7d2ab221fb3f github.com/pierrec/lz4 v2.0.5+incompatible // indirect - github.com/pkg/errors v0.8.1 // indirect github.com/satori/go.uuid v1.2.0 github.com/sirupsen/logrus v1.3.0 github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3 // indirect @@ -64,7 +60,6 @@ require ( github.com/ulikunitz/xz v0.5.6 // indirect github.com/vito/go-interact v0.0.0-20171111012221-fa338ed9e9ec // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect - github.com/ziutek/mymysql v1.5.4 // indirect golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f golang.org/x/net v0.0.0-20190420063019-afa5a82059c6 // indirect google.golang.org/appengine v1.5.0 // indirect From e4981be13f2e40f5be08f2ad133d0fec53e670e7 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Sat, 17 Aug 2019 17:57:59 +0100 Subject: [PATCH 049/648] Unit test fix --- .../chart-details-usage.component.spec.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.spec.ts b/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.spec.ts index abf3851288..fd707c5964 100644 --- a/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.spec.ts +++ b/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.spec.ts @@ -1,8 +1,10 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; - import { BaseTestModulesNoShared } from '../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { ChartDetailsUsageComponent } from './chart-details-usage.component'; +import { EndpointsService } from '../../../../../core/endpoints.service'; +import { UtilsService } from '../../../../../core/utils.service'; +import { PaginationMonitorFactory } from '../../../../../shared/monitors/pagination-monitor.factory'; describe('Component: ChartDetailsUsage', () => { beforeEach(() => { @@ -10,7 +12,9 @@ describe('Component: ChartDetailsUsage', () => { imports: [...BaseTestModulesNoShared], declarations: [ChartDetailsUsageComponent], providers: [ - + EndpointsService, + UtilsService, + PaginationMonitorFactory ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); From 8c2e316c9705418c3c24cc0e7409a3f90bd0760d Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Mon, 19 Aug 2019 10:33:51 +0100 Subject: [PATCH 050/648] Fix test imports so they run with customizations --- .../edit-autoscaler-policy-service.spec.ts | 4 ++-- ...app-autoscaler-events-config.service.spec.ts | 17 +++++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-service.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-service.spec.ts index a2ce67df14..fd3f67b2ef 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-service.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-service.spec.ts @@ -1,7 +1,7 @@ import { inject, TestBed } from '@angular/core/testing'; import { EntityServiceFactory } from '../../../../core/src/core/entity-service-factory.service'; -import { ApplicationsModule } from '../../../../core/src/features/applications/applications.module'; +import { SharedModule } from '../../../../core/src/shared/shared.module'; import { createBasicStoreModule } from '../../../../core/test-framework/store-test-helper'; import { CfAutoscalerTestingModule } from '../../cf-autoscaler-testing.module'; import { EditAutoscalerPolicyService } from './edit-autoscaler-policy-service'; @@ -14,7 +14,7 @@ describe('EditAutoscalerPolicyService', () => { EntityServiceFactory, ], imports: [ - ApplicationsModule, + SharedModule, createBasicStoreModule(), CfAutoscalerTestingModule ] diff --git a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-config.service.spec.ts b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-config.service.spec.ts index 651ce2e852..79b0826927 100644 --- a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-config.service.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-config.service.spec.ts @@ -1,21 +1,22 @@ -import { CommonModule } from '@angular/common'; import { inject, TestBed } from '@angular/core/testing'; import { ConnectionBackend, Http } from '@angular/http'; import { MockBackend } from '@angular/http/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { CoreModule } from '../../../../../core/src/core/core.module'; import { EntityServiceFactory } from '../../../../../core/src/core/entity-service-factory.service'; -import { ApplicationsModule } from '../../../../../core/src/features/applications/applications.module'; -import { SharedModule } from '../../../../../core/src/shared/shared.module'; import { generateTestApplicationServiceProvider } from '../../../../../core/test-framework/application-service-helper'; import { generateTestEntityServiceProvider } from '../../../../../core/test-framework/entity-service.helper'; -import { createBasicStoreModule, getInitialTestStoreState } from '../../../../../core/test-framework/store-test-helper'; +import { getInitialTestStoreState, createBasicStoreModule } from '../../../../../core/test-framework/store-test-helper'; import { GetApplication } from '../../../../../store/src/actions/application.actions'; import { applicationSchemaKey, entityFactory } from '../../../../../store/src/helpers/entity-factory'; import { endpointStoreNames } from '../../../../../store/src/types/endpoint.types'; import { CfAutoscalerTestingModule } from '../../../cf-autoscaler-testing.module'; import { CfAppAutoscalerEventsConfigService } from './cf-app-autoscaler-events-config.service'; +import { CommonModule, DatePipe } from '@angular/common'; +import { CoreModule } from '../../../../../core/src/core/core.module'; +import { SharedModule } from '../../../../../core/src/shared/shared.module'; +import { + ApplicationEnvVarsHelper +} from '../../../../../core/src/features/applications/application/application-tabs-base/tabs/build-tab/application-env-vars.service'; describe('CfAppAutoscalerEventsConfigService', () => { const initialState = getInitialTestStoreState(); @@ -25,6 +26,8 @@ describe('CfAppAutoscalerEventsConfigService', () => { beforeEach(() => { TestBed.configureTestingModule({ providers: [ + ApplicationEnvVarsHelper, + DatePipe, CfAppAutoscalerEventsConfigService, EntityServiceFactory, generateTestEntityServiceProvider( @@ -40,9 +43,7 @@ describe('CfAppAutoscalerEventsConfigService', () => { CommonModule, CoreModule, SharedModule, - ApplicationsModule, createBasicStoreModule(), - RouterTestingModule, CfAutoscalerTestingModule ] }); From f846a3def0b39373dbcb8d525f16935d55b46e6d Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Mon, 19 Aug 2019 22:15:55 +0100 Subject: [PATCH 051/648] Bump product version to 1.5 --- custom-src/stratos.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom-src/stratos.yaml b/custom-src/stratos.yaml index 91aded8cec..f24554a8d5 100644 --- a/custom-src/stratos.yaml +++ b/custom-src/stratos.yaml @@ -1,2 +1,2 @@ title: SUSE CAP -productVersion: 1.4 +productVersion: 1.5 From 19ac7493dd730d18efd5121e4226feec9fee849a Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 20 Aug 2019 11:09:50 +0100 Subject: [PATCH 052/648] Fix intermittent unit test --- .../chart-index/chart-index.component.spec.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/custom-src/frontend/app/custom/helm/monocular/chart-index/chart-index.component.spec.ts b/custom-src/frontend/app/custom/helm/monocular/chart-index/chart-index.component.spec.ts index 418098dd09..d881c083f6 100644 --- a/custom-src/frontend/app/custom/helm/monocular/chart-index/chart-index.component.spec.ts +++ b/custom-src/frontend/app/custom/helm/monocular/chart-index/chart-index.component.spec.ts @@ -2,6 +2,7 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; +import { of as observableOf } from 'rxjs'; import { ChartItemComponent } from '../chart-item/chart-item.component'; import { ChartListComponent } from '../chart-list/chart-list.component'; @@ -11,11 +12,19 @@ import { ChartsService } from '../shared/services/charts.service'; import { ConfigService } from '../shared/services/config.service'; import { MenuService } from '../shared/services/menu.service'; import { ChartIndexComponent } from './chart-index.component'; +import { GetMonocularCharts } from '../../store/helm.actions'; // import { HeaderBarComponent } from '../header-bar/header-bar.component'; // import { MainHeaderComponent } from '../main-header/main-header.component'; // import { SeoService } from '../shared/services/seo.service'; +export class MockChartService { + + public getCharts(){ + return observableOf([]); + } +} + describe('Component: ChartIndex', () => { beforeEach(() => { TestBed.configureTestingModule({ @@ -32,7 +41,7 @@ describe('Component: ChartIndex', () => { providers: [ ConfigService, MenuService, - { provide: ChartsService }, + { provide: ChartsService, useValue: new MockChartService()}, // { provide: SeoService }, { provide: Router } ], From 9e493c396b9cfd40665bc35f53c5d3b4ffc6f350 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 20 Aug 2019 12:43:33 +0100 Subject: [PATCH 053/648] Fix lint issue --- .../helm/monocular/chart-index/chart-index.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom-src/frontend/app/custom/helm/monocular/chart-index/chart-index.component.spec.ts b/custom-src/frontend/app/custom/helm/monocular/chart-index/chart-index.component.spec.ts index d881c083f6..60db292697 100644 --- a/custom-src/frontend/app/custom/helm/monocular/chart-index/chart-index.component.spec.ts +++ b/custom-src/frontend/app/custom/helm/monocular/chart-index/chart-index.component.spec.ts @@ -20,7 +20,7 @@ import { GetMonocularCharts } from '../../store/helm.actions'; export class MockChartService { - public getCharts(){ + public getCharts() { return observableOf([]); } } From 2049fe89b665a52671f2ab2d72ee206308874bd7 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 20 Aug 2019 13:11:18 +0100 Subject: [PATCH 054/648] Fix missing icons on Kubernetes --- .../monocular-tab-base/monocular-tab-base.component.ts | 8 ++++---- .../helm-release-tab-base.component.ts | 10 +++++----- .../kubernetes/helm-release/helm-release.component.ts | 4 ++-- .../kubernetes-namespace.component.ts | 4 ++-- .../kubernetes-node/kubernetes-node.component.ts | 6 +++--- .../kubernetes-tab-base.component.ts | 10 +++++----- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/custom-src/frontend/app/custom/helm/monocular-tab-base/monocular-tab-base.component.ts b/custom-src/frontend/app/custom/helm/monocular-tab-base/monocular-tab-base.component.ts index 16c7bee04a..67993ea19f 100644 --- a/custom-src/frontend/app/custom/helm/monocular-tab-base/monocular-tab-base.component.ts +++ b/custom-src/frontend/app/custom/helm/monocular-tab-base/monocular-tab-base.component.ts @@ -8,10 +8,10 @@ import { Component } from '@angular/core'; export class MonocularTabBaseComponent { tabLinks = [ - { link: 'charts', label: 'Charts', matIcon: 'folder_open' }, - { link: 'repos', label: 'Repositories', matIcon: 'products', matIconFont: 'stratos-icons' }, - { link: 'releases', label: 'Releases', matIcon: 'apps' }, - { link: 'config', label: 'Config', matIcon: 'build' }, + { link: 'charts', label: 'Charts', icon: 'folder_open' }, + { link: 'repos', label: 'Repositories', icon: 'products', iconFont: 'stratos-icons' }, + { link: 'releases', label: 'Releases', icon: 'apps' }, + { link: 'config', label: 'Config', icon: 'build' }, ]; } diff --git a/custom-src/frontend/app/custom/helm/release/helm-release-tab-base/helm-release-tab-base.component.ts b/custom-src/frontend/app/custom/helm/release/helm-release-tab-base/helm-release-tab-base.component.ts index 1f62b30a07..e5063a381f 100644 --- a/custom-src/frontend/app/custom/helm/release/helm-release-tab-base/helm-release-tab-base.component.ts +++ b/custom-src/frontend/app/custom/helm/release/helm-release-tab-base/helm-release-tab-base.component.ts @@ -36,11 +36,11 @@ export class HelmReleaseTabBaseComponent { public title = ''; tabLinks = [ - { link: 'summary', label: 'Summary', matIcon: 'helm', matIconFont: 'stratos-icons' }, - { link: 'notes', label: 'Notes', matIcon: 'subject' }, - { link: 'values', label: 'Values', matIcon: 'list' }, - { link: 'pods', label: 'Pods', matIcon: 'adjust' }, - { link: 'services', label: 'Services', matIcon: 'service', matIconFont: 'stratos-icons' } + { link: 'summary', label: 'Summary', icon: 'helm', iconFont: 'stratos-icons' }, + { link: 'notes', label: 'Notes', icon: 'subject' }, + { link: 'values', label: 'Values', icon: 'list' }, + { link: 'pods', label: 'Pods', icon: 'adjust' }, + { link: 'services', label: 'Services', icon: 'service', iconFont: 'stratos-icons' } ]; constructor( private helmRelease: HelmReleaseGuid, diff --git a/custom-src/frontend/app/custom/kubernetes/helm-release/helm-release.component.ts b/custom-src/frontend/app/custom/kubernetes/helm-release/helm-release.component.ts index d7a20e8472..b0734a0099 100644 --- a/custom-src/frontend/app/custom/kubernetes/helm-release/helm-release.component.ts +++ b/custom-src/frontend/app/custom/kubernetes/helm-release/helm-release.component.ts @@ -32,8 +32,8 @@ import { map } from 'rxjs/operators'; export class HelmReleaseComponent implements OnInit { public tabLinks = [ - { link: 'pods', label: 'Pods', matIcon: 'adjust' }, - { link: 'services', label: 'Services', matIcon: 'service', matIconFont: 'stratos-icons' }, + { link: 'pods', label: 'Pods', icon: 'adjust' }, + { link: 'services', label: 'Services', icon: 'service', iconFont: 'stratos-icons' }, ]; public breadcrumbs$: Observable; diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-namespace/kubernetes-namespace.component.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-namespace/kubernetes-namespace.component.ts index c5556dd390..ebe43a1a09 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes-namespace/kubernetes-namespace.component.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-namespace/kubernetes-namespace.component.ts @@ -33,8 +33,8 @@ import { KubernetesService } from '../services/kubernetes.service'; export class KubernetesNamespaceComponent { tabLinks = [ - { link: 'pods', label: 'Pods', matIcon: 'adjust' }, - { link: 'services', label: 'Services', matIcon: 'service', matIconFont: 'stratos-icons' } + { link: 'pods', label: 'Pods', icon: 'adjust' }, + { link: 'services', label: 'Services', icon: 'service', iconFont: 'stratos-icons' } ]; public breadcrumbs$: Observable; diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node.component.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node.component.ts index 46fa48f6fa..572a86da86 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node.component.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node.component.ts @@ -34,9 +34,9 @@ import { KubernetesService } from '../services/kubernetes.service'; export class KubernetesNodeComponent { tabLinks = [ - { link: 'summary', label: 'Summary', matIcon: 'kubernetes', matIconFont: 'stratos-icons' }, - { link: 'metrics', label: 'Metrics', matIcon: 'equalizer' }, - { link: 'pods', label: 'Pods', matIcon: 'adjust' }, + { link: 'summary', label: 'Summary', icon: 'kubernetes', iconFont: 'stratos-icons' }, + { link: 'metrics', label: 'Metrics', icon: 'equalizer' }, + { link: 'pods', label: 'Pods', icon: 'adjust' }, ]; public breadcrumbs$: Observable; diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.ts index fc2bb549de..c954603b71 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.ts @@ -30,11 +30,11 @@ import { UserFavoriteEndpoint } from '../../../../../store/src/types/user-favori export class KubernetesTabBaseComponent implements OnInit { tabLinks = [ - { link: 'summary', label: 'Summary', matIcon: 'kubernetes', matIconFont: 'stratos-icons' }, - { link: 'nodes', label: 'Nodes', matIcon: 'developer_board' }, - { link: 'namespaces', label: 'Namespaces', matIcon: 'language' }, - { link: 'pods', label: 'Pods', matIcon: 'adjust' }, - { link: 'apps', label: 'Applications', matIcon: 'apps' } + { link: 'summary', label: 'Summary', icon: 'kubernetes', iconFont: 'stratos-icons' }, + { link: 'nodes', label: 'Nodes', icon: 'developer_board' }, + { link: 'namespaces', label: 'Namespaces', icon: 'language' }, + { link: 'pods', label: 'Pods', icon: 'adjust' }, + { link: 'apps', label: 'Applications', icon: 'apps' } ]; isFetching$: Observable; From 0444983631f423d1aab47a14a567a7981a191658 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 20 Aug 2019 13:55:33 +0100 Subject: [PATCH 055/648] Unit test fix --- .../chart-index/chart-index.component.spec.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/custom-src/frontend/app/custom/helm/monocular/chart-index/chart-index.component.spec.ts b/custom-src/frontend/app/custom/helm/monocular/chart-index/chart-index.component.spec.ts index 418098dd09..60db292697 100644 --- a/custom-src/frontend/app/custom/helm/monocular/chart-index/chart-index.component.spec.ts +++ b/custom-src/frontend/app/custom/helm/monocular/chart-index/chart-index.component.spec.ts @@ -2,6 +2,7 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; +import { of as observableOf } from 'rxjs'; import { ChartItemComponent } from '../chart-item/chart-item.component'; import { ChartListComponent } from '../chart-list/chart-list.component'; @@ -11,11 +12,19 @@ import { ChartsService } from '../shared/services/charts.service'; import { ConfigService } from '../shared/services/config.service'; import { MenuService } from '../shared/services/menu.service'; import { ChartIndexComponent } from './chart-index.component'; +import { GetMonocularCharts } from '../../store/helm.actions'; // import { HeaderBarComponent } from '../header-bar/header-bar.component'; // import { MainHeaderComponent } from '../main-header/main-header.component'; // import { SeoService } from '../shared/services/seo.service'; +export class MockChartService { + + public getCharts() { + return observableOf([]); + } +} + describe('Component: ChartIndex', () => { beforeEach(() => { TestBed.configureTestingModule({ @@ -32,7 +41,7 @@ describe('Component: ChartIndex', () => { providers: [ ConfigService, MenuService, - { provide: ChartsService }, + { provide: ChartsService, useValue: new MockChartService()}, // { provide: SeoService }, { provide: Router } ], From bdf750adc1a7e144a923186ac6d53b1e65df7c9e Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 21 Aug 2019 12:42:55 +0100 Subject: [PATCH 056/648] Make unit test more reliable --- .../chart-details-info.component.spec.ts | 5 +- .../chart-details-readme.component.spec.ts | 6 +- .../chart-details.component.spec.ts | 8 +- .../chart-details/chart-details.component.ts | 27 +++-- .../chart-index/chart-index.component.spec.ts | 10 +- .../monocular/charts/charts.component.spec.ts | 3 +- .../shared/services/chart.service.mock.ts | 114 ++++++++++++++++++ 7 files changed, 146 insertions(+), 27 deletions(-) create mode 100644 custom-src/frontend/app/custom/helm/monocular/shared/services/chart.service.mock.ts diff --git a/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.spec.ts b/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.spec.ts index debefd2f28..ee32fad538 100644 --- a/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.spec.ts +++ b/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.spec.ts @@ -3,6 +3,7 @@ import { async, TestBed } from '@angular/core/testing'; import { ChartsService } from '../../shared/services/charts.service'; import { ChartDetailsInfoComponent } from './chart-details-info.component'; +import { MockChartService } from '../../shared/services/chart.service.mock'; describe('Component: ChartDetailsInfo', () => { @@ -11,7 +12,9 @@ describe('Component: ChartDetailsInfo', () => { TestBed.configureTestingModule({ declarations: [ChartDetailsInfoComponent], imports: [], - providers: [{ provide: ChartsService }], + providers: [ + { provide: ChartsService, useValue: new MockChartService()}, + ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); }) diff --git a/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.spec.ts b/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.spec.ts index d0a4a1abdb..132e3f1d9c 100644 --- a/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.spec.ts +++ b/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.spec.ts @@ -3,14 +3,16 @@ import { TestBed } from '@angular/core/testing'; import { ChartsService } from '../../shared/services/charts.service'; import { ChartDetailsReadmeComponent } from './chart-details-readme.component'; - +import { MockChartService } from '../../shared/services/chart.service.mock'; describe('Component: ChartDetailsReadme', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [], declarations: [ChartDetailsReadmeComponent], - providers: [{ provide: ChartsService }], + providers: [ + { provide: ChartsService, useValue: new MockChartService()}, + ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); }); diff --git a/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details.component.spec.ts b/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details.component.spec.ts index ba98324bf2..2d834a6316 100644 --- a/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details.component.spec.ts +++ b/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details.component.spec.ts @@ -9,6 +9,7 @@ import { BaseTestModulesNoShared } from '../../../../../test-framework/cloud-fou import { EntitySummaryTitleComponent, } from '../../../../shared/components/entity-summary-title/entity-summary-title.component'; +import { PaginationMonitorFactory } from '../../../../shared/monitors/pagination-monitor.factory'; import { ChartItemComponent } from '../chart-item/chart-item.component'; import { ListItemComponent } from '../list-item/list-item.component'; import { LoaderComponent } from '../loader/loader.component'; @@ -21,6 +22,8 @@ import { ChartDetailsReadmeComponent } from './chart-details-readme/chart-detail import { ChartDetailsUsageComponent } from './chart-details-usage/chart-details-usage.component'; import { ChartDetailsVersionsComponent } from './chart-details-versions/chart-details-versions.component'; import { ChartDetailsComponent } from './chart-details.component'; +import { MockChartService } from '../shared/services/chart.service.mock'; +import { HttpClientModule } from '@angular/common/http'; /* tslint:disable:no-unused-variable */ // import { Angulartics2Module } from 'angulartics2'; @@ -89,6 +92,7 @@ describe('ChartDetailsComponent', () => { // Angulartics2Module, RouterTestingModule, HttpModule, + HttpClientModule, ...BaseTestModulesNoShared ], declarations: [ @@ -106,11 +110,11 @@ describe('ChartDetailsComponent', () => { EntitySummaryTitleComponent ], providers: [ - { provide: ChartsService }, + { provide: ChartsService, useValue: new MockChartService()}, { provide: ConfigService, useValue: { appName: 'appName' } }, // { provide: SeoService }, { provide: MenuService }, - ChartsService + PaginationMonitorFactory, ] }).compileComponents(); }) diff --git a/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details.component.ts b/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details.component.ts index 6d705ff79b..6e0d22df33 100644 --- a/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details.component.ts +++ b/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details.component.ts @@ -30,18 +30,21 @@ export class ChartDetailsComponent implements OnInit { this.route.params.forEach((params: Params) => { const repo = params.repo; const chartName = params.chartName; - this.chartsService.getChart(repo, chartName).pipe(first()).subscribe(chart => { - this.loading = false; - this.chart = chart; - const version = params.version || this.chart.relationships.latestChartVersion.data.version; - this.chartsService.getVersion(repo, chartName, version).pipe(first()) - .subscribe(chartVersion => { - this.currentVersion = chartVersion; - this.titleVersion = this.currentVersion.attributes.app_version || ''; - this.updateMetaTags(); - }); - this.iconUrl = this.chartsService.getChartIconURL(this.chart); - }); + + if (!!chartName) { + this.chartsService.getChart(repo, chartName).pipe(first()).subscribe(chart => { + this.loading = false; + this.chart = chart; + const version = params.version || this.chart.relationships.latestChartVersion.data.version; + this.chartsService.getVersion(repo, chartName, version).pipe(first()) + .subscribe(chartVersion => { + this.currentVersion = chartVersion; + this.titleVersion = this.currentVersion.attributes.app_version || ''; + this.updateMetaTags(); + }); + this.iconUrl = this.chartsService.getChartIconURL(this.chart); + }); + } }); } diff --git a/custom-src/frontend/app/custom/helm/monocular/chart-index/chart-index.component.spec.ts b/custom-src/frontend/app/custom/helm/monocular/chart-index/chart-index.component.spec.ts index 60db292697..e19b0089eb 100644 --- a/custom-src/frontend/app/custom/helm/monocular/chart-index/chart-index.component.spec.ts +++ b/custom-src/frontend/app/custom/helm/monocular/chart-index/chart-index.component.spec.ts @@ -2,7 +2,6 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; -import { of as observableOf } from 'rxjs'; import { ChartItemComponent } from '../chart-item/chart-item.component'; import { ChartListComponent } from '../chart-list/chart-list.component'; @@ -12,19 +11,12 @@ import { ChartsService } from '../shared/services/charts.service'; import { ConfigService } from '../shared/services/config.service'; import { MenuService } from '../shared/services/menu.service'; import { ChartIndexComponent } from './chart-index.component'; -import { GetMonocularCharts } from '../../store/helm.actions'; +import { MockChartService } from '../shared/services/chart.service.mock'; // import { HeaderBarComponent } from '../header-bar/header-bar.component'; // import { MainHeaderComponent } from '../main-header/main-header.component'; // import { SeoService } from '../shared/services/seo.service'; -export class MockChartService { - - public getCharts() { - return observableOf([]); - } -} - describe('Component: ChartIndex', () => { beforeEach(() => { TestBed.configureTestingModule({ diff --git a/custom-src/frontend/app/custom/helm/monocular/charts/charts.component.spec.ts b/custom-src/frontend/app/custom/helm/monocular/charts/charts.component.spec.ts index f90e98215a..ae3fcdd923 100644 --- a/custom-src/frontend/app/custom/helm/monocular/charts/charts.component.spec.ts +++ b/custom-src/frontend/app/custom/helm/monocular/charts/charts.component.spec.ts @@ -14,6 +14,7 @@ import { ConfigService } from '../shared/services/config.service'; import { MenuService } from '../shared/services/menu.service'; import { ReposService } from '../shared/services/repos.service'; import { ChartsComponent } from './charts.component'; +import { MockChartService } from '../shared/services/chart.service.mock'; // import { HeaderBarComponent } from '../header-bar/header-bar.component'; // import { SeoService } from '../shared/services/seo.service'; @@ -35,7 +36,7 @@ describe('Component: Charts', () => { providers: [ ConfigService, MenuService, - { provide: ChartsService }, + { provide: ChartsService, useValue: new MockChartService()}, // { provide: SeoService }, { provide: ActivatedRoute }, { provide: Router }, diff --git a/custom-src/frontend/app/custom/helm/monocular/shared/services/chart.service.mock.ts b/custom-src/frontend/app/custom/helm/monocular/shared/services/chart.service.mock.ts new file mode 100644 index 0000000000..09e1a7f191 --- /dev/null +++ b/custom-src/frontend/app/custom/helm/monocular/shared/services/chart.service.mock.ts @@ -0,0 +1,114 @@ +import { Observable, of as observableOf } from 'rxjs'; +import { Chart } from '../models/chart'; +import { ChartVersion } from '../models/chart-version'; + + +const mockChart: Chart = { + id: 'incubator/test', + type: 'chart', + links: [], + attributes: { + description: 'Testing the chart', + home: 'helm.sh', + keywords: ['artifactory'], + maintainers: [ + { + email: 'test@example.com', + name: 'Test' + } + ], + name: 'test', + repo: { + name: 'incubator', + url: 'test' + }, + icon: 'icon', + sources: ['https://github.com/'] + }, + relationships: { + latestChartVersion: { + data: { + app_version: '1.0', + created: new Date('2017-02-13T04:33:57.218083521Z'), + digest: + 'eba0c51d4bc5b88d84f83d8b2ba0c5e5a3aad8bc19875598198bdbb0b675f683', + icons: [ + { + name: '160x160-fit', + path: '/assets/incubator/test/4.16.0/logo-160x160-fit.png' + } + ], + readme: '/assets/incubator/test/4.16.0/README.md', + urls: [ + 'https://kubernetes-charts-incubator.storage.googleapis.com/test-4.16.0.tgz' + ], + version: '4.16.0' + }, + links: { + self: '/v1/charts/incubator/test/versions/4.16.0' + } + } + } +}; + +const mockChartVersion: ChartVersion = { + id: 'incubator/test', + type: 'chart', + attributes: { + app_version: '1.0', + created: new Date('2017-02-13T04:33:57.218083521Z'), + digest: + 'eba0c51d4bc5b88d84f83d8b2ba0c5e5a3aad8bc19875598198bdbb0b675f683', + icons: [ + { + name: '160x160-fit', + path: '/assets/incubator/test/4.16.0/logo-160x160-fit.png' + } + ], + readme: '/assets/incubator/test/4.16.0/README.md', + urls: [ + 'https://kubernetes-charts-incubator.storage.googleapis.com/test-4.16.0.tgz' + ], + version: '4.16.0' + }, + relationships: { + chart: { + data: mockChart.attributes, + links: { + self: '/v1/charts/incubator/test/versions/4.16.0' + } + } + } +}; + +export class MockChartService { + + public getCharts() { + return observableOf([]); + } + + public getChart(): Observable { + return observableOf(mockChart); + } + + public getChartReadme(): Observable { + return observableOf({} as Response); + } + + public getVersions(): Observable { + return observableOf([]); + } + + public getVersion(): Observable { + return observableOf(mockChartVersion); + } + + public searchCharts(): Observable { + return observableOf([]); + } + + public getChartIconURL() { + return null; + } + +} From bb72b416e5b97a3cce2c8f0afe53f672576a6a8a Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 22 Aug 2019 11:52:14 +0100 Subject: [PATCH 057/648] Disable Helm feature --- custom-src/frontend/app/custom/custom.module.ts | 11 +++++++---- deploy/ci/tasks/dev-releases/check-gh-release.yml | 1 - src/jetstream/plugins/monocular/main.go | 14 ++++++++------ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/custom-src/frontend/app/custom/custom.module.ts b/custom-src/frontend/app/custom/custom.module.ts index e0dfea55d4..ca20c1a326 100644 --- a/custom-src/frontend/app/custom/custom.module.ts +++ b/custom-src/frontend/app/custom/custom.module.ts @@ -14,8 +14,8 @@ import { KubernetesSetupModule } from './kubernetes/kubernetes.setup.module'; import { KubeHealthCheck } from './kubernetes/store/kubernetes.actions'; import { SuseAboutInfoComponent } from './suse-about-info/suse-about-info.component'; import { SuseLoginComponent } from './suse-login/suse-login.component'; -import { HelmModule } from './helm/helm.module'; -import { HelmSetupModule } from './helm/helm.setup.module'; +// import { HelmModule } from './helm/helm.module'; +// import { HelmSetupModule } from './helm/helm.setup.module'; const SuseCustomizations: CustomizationsMetadata = { copyright: '© 2019 SUSE', @@ -29,8 +29,11 @@ const SuseCustomizations: CustomizationsMetadata = { SharedModule, MDAppModule, KubernetesSetupModule, - HelmModule, - HelmSetupModule + // #150 - Uncomment to enable helm plugin + // --------------------------------------- + // HelmModule, + // HelmSetupModule + // --------------------------------------- ], declarations: [ SuseLoginComponent, diff --git a/deploy/ci/tasks/dev-releases/check-gh-release.yml b/deploy/ci/tasks/dev-releases/check-gh-release.yml index 55ad3f92f3..84529c8419 100644 --- a/deploy/ci/tasks/dev-releases/check-gh-release.yml +++ b/deploy/ci/tasks/dev-releases/check-gh-release.yml @@ -3,7 +3,6 @@ platform: linux inputs: - name: stratos - name: image-tag -- name: aio-docker-image image_resource: type: docker-image source: diff --git a/src/jetstream/plugins/monocular/main.go b/src/jetstream/plugins/monocular/main.go index 2336033fa0..6035925740 100644 --- a/src/jetstream/plugins/monocular/main.go +++ b/src/jetstream/plugins/monocular/main.go @@ -44,12 +44,14 @@ func (m *Monocular) GetChartStore() chartsvc.ChartSvcDatastore { // Init performs plugin initialization func (m *Monocular) Init() error { - - m.ConfigureSQL() - m.chartSvcRoutes = chartsvc.GetRoutes() - m.InitSync() - m.syncOnStartup() - return nil + return errors.New("Manually disabled") + // #150 - Uncomment to enable helm plugin + // --------------------------------------- + // m.ConfigureSQL() + // m.chartSvcRoutes = chartsvc.GetRoutes() + // m.InitSync() + // m.syncOnStartup() + // return nil } func (m *Monocular) syncOnStartup() { From d3f798dab53a27a8f9414245efa1d4b7cd519dcf Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 22 Aug 2019 21:40:19 +0100 Subject: [PATCH 058/648] Gate Helm feature on tech preview and always hide CF nav items --- .../frontend/app/custom/custom.module.ts | 12 +++++------ .../app/custom/helm/helm.store.module.ts | 3 ++- src/jetstream/plugins/monocular/main.go | 20 ++++++++----------- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/custom-src/frontend/app/custom/custom.module.ts b/custom-src/frontend/app/custom/custom.module.ts index cbcc12110c..bc74c5f846 100644 --- a/custom-src/frontend/app/custom/custom.module.ts +++ b/custom-src/frontend/app/custom/custom.module.ts @@ -14,11 +14,14 @@ import { KubernetesSetupModule } from './kubernetes/kubernetes.setup.module'; import { KubeHealthCheck } from './kubernetes/store/kubernetes.actions'; import { SuseAboutInfoComponent } from './suse-about-info/suse-about-info.component'; import { SuseLoginComponent } from './suse-login/suse-login.component'; +import { HelmModule } from './helm/helm.module'; +import { HelmSetupModule } from './helm/helm.setup.module'; const SuseCustomizations: CustomizationsMetadata = { copyright: '© 2019 SUSE', hasEula: true, - aboutInfoComponent: SuseAboutInfoComponent + aboutInfoComponent: SuseAboutInfoComponent, + alwaysShowNavForEndpointTypes: (typ) => false, }; @NgModule({ @@ -27,11 +30,8 @@ const SuseCustomizations: CustomizationsMetadata = { SharedModule, MDAppModule, KubernetesSetupModule, - // #150 - Uncomment to enable helm plugin - // --------------------------------------- - // HelmModule, - // HelmSetupModule - // --------------------------------------- + HelmModule, + HelmSetupModule ], declarations: [ SuseLoginComponent, diff --git a/custom-src/frontend/app/custom/helm/helm.store.module.ts b/custom-src/frontend/app/custom/helm/helm.store.module.ts index 0453fb0a20..520d8577d8 100644 --- a/custom-src/frontend/app/custom/helm/helm.store.module.ts +++ b/custom-src/frontend/app/custom/helm/helm.store.module.ts @@ -14,7 +14,8 @@ const helmEndpointTypes: EndpointTypeExtensionConfig[] = [{ imagePath: '/core/assets/custom/helm.svg', homeLink: (guid) => ['/monocular/repos', guid], entitySchemaKeys: monocularEntityKeys, - doesNotSupportConnect: true + doesNotSupportConnect: true, + techPreview: true, }]; @StratosExtension({ diff --git a/src/jetstream/plugins/monocular/main.go b/src/jetstream/plugins/monocular/main.go index a834164206..20913e5b25 100644 --- a/src/jetstream/plugins/monocular/main.go +++ b/src/jetstream/plugins/monocular/main.go @@ -45,19 +45,15 @@ func (m *Monocular) GetChartStore() chartsvc.ChartSvcDatastore { // Init performs plugin initialization func (m *Monocular) Init() error { - return errors.New("Manually disabled") - // #150 - Uncomment to enable helm plugin - // --------------------------------------- - // m.ConfigureSQL() - - // m.chartSvcRoutes = chartsvc.GetRoutes() - - // m.InitSync() - - // m.syncOnStartup() + if !m.portalProxy.GetConfig().EnableTechPreview { + return errors.New("Feature is in Tech Preview") + } - // return nil - // --------------------------------------- + m.ConfigureSQL() + m.chartSvcRoutes = chartsvc.GetRoutes() + m.InitSync() + m.syncOnStartup() + return nil } func (m *Monocular) syncOnStartup() { From c69960e82ab5a4fb54862af5a87a68a3dc8bc3b2 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 22 Aug 2019 21:42:39 +0100 Subject: [PATCH 059/648] force a change --- src/jetstream/plugins/monocular/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/jetstream/plugins/monocular/main.go b/src/jetstream/plugins/monocular/main.go index 6035925740..f07a4175c4 100644 --- a/src/jetstream/plugins/monocular/main.go +++ b/src/jetstream/plugins/monocular/main.go @@ -45,6 +45,7 @@ func (m *Monocular) GetChartStore() chartsvc.ChartSvcDatastore { // Init performs plugin initialization func (m *Monocular) Init() error { return errors.New("Manually disabled") + // #150 - Uncomment to enable helm plugin // --------------------------------------- // m.ConfigureSQL() From 75ec74f585db6f026f8544057ac2fe071754abc0 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 22 Aug 2019 21:47:52 +0100 Subject: [PATCH 060/648] Enable kube dashboard with tech preview mode --- src/jetstream/plugins/kubernetes/main.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/jetstream/plugins/kubernetes/main.go b/src/jetstream/plugins/kubernetes/main.go index b8b0598ed5..5588b3b172 100644 --- a/src/jetstream/plugins/kubernetes/main.go +++ b/src/jetstream/plugins/kubernetes/main.go @@ -2,6 +2,7 @@ package kubernetes import ( "net/url" + "strconv" "strings" "errors" @@ -143,7 +144,8 @@ func (c *KubernetesSpecification) Init() error { UserInfo: c.GetGKEUserFromToken, }) - c.portalProxy.GetConfig().PluginConfig[KubeDashboardPluginConfigSetting] = "false" + // Kube dashboard is enabled by Tech Preview mode + c.portalProxy.GetConfig().PluginConfig[KubeDashboardPluginConfigSetting] = strconv.FormatBool(c.portalProxy.GetConfig().EnableTechPreview) return nil } From c6789b6bd8a6422483f1b7c2e7283a5d2e93713f Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 23 Aug 2019 10:07:26 +0100 Subject: [PATCH 061/648] Fix casing of auth name for Kube Config file --- src/jetstream/plugins/kubernetes/auth/kubeconfig.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jetstream/plugins/kubernetes/auth/kubeconfig.go b/src/jetstream/plugins/kubernetes/auth/kubeconfig.go index 0af53ac0b5..da607b926c 100644 --- a/src/jetstream/plugins/kubernetes/auth/kubeconfig.go +++ b/src/jetstream/plugins/kubernetes/auth/kubeconfig.go @@ -4,7 +4,7 @@ import ( "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" ) -const AuthConnectTypeKubeConfig = "KubeConfig" +const AuthConnectTypeKubeConfig = "kubeconfig" // KubeConfigAuth is same as OIDC with different name type KubeConfigAuth struct { From b2fc9c399bcaa8143add8517a2dee5372d80ff6f Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 23 Aug 2019 22:05:54 +0100 Subject: [PATCH 062/648] Add support for connecting CaaSP V4 --- src/jetstream/auth.go | 7 +- src/jetstream/passthrough.go | 2 +- .../plugins/kubernetes/auth/kubeconfig.go | 98 ++++++++++++++++++- src/jetstream/plugins/kubernetes/auth/oidc.go | 9 +- .../plugins/kubernetes/config/kube_config.go | 57 +++++++++-- src/jetstream/plugins/kubernetes/main.go | 1 + 6 files changed, 156 insertions(+), 18 deletions(-) diff --git a/src/jetstream/auth.go b/src/jetstream/auth.go index 71c7f63e11..46ab9e47c7 100644 --- a/src/jetstream/auth.go +++ b/src/jetstream/auth.go @@ -1,7 +1,6 @@ package main import ( - "crypto/rand" "encoding/base64" "encoding/json" @@ -20,8 +19,8 @@ import ( "github.com/labstack/echo" "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" - "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/tokens" "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/localusers" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/tokens" "golang.org/x/crypto/bcrypt" ) @@ -343,7 +342,7 @@ func (p *portalProxy) doLocalLogin(c echo.Context) (string, string, error) { username := c.FormValue("username") password := c.FormValue("password") - guid := c.FormValue("guid") + guid := c.FormValue("guid") if len(username) == 0 || len(password) == 0 || len(guid) == 0 { return guid, username, errors.New("Needs username, password and guid") @@ -1249,7 +1248,7 @@ func (p *portalProxy) GetCNSIUserFromOAuthToken(cnsiGUID string, cfTokenRecord * } func (p *portalProxy) DoAuthFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request, authHandler interfaces.AuthHandlerFunc) (*http.Response, error) { - + log.Debug("DoAuthFlowRequest") // get a cnsi token record and a cnsi record tokenRec, cnsi, err := p.getCNSIRequestRecords(cnsiRequest) if err != nil { diff --git a/src/jetstream/passthrough.go b/src/jetstream/passthrough.go index ea6499475f..dde1df041c 100644 --- a/src/jetstream/passthrough.go +++ b/src/jetstream/passthrough.go @@ -365,7 +365,7 @@ func (p *portalProxy) SendProxiedResponse(c echo.Context, responses map[string]* } func (p *portalProxy) doRequest(cnsiRequest *interfaces.CNSIRequest, done chan<- *interfaces.CNSIRequest) { - log.Debug("doRequest") + log.Debugf("doRequest for URL: %s", cnsiRequest.URL.String()) var body io.Reader var res *http.Response var req *http.Request diff --git a/src/jetstream/plugins/kubernetes/auth/kubeconfig.go b/src/jetstream/plugins/kubernetes/auth/kubeconfig.go index 0af53ac0b5..2adce58f80 100644 --- a/src/jetstream/plugins/kubernetes/auth/kubeconfig.go +++ b/src/jetstream/plugins/kubernetes/auth/kubeconfig.go @@ -1,10 +1,21 @@ package auth import ( + "encoding/base64" + "encoding/json" + "fmt" + "io/ioutil" + "time" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/config" "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" + "github.com/labstack/echo" + log "github.com/sirupsen/logrus" ) -const AuthConnectTypeKubeConfig = "KubeConfig" +const AuthConnectTypeKubeConfig = "kubeconfig" + +// KubeConfigAuth will look at the kube config file and use the appropriate auth provider // KubeConfigAuth is same as OIDC with different name type KubeConfigAuth struct { @@ -20,6 +31,86 @@ func (c *KubeConfigAuth) GetName() string { return AuthConnectTypeKubeConfig } +func (c *KubeConfigAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { + log.Debug("FetchToken (KubeConfigAuth)") + + req := ec.Request() + + // Need to extract the parameters from the request body + defer req.Body.Close() + body, err := ioutil.ReadAll(req.Body) + if err != nil { + return nil, nil, err + } + + kubeConfig, err := config.ParseKubeConfig(body) + kubeConfigUser, err := kubeConfig.GetUserForCluster(cnsiRecord.APIEndpoint.String()) + if err != nil { + return nil, nil, fmt.Errorf("Unable to find cluster in kubeconfig") + } + + // OIDC ? == CaaSP V3 + if kubeConfigUser.User.AuthProvider.Name == "oidc" { + return c.GetTokenFromKubeConfigUser(cnsiRecord, kubeConfigUser) + } + + // Check for Certificate == CaaSP V4 + if len(kubeConfigUser.User.ClientCertificate) > 0 && len(kubeConfigUser.User.ClientKeyData) > 0 { + return c.GetCertAuth(cnsiRecord, kubeConfigUser) + } + + // Check for Token == CaaSP V4 + if len(kubeConfigUser.User.Token) > 0 { + tokenRecord := c.portalProxy.InitEndpointTokenRecord(getLargeExpiryTime(), kubeConfigUser.User.Token, "__NONE__", false) + tokenRecord.AuthType = interfaces.AuthTypeOIDC + + oauthMetadata := &interfaces.OAuth2Metadata{} + jsonString, err := json.Marshal(oauthMetadata) + if err == nil { + tokenRecord.Metadata = string(jsonString) + } + + // Could try and make a K8S Api call to validate the token + // Or, maybe we can verify the access token with the auth URL ? + return &tokenRecord, &cnsiRecord, nil + } + + return nil, nil, fmt.Errorf("OIDC: Unsupported authentication provider for user: %s", kubeConfigUser.User.AuthProvider.Name) +} + +func (c *KubeConfigAuth) GetCertAuth(cnsiRecord interfaces.CNSIRecord, user *config.KubeConfigUser) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { + + kubeCertAuth := &KubeCertificate{} + + cert, err := base64.StdEncoding.DecodeString(user.User.ClientCertificate) + if err != nil { + return nil, nil, err + } + certKey, err := base64.StdEncoding.DecodeString(user.User.ClientKeyData) + if err != nil { + return nil, nil, err + } + + kubeCertAuth.Certificate = string(cert) + kubeCertAuth.CertificateKey = string(certKey) + + jsonString, err := kubeCertAuth.GetJSON() + if err != nil { + return nil, nil, err + } + + // Refresh token isn't required since the AccessToken will never expire + refreshToken := jsonString + + accessToken := jsonString + + // Tokens lasts forever + disconnected := false + tokenRecord := c.portalProxy.InitEndpointTokenRecord(getLargeExpiryTime(), accessToken, refreshToken, disconnected) + tokenRecord.AuthType = authConnectTypeCertAuth + return &tokenRecord, &cnsiRecord, nil +} + func (c *KubeConfigAuth) RegisterJetstreamAuthType(portal interfaces.PortalProxy) { // Register auth type with Jetstream c.portalProxy.AddAuthProvider(c.GetName(), interfaces.AuthProvider{ @@ -27,3 +118,8 @@ func (c *KubeConfigAuth) RegisterJetstreamAuthType(portal interfaces.PortalProxy UserInfo: nil, }) } + +func getLargeExpiryTime() int64 { + expiry := time.Now().Local().Add(time.Hour * time.Duration(100000)) + return expiry.Unix() +} diff --git a/src/jetstream/plugins/kubernetes/auth/oidc.go b/src/jetstream/plugins/kubernetes/auth/oidc.go index 70518c16b0..81bdf0000b 100644 --- a/src/jetstream/plugins/kubernetes/auth/oidc.go +++ b/src/jetstream/plugins/kubernetes/auth/oidc.go @@ -88,9 +88,14 @@ func (c *OIDCKubeAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Cont // We only support OIDC auth provider at the moment if kubeConfigUser.User.AuthProvider.Name != "oidc" { - return nil, nil, errors.New("Unsupported authentication provider") + return nil, nil, fmt.Errorf("OIDC: Unsupported authentication provider for user: %s", kubeConfigUser.User.AuthProvider.Name) } + return c.GetTokenFromKubeConfigUser(cnsiRecord, kubeConfigUser) +} + +func (c *OIDCKubeAuth) GetTokenFromKubeConfigUser(cnsiRecord interfaces.CNSIRecord, kubeConfigUser *config.KubeConfigUser) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { + oidcConfig, err := c.GetOIDCConfig(kubeConfigUser) if err != nil { log.Info(err) @@ -142,7 +147,7 @@ func (c *OIDCKubeAuth) GetOIDCConfig(k *config.KubeConfigUser) (*KubeConfigAuthP expiry, ok := token.Claims().Expiration() if !ok { - return nil, errors.New("Can not get Acces Token expiry time") + return nil, errors.New("Can not get Access Token expiry time") } OIDCConfig.Expiry = expiry diff --git a/src/jetstream/plugins/kubernetes/config/kube_config.go b/src/jetstream/plugins/kubernetes/config/kube_config.go index 3a9ab7419b..e86de033cc 100644 --- a/src/jetstream/plugins/kubernetes/config/kube_config.go +++ b/src/jetstream/plugins/kubernetes/config/kube_config.go @@ -8,7 +8,6 @@ import ( "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces/config" "gopkg.in/yaml.v2" - ) type KubeConfigClusterDetail struct { @@ -38,6 +37,7 @@ type KubeConfigUser struct { //ExtraScopes string `yaml:"extra-scopes"` type KubeConfigContexts struct { + Name string `yaml:"name"` Context struct { Cluster string User string @@ -45,11 +45,12 @@ type KubeConfigContexts struct { } type KubeConfigFile struct { - ApiVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` - Clusters []KubeConfigCluster `yaml:"clusters"` - Users []KubeConfigUser `yaml:"users"` - Contexts []KubeConfigContexts `yaml:"contexts"` + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Clusters []KubeConfigCluster `yaml:"clusters"` + Users []KubeConfigUser `yaml:"users"` + Contexts []KubeConfigContexts `yaml:"contexts"` + CurrentContext string `yaml:"current-context"` } func (k *KubeConfigFile) GetClusterByAPIEndpoint(endpoint string) (*KubeConfigCluster, error) { @@ -61,6 +62,15 @@ func (k *KubeConfigFile) GetClusterByAPIEndpoint(endpoint string) (*KubeConfigCl return nil, fmt.Errorf("Unable to find cluster") } +func (k *KubeConfigFile) GetClusterByName(name string) (*KubeConfigCluster, error) { + for _, cluster := range k.Clusters { + if cluster.Name == name { + return &cluster, nil + } + } + return nil, fmt.Errorf("Unable to find cluster") +} + func (k *KubeConfigFile) GetClusterContext(clusterName string) (*KubeConfigContexts, error) { for _, context := range k.Contexts { if context.Context.Cluster == clusterName { @@ -70,6 +80,15 @@ func (k *KubeConfigFile) GetClusterContext(clusterName string) (*KubeConfigConte return nil, fmt.Errorf("Unable to find context") } +func (k *KubeConfigFile) GetContext(contextName string) (*KubeConfigContexts, error) { + for _, context := range k.Contexts { + if context.Name == contextName { + return &context, nil + } + } + return nil, fmt.Errorf("Unable to find context") +} + func (k *KubeConfigFile) GetUser(userName string) (*KubeConfigUser, error) { for _, user := range k.Users { if user.Name == userName { @@ -80,14 +99,32 @@ func (k *KubeConfigFile) GetUser(userName string) (*KubeConfigUser, error) { } func (k *KubeConfigFile) GetUserForCluster(clusterEndpoint string) (*KubeConfigUser, error) { - cluster, err := k.GetClusterByAPIEndpoint(clusterEndpoint) - if err != nil { - return nil, errors.New("Unable to find cluster in kubeconfig") + var cluster *KubeConfigCluster + var err error + + // Check to see if the current-context is for this endpoint, before going a search through all contexts + if len(k.CurrentContext) > 0 { + currentContext, err := k.GetContext(k.CurrentContext) + if err == nil { + c, err := k.GetClusterByName(currentContext.Context.Cluster) + if err == nil { + if c.Cluster.Server == clusterEndpoint { + // Cluster refrences the same Kube API Server + cluster = c + } + } + } } - clusterName := cluster.Name + if cluster == nil { + cluster, err = k.GetClusterByAPIEndpoint(clusterEndpoint) + if err != nil { + return nil, errors.New("Unable to find cluster in kubeconfig") + } + } + clusterName := cluster.Name if clusterName == "" { return nil, errors.New("Unable to find cluster") } diff --git a/src/jetstream/plugins/kubernetes/main.go b/src/jetstream/plugins/kubernetes/main.go index 736ddfa35d..a52b77630d 100644 --- a/src/jetstream/plugins/kubernetes/main.go +++ b/src/jetstream/plugins/kubernetes/main.go @@ -56,6 +56,7 @@ func (c *KubernetesSpecification) Register(echoContext echo.Context) error { } func (c *KubernetesSpecification) Validate(userGUID string, cnsiRecord interfaces.CNSIRecord, tokenRecord interfaces.TokenRecord) error { + log.Debugf("Validating Kubernetes endpoint connection for user: %s", userGUID) response, err := c.portalProxy.DoProxySingleRequest(cnsiRecord.GUID, userGUID, "GET", "api/v1/pods?limit=1", nil, nil) if err != nil { return err From 82cf2c5e92ece2ff0290d90c9725dcac33d9c79c Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 27 Aug 2019 16:10:23 +0100 Subject: [PATCH 063/648] Fix typo --- .../app/custom/helm/create-release/create-release.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom-src/frontend/app/custom/helm/create-release/create-release.component.ts b/custom-src/frontend/app/custom/helm/create-release/create-release.component.ts index ba1999cdef..b9eb3b0f49 100644 --- a/custom-src/frontend/app/custom/helm/create-release/create-release.component.ts +++ b/custom-src/frontend/app/custom/helm/create-release/create-release.component.ts @@ -27,7 +27,7 @@ export class CreateReleaseComponent implements OnInit { // Confirmation dialog overwriteValuesConfirmation = new ConfirmationDialogConfig( 'Overwrite Values?', - 'Are you sure you want to replace your values with those from valuyes.yaml?', + 'Are you sure you want to replace your values with those from values.yaml?', 'Overwrite' ); From c55f6fb44374dd6190887fe7aae9d225150b1065 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 27 Aug 2019 16:11:34 +0100 Subject: [PATCH 064/648] Remove console.log --- .../app/custom/helm/create-release/create-release.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/custom-src/frontend/app/custom/helm/create-release/create-release.component.ts b/custom-src/frontend/app/custom/helm/create-release/create-release.component.ts index b9eb3b0f49..2f00845e97 100644 --- a/custom-src/frontend/app/custom/helm/create-release/create-release.component.ts +++ b/custom-src/frontend/app/custom/helm/create-release/create-release.component.ts @@ -103,7 +103,6 @@ export class CreateReleaseComponent implements OnInit { this.kubeEndpoints$.pipe( first(), tap(ep => { - console.log(ep); if (ep.length === 1) { this.details.controls.endpoint.setValue(ep[0].guid, {onlySelf: true}); setTimeout(() => { From c78dfdef3cf280b44c1f089356f6304963980b6f Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 28 Aug 2019 10:11:49 +0100 Subject: [PATCH 065/648] Fix bad merge issue --- src/jetstream/plugins/kubernetes/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jetstream/plugins/kubernetes/main.go b/src/jetstream/plugins/kubernetes/main.go index ed4bb07e60..ab12bfe713 100644 --- a/src/jetstream/plugins/kubernetes/main.go +++ b/src/jetstream/plugins/kubernetes/main.go @@ -97,8 +97,8 @@ func (c *KubernetesSpecification) Init() error { c.AddAuthProvider(auth.InitKubeConfigAuth(c.portalProxy)) // Kube dashboard is enabled by Tech Preview mode - c.portalProxy.GetConfig().PluginConfig[KubeDashboardPluginConfigSetting] = strconv.FormatBool(c.portalProxy.GetConfig().EnableTechPreview - + c.portalProxy.GetConfig().PluginConfig[KubeDashboardPluginConfigSetting] = strconv.FormatBool(c.portalProxy.GetConfig().EnableTechPreview) + return nil } From fdf2ba3f87b31d0e54ddb8a15059dfffd5a94ec2 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 28 Aug 2019 10:12:25 +0100 Subject: [PATCH 066/648] Fix bad merge issue --- src/jetstream/plugins/kubernetes/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jetstream/plugins/kubernetes/main.go b/src/jetstream/plugins/kubernetes/main.go index ab12bfe713..a6d7f13716 100644 --- a/src/jetstream/plugins/kubernetes/main.go +++ b/src/jetstream/plugins/kubernetes/main.go @@ -97,7 +97,7 @@ func (c *KubernetesSpecification) Init() error { c.AddAuthProvider(auth.InitKubeConfigAuth(c.portalProxy)) // Kube dashboard is enabled by Tech Preview mode - c.portalProxy.GetConfig().PluginConfig[KubeDashboardPluginConfigSetting] = strconv.FormatBool(c.portalProxy.GetConfig().EnableTechPreview) + c.portalProxy.GetConfig().PluginConfig[kubeDashboardPluginConfigSetting] = strconv.FormatBool(c.portalProxy.GetConfig().EnableTechPreview) return nil } From eaf9f1c87b2d8a3a3e5de04daa4bd35766545e31 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Wed, 28 Aug 2019 10:54:16 +0100 Subject: [PATCH 067/648] Fix test & compile --- .../create-release/create-release.component.ts | 14 +++++++------- .../chart-index/chart-index.component.spec.ts | 5 +++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/custom-src/frontend/app/custom/helm/create-release/create-release.component.ts b/custom-src/frontend/app/custom/helm/create-release/create-release.component.ts index 2f00845e97..5d5ee711ea 100644 --- a/custom-src/frontend/app/custom/helm/create-release/create-release.component.ts +++ b/custom-src/frontend/app/custom/helm/create-release/create-release.component.ts @@ -1,21 +1,21 @@ import { HttpClient } from '@angular/common/http'; -import { Component, ElementRef, ViewChild, OnInit } from '@angular/core'; +import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { MatTextareaAutosize } from '@angular/material'; import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; import { Observable, of, Subscription } from 'rxjs'; -import { delay, filter, map, pairwise, switchMap, first, tap } from 'rxjs/operators'; +import { delay, filter, first, map, pairwise, switchMap, tap } from 'rxjs/operators'; import { AppState } from '../../../../../store/src/app-state'; import { selectUpdateInfo } from '../../../../../store/src/selectors/api.selectors'; import { EndpointsService } from '../../../core/endpoints.service'; +import { ConfirmationDialogConfig } from '../../../shared/components/confirmation-dialog.config'; +import { ConfirmationDialogService } from '../../../shared/components/confirmation-dialog.service'; import { StepOnNextFunction } from '../../../shared/components/stepper/step/step.component'; import { HelmInstall } from '../store/helm.actions'; import { helmReleaseSchemaKey } from '../store/helm.entities'; import { HELM_INSTALLING_KEY, HelmInstallValues } from '../store/helm.types'; -import { ConfirmationDialogConfig } from '../../../shared/components/confirmation-dialog.config'; -import { ConfirmationDialogService } from '../../../shared/components/confirmation-dialog.service'; @Component({ selector: 'app-create-release', @@ -45,7 +45,7 @@ export class CreateReleaseComponent implements OnInit { @ViewChild('overridesYamlTextArea') overridesYamlTextArea: ElementRef; @ViewChild(MatTextareaAutosize) overridesYamlAutosize: MatTextareaAutosize; - private valuesYaml = ''; + public valuesYaml = ''; constructor( private route: ActivatedRoute, @@ -95,7 +95,7 @@ export class CreateReleaseComponent implements OnInit { } private replaceWithValuesYaml() { - this.overrides.controls.values.setValue(this.valuesYaml, {onlySelf: true}); + this.overrides.controls.values.setValue(this.valuesYaml, { onlySelf: true }); } ngOnInit() { @@ -104,7 +104,7 @@ export class CreateReleaseComponent implements OnInit { first(), tap(ep => { if (ep.length === 1) { - this.details.controls.endpoint.setValue(ep[0].guid, {onlySelf: true}); + this.details.controls.endpoint.setValue(ep[0].guid, { onlySelf: true }); setTimeout(() => { this.releaseNameInputField.nativeElement.focus(); }, 1); diff --git a/custom-src/frontend/app/custom/helm/monocular/chart-index/chart-index.component.spec.ts b/custom-src/frontend/app/custom/helm/monocular/chart-index/chart-index.component.spec.ts index 37e7a65aba..4561de7fbc 100644 --- a/custom-src/frontend/app/custom/helm/monocular/chart-index/chart-index.component.spec.ts +++ b/custom-src/frontend/app/custom/helm/monocular/chart-index/chart-index.component.spec.ts @@ -2,6 +2,7 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; +import { of } from 'rxjs'; import { ChartItemComponent } from '../chart-item/chart-item.component'; import { ChartListComponent } from '../chart-list/chart-list.component'; @@ -19,7 +20,7 @@ import { ChartIndexComponent } from './chart-index.component'; export class MockChartService { public getCharts() { - return observableOf([]); + return of([]); } } @@ -39,7 +40,7 @@ describe('Component: ChartIndex', () => { providers: [ ConfigService, MenuService, - { provide: ChartsService, useValue: new MockChartService()}, + { provide: ChartsService, useValue: new MockChartService() }, // { provide: SeoService }, { provide: Router } ], From 30165526586fe761f50735c7f9d8a886225d24b8 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Wed, 28 Aug 2019 10:56:57 +0100 Subject: [PATCH 068/648] Ensure helm endpoint cards have no opacity --- .../endpoint/endpoint-card/endpoint-card.component.html | 5 ++--- .../endpoint/endpoint-card/endpoint-card.component.ts | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.html b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.html index fff7475385..c0fdab6721 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.html +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.html @@ -1,9 +1,8 @@ + [ngClass]="{'endpoint-card--no-link': !endpointLink, 'endpoint-card--disconnected': connectionStatus === 'disconnected', 'endpoint-card--checking': connectionStatus === 'checking'}" + [favorite]="favorite" [actionMenu]="cardMenu" [status$]="cardStatus$" [statusIcon]="false" [statusBackground]="true">
    diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.ts b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.ts index 6359365f51..093ec05bc5 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.ts @@ -52,6 +52,7 @@ export class EndpointCardComponent extends CardCell implements On public endpointIds$: Observable; public cardStatus$: Observable; private subs: Subscription[] = []; + public connectionStatus: string; private componentRef: ComponentRef; @@ -76,7 +77,7 @@ export class EndpointCardComponent extends CardCell implements On this.endpointLink = row.connectionStatus === 'connected' || this.endpointConfig.doesNotSupportConnect ? EndpointsService.getLinkForEndpoint(row) : null; this.updateInnerComponent(); - + this.connectionStatus = this.endpointConfig.doesNotSupportConnect ? 'connected' : row.connectionStatus; } get row(): EndpointModel { return this.pRow; From d8a590f500583bf03b9a7e97126ccc20c25d2250 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Wed, 28 Aug 2019 13:21:38 +0100 Subject: [PATCH 069/648] Add health check for helm endpoint type When endpoint is syncing fetch endpoint data until sycing has finished --- custom-src/frontend/app/custom/custom.module.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/custom-src/frontend/app/custom/custom.module.ts b/custom-src/frontend/app/custom/custom.module.ts index ca20c1a326..f73017222f 100644 --- a/custom-src/frontend/app/custom/custom.module.ts +++ b/custom-src/frontend/app/custom/custom.module.ts @@ -2,6 +2,7 @@ import { NgModule } from '@angular/core'; import { Router } from '@angular/router'; import { Store } from '@ngrx/store'; +import { GetSystemInfo } from '../../../store/src/actions/system.actions'; import { AppState } from '../../../store/src/app-state'; import { EndpointHealthCheck } from '../../endpoints-health-checks'; import { CoreModule } from '../core/core.module'; @@ -14,8 +15,6 @@ import { KubernetesSetupModule } from './kubernetes/kubernetes.setup.module'; import { KubeHealthCheck } from './kubernetes/store/kubernetes.actions'; import { SuseAboutInfoComponent } from './suse-about-info/suse-about-info.component'; import { SuseLoginComponent } from './suse-login/suse-login.component'; -// import { HelmModule } from './helm/helm.module'; -// import { HelmSetupModule } from './helm/helm.setup.module'; const SuseCustomizations: CustomizationsMetadata = { copyright: '© 2019 SUSE', @@ -57,6 +56,13 @@ export class CustomModule { endpointService.registerHealthCheck( new EndpointHealthCheck('k8s', (endpoint) => store.dispatch(new KubeHealthCheck(endpoint.guid))) ); + endpointService.registerHealthCheck( + new EndpointHealthCheck('helm', (endpoint) => { + if (endpoint.endpoint_metadata && endpoint.endpoint_metadata.status === 'Synchronizing') { + store.dispatch(new GetSystemInfo()); + } + }) + ); // Only update the routes once if (!CustomModule.init) { // Override the component used for the login route From 64d772aefde060c652646809563eca6062117586 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Wed, 28 Aug 2019 14:31:10 +0100 Subject: [PATCH 070/648] Fix e2e tests --- .../cloud-foundry-list-cf-e2e.spec.ts | 13 ++-------- src/test-e2e/endpoints/endpoints-e2e.spec.ts | 25 +++---------------- src/test-e2e/po/side-nav.po.ts | 12 +++++++-- 3 files changed, 16 insertions(+), 34 deletions(-) diff --git a/src/test-e2e/cloud-foundry/cloud-foundry-list-cf-e2e.spec.ts b/src/test-e2e/cloud-foundry/cloud-foundry-list-cf-e2e.spec.ts index 68fb0bcc78..2d193d6eb6 100644 --- a/src/test-e2e/cloud-foundry/cloud-foundry-list-cf-e2e.spec.ts +++ b/src/test-e2e/cloud-foundry/cloud-foundry-list-cf-e2e.spec.ts @@ -15,17 +15,8 @@ describe('CF Endpoints Dashboard - ', () => { .clearAllEndpoints(); }); - beforeEach(() => { - nav.goto(SideNavMenuItem.CloudFoundry); - cloudFoundry.loadingIndicator.waitUntilNotShown(); - }); - - it('should be the Endpoints page', () => { - expect(cloudFoundry.isActivePage()).toBeTruthy(); - }); - - it('should show the `no registered endpoints` message', () => { - expect(cloudFoundry.hasNoCloudFoundryMessage).toBeTruthy(); + it('No CF side nav when no CF connected', () => { + expect(nav.isMenuItemPresent(SideNavMenuItem.CloudFoundry)).toBeFalsy(); }); }); diff --git a/src/test-e2e/endpoints/endpoints-e2e.spec.ts b/src/test-e2e/endpoints/endpoints-e2e.spec.ts index 486018cad5..4aa2dfbaff 100644 --- a/src/test-e2e/endpoints/endpoints-e2e.spec.ts +++ b/src/test-e2e/endpoints/endpoints-e2e.spec.ts @@ -1,18 +1,12 @@ -import { ApplicationsPage } from '../applications/applications.po'; -import { CfTopLevelPage } from '../cloud-foundry/cf-level/cf-top-level-page.po'; import { e2e } from '../e2e'; import { ConsoleUserType } from '../helpers/e2e-helpers'; import { MenuComponent } from '../po/menu.po'; import { SideNavMenuItem } from '../po/side-nav.po'; import { SnackBarPo } from '../po/snackbar.po'; -import { ServicesPage } from '../services/services.po'; import { EndpointsPage } from './endpoints.po'; describe('Endpoints', () => { const endpointsPage = new EndpointsPage(); - const applications = new ApplicationsPage(); - const services = new ServicesPage(); - const cloudFoundry = new CfTopLevelPage(); describe('Workflow on log in (admin/non-admin + no endpoints/some endpoints) -', () => { describe('As Admin -', () => { @@ -46,19 +40,8 @@ describe('Endpoints', () => { expect(endpointsPage.isActivePage()).toBeTruthy(); }); - it('Should show application wall with \'no clusters\' message', () => { - endpointsPage.sideNav.goto(SideNavMenuItem.Applications); - expect(applications.hasNoCloudFoundryMessage()).toBeTruthy(); - }); - - it('Should show services view with \'no clusters\' message', () => { - endpointsPage.sideNav.goto(SideNavMenuItem.Services); - expect(services.hasNoCloudFoundryMessage()).toBeTruthy(); - }); - - it('Should show Cloud Foundry view with \'no clusters\' message', () => { - endpointsPage.sideNav.goto(SideNavMenuItem.CloudFoundry); - expect(cloudFoundry.hasNoCloudFoundryMessage()).toBeTruthy(); + it('No CF side nav when no CF connected', () => { + expect(endpointsPage.sideNav.isMenuItemPresent(SideNavMenuItem.CloudFoundry)).toBeFalsy(); }); it('Welcome snackbar message should be displayed', () => { @@ -121,8 +104,8 @@ describe('Endpoints', () => { const menu = new MenuComponent(); menu.waitUntilShown(); menu.getItemMap().then(items => { - expect(items['connect']).toBeDefined(); - expect(items['disconnect']).not.toBeDefined(); + expect(items.connect).toBeDefined(); + expect(items.disconnect).not.toBeDefined(); }); return menu.close(); }); diff --git a/src/test-e2e/po/side-nav.po.ts b/src/test-e2e/po/side-nav.po.ts index 92ea249ebc..1e258f5509 100644 --- a/src/test-e2e/po/side-nav.po.ts +++ b/src/test-e2e/po/side-nav.po.ts @@ -1,4 +1,4 @@ -import { browser, by, element, promise } from 'protractor'; +import { browser, by, element, ElementFinder, promise } from 'protractor'; import { E2EHelpers } from '../helpers/e2e-helpers'; import { Component } from './component.po'; @@ -23,9 +23,17 @@ export class SideNavigation extends Component { // Goto the specified menu item goto(menuItem: SideNavMenuItem): promise.Promise { - return this.helpers.waitForElementAndClick(element(by.cssContainingText('.side-nav__item', menuItem))) + return this.helpers.waitForElementAndClick(this.getMenuItem(menuItem)) .then(() => browser.actions().mouseMove({ x: 500, y: 0 }).perform()) .then(() => browser.sleep(500)); } + isMenuItemPresent(menuItem: SideNavMenuItem) { + return this.getMenuItem(menuItem).isPresent(); + } + + getMenuItem(menuItem: SideNavMenuItem): ElementFinder { + return this.locator.element(by.cssContainingText('.side-nav__item', menuItem)); + } + } From 70c576994e8ab730b5786dd08fc208cc800bcabd Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Wed, 28 Aug 2019 15:00:48 +0100 Subject: [PATCH 071/648] Fix unit tests --- .../create-release.component.spec.ts | 19 ++++++++++++++++--- .../chart-details-info.component.spec.ts | 4 ++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/custom-src/frontend/app/custom/helm/create-release/create-release.component.spec.ts b/custom-src/frontend/app/custom/helm/create-release/create-release.component.spec.ts index 2d0492460b..c0e7e1b359 100644 --- a/custom-src/frontend/app/custom/helm/create-release/create-release.component.spec.ts +++ b/custom-src/frontend/app/custom/helm/create-release/create-release.component.spec.ts @@ -1,7 +1,8 @@ import { CommonModule } from '@angular/common'; import { HttpClientModule } from '@angular/common/http'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { ConnectionBackend, Http, HttpModule } from '@angular/http'; +import { ConnectionBackend, Http } from '@angular/http'; import { MockBackend } from '@angular/http/testing'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; @@ -10,6 +11,7 @@ import { StoreModule } from '@ngrx/store'; import { appReducers } from '../../../../../store/src/reducers.module'; import { TabNavService } from '../../../../tab-nav.service'; import { CoreModule } from '../../../core/core.module'; +import { ConfirmationDialogService } from '../../../shared/components/confirmation-dialog.service'; import { CreateApplicationStep1Component, } from '../../../shared/components/create-application/create-application-step1/create-application-step1.component'; @@ -25,6 +27,7 @@ import { CreateReleaseComponent } from './create-release.component'; describe('CreateReleaseComponent', () => { let component: CreateReleaseComponent; let fixture: ComponentFixture; + let httpMock: HttpTestingController; beforeEach(async(() => { TestBed.configureTestingModule({ @@ -36,7 +39,7 @@ describe('CreateReleaseComponent', () => { imports: [ CommonModule, CoreModule, - HttpModule, + HttpClientTestingModule, RouterTestingModule, BrowserAnimationsModule, PageHeaderModule, @@ -56,10 +59,14 @@ describe('CreateReleaseComponent', () => { EntityMonitorFactory, InternalEventMonitorFactory, CloudFoundryService, - TabNavService + TabNavService, + ConfirmationDialogService ] }) .compileComponents(); + + httpMock = TestBed.get(HttpTestingController); + })); beforeEach(() => { @@ -69,6 +76,12 @@ describe('CreateReleaseComponent', () => { }); it('should be created', () => { + httpMock.expectOne('/pp/v1/chartsvc/v1/assets/undefined/undefined/versions/undefined/values.yaml'); + expect(component).toBeTruthy(); }); + + afterEach(() => { + httpMock.verify(); + }); }); diff --git a/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.spec.ts b/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.spec.ts index ee32fad538..74d65fd3c7 100644 --- a/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.spec.ts +++ b/custom-src/frontend/app/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.spec.ts @@ -1,9 +1,9 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { async, TestBed } from '@angular/core/testing'; +import { MockChartService } from '../../shared/services/chart.service.mock'; import { ChartsService } from '../../shared/services/charts.service'; import { ChartDetailsInfoComponent } from './chart-details-info.component'; -import { MockChartService } from '../../shared/services/chart.service.mock'; describe('Component: ChartDetailsInfo', () => { @@ -13,7 +13,7 @@ describe('Component: ChartDetailsInfo', () => { declarations: [ChartDetailsInfoComponent], imports: [], providers: [ - { provide: ChartsService, useValue: new MockChartService()}, + { provide: ChartsService, useValue: new MockChartService() }, ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); From a86fee33b7d3a8ef2b9a7029c6ac882ff3cd0d5c Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 28 Aug 2019 11:25:14 +0100 Subject: [PATCH 072/648] Allow DB directory to be specified for SQLite --- src/jetstream/datastore/datastore.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jetstream/datastore/datastore.go b/src/jetstream/datastore/datastore.go index 26c2917bcc..d377039b88 100644 --- a/src/jetstream/datastore/datastore.go +++ b/src/jetstream/datastore/datastore.go @@ -147,7 +147,7 @@ func GetConnection(dc DatabaseConfig, env *env.VarSet) (*sql.DB, error) { } - // SQL Lite + // SQL Lite - SQLITE_DB_DIR env var allows directory for console db to be changed return GetSQLLiteConnection(env.MustBool("SQLITE_KEEP_DB"), env.String("SQLITE_DB_DIR", ".")) } @@ -157,7 +157,7 @@ func GetSQLLiteConnection(sqliteKeepDB bool, sqlDbDir string) (*sql.DB, error) { dbFilePath := path.Join(sqlDbDir, SQLiteDatabaseFile) log.Infof("SQLite Database file: %s", dbFilePath) - return GetSQLLiteConnectionWithPath(SQLiteDatabaseFile, sqliteKeepDB) + return GetSQLLiteConnectionWithPath(dbFilePath, sqliteKeepDB) } // GetSQLLiteConnectionWithPath returns an SQLite DB Connection From 9dd0511f97134ed57652ab8639abf7b91a77c1bc Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 29 Aug 2019 11:25:18 +0100 Subject: [PATCH 073/648] Add a custom welcome page for no endpoints --- .../frontend/app/custom/custom.module.ts | 4 ++ .../suse-welcome/suse-welcome.component.html | 31 +++++++++++++ .../suse-welcome/suse-welcome.component.scss | 45 +++++++++++++++++++ .../suse-welcome/suse-welcome.component.ts | 8 ++++ .../features/endpoints/endpoints.module.ts | 4 +- 5 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 custom-src/frontend/app/custom/suse-welcome/suse-welcome.component.html create mode 100644 custom-src/frontend/app/custom/suse-welcome/suse-welcome.component.scss create mode 100644 custom-src/frontend/app/custom/suse-welcome/suse-welcome.component.ts diff --git a/custom-src/frontend/app/custom/custom.module.ts b/custom-src/frontend/app/custom/custom.module.ts index a8d8ef9586..6f83726f5b 100644 --- a/custom-src/frontend/app/custom/custom.module.ts +++ b/custom-src/frontend/app/custom/custom.module.ts @@ -17,11 +17,13 @@ import { SuseAboutInfoComponent } from './suse-about-info/suse-about-info.compon import { SuseLoginComponent } from './suse-login/suse-login.component'; import { HelmModule } from './helm/helm.module'; import { HelmSetupModule } from './helm/helm.setup.module'; +import { SuseWelcomeComponent } from './suse-welcome/suse-welcome.component'; const SuseCustomizations: CustomizationsMetadata = { copyright: '© 2019 SUSE', hasEula: true, aboutInfoComponent: SuseAboutInfoComponent, + noEndpointsComponent: SuseWelcomeComponent, alwaysShowNavForEndpointTypes: (typ) => false, }; @@ -37,11 +39,13 @@ const SuseCustomizations: CustomizationsMetadata = { declarations: [ SuseLoginComponent, SuseAboutInfoComponent, + SuseWelcomeComponent, DemoHelperComponent ], entryComponents: [ SuseLoginComponent, SuseAboutInfoComponent, + SuseWelcomeComponent, DemoHelperComponent, ], providers: [ diff --git a/custom-src/frontend/app/custom/suse-welcome/suse-welcome.component.html b/custom-src/frontend/app/custom/suse-welcome/suse-welcome.component.html new file mode 100644 index 0000000000..b2a52c6faa --- /dev/null +++ b/custom-src/frontend/app/custom/suse-welcome/suse-welcome.component.html @@ -0,0 +1,31 @@ + + +
    + settings_ethernet +
    There are no registered endpoints
    +
    + + + + + +

    Getting Started

    +
      +
    • Start by registering endpoints, such as a Kubernetes Cluster or a Cloud Foundry. Use the add icon in the top-right
    • +
    • Once you have registered one or more endpoints, connect to them with your credentials
    • +
    • As you register and connect endpoints the side navigation bar will update to show the features appropriate to your endpoints
    • +
    • You can register and connect to multiple endpoints of any type
    • +
    +
    +
    + + +

    Tips

    +
      +
    • You can collapse and expand the side navigation bar using the menu icon in the top-left. This setting is stored in your browser and will persist across application reloads
    • +
    • You can favorite endpoints and entities within an endpoint using the star_border icon. These will appear for quick access on the Home page
    • +
    +
    +
    +
    +
    diff --git a/custom-src/frontend/app/custom/suse-welcome/suse-welcome.component.scss b/custom-src/frontend/app/custom/suse-welcome/suse-welcome.component.scss new file mode 100644 index 0000000000..ee42a17333 --- /dev/null +++ b/custom-src/frontend/app/custom/suse-welcome/suse-welcome.component.scss @@ -0,0 +1,45 @@ +@import '../../../sass/custom/suse-colors'; + +.no-endpoints-message { + color: $suse-button-gray; + display: flex; + flex-direction: row; + justify-content: center; + margin: 40px 0; + + &__icon { + font-size: 32px; + height: 32px; + width: 32px; + } + + &__text { + font-size: 24px; + margin-left: 12px; + } +} + +.messages { + + &__title { + color: $suse-primary; + font-size: 20px; + font-weight: normal; + margin: 20px 0; + } + + &__item { + font-size: 16px; + line-height: 22px; + margin-bottom: 12px; + + mat-icon { + background-color: $suse-text-gray; + font-size: 16px; + height: 16px; + margin: 0 4px; + vertical-align: middle; + width: 16px; + } + } +} \ No newline at end of file diff --git a/custom-src/frontend/app/custom/suse-welcome/suse-welcome.component.ts b/custom-src/frontend/app/custom/suse-welcome/suse-welcome.component.ts new file mode 100644 index 0000000000..dc04e71d03 --- /dev/null +++ b/custom-src/frontend/app/custom/suse-welcome/suse-welcome.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-suse-welcome', + templateUrl: './suse-welcome.component.html', + styleUrls: ['./suse-welcome.component.scss'], +}) +export class SuseWelcomeComponent {} diff --git a/src/frontend/packages/core/src/features/endpoints/endpoints.module.ts b/src/frontend/packages/core/src/features/endpoints/endpoints.module.ts index 3fafb30c7a..bf87b9653e 100644 --- a/src/frontend/packages/core/src/features/endpoints/endpoints.module.ts +++ b/src/frontend/packages/core/src/features/endpoints/endpoints.module.ts @@ -9,13 +9,15 @@ import { ConnectEndpointDialogComponent } from './connect-endpoint-dialog/connec import { CreateEndpointModule } from './create-endpoint/create-endpoint.module'; import { EndpointsPageComponent } from './endpoints-page/endpoints-page.component'; import { EndpointsRoutingModule } from './endpoints.routing'; +import { CustomImportModule } from '../../custom-import.module'; @NgModule({ imports: [ CoreModule, SharedModule, EndpointsRoutingModule, - CreateEndpointModule + CreateEndpointModule, + CustomImportModule ], declarations: [ EndpointsPageComponent, From f95c4cfe1c44029c066538b0647fe94edc56751a Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Thu, 29 Aug 2019 15:06:41 +0100 Subject: [PATCH 074/648] Don't include `Use the Endpoints view to register` hint in endpoints view --- .../endpoints/endpoints-page/endpoints-page.component.html | 5 +++-- .../endpoints-missing/endpoints-missing.component.html | 3 ++- .../endpoints-missing/endpoints-missing.component.ts | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.html b/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.html index 580ba39c65..6a80270ca3 100644 --- a/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.html +++ b/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.html @@ -9,6 +9,7 @@

    Endpoints

    - + - + \ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/endpoints-missing/endpoints-missing.component.html b/src/frontend/packages/core/src/shared/components/endpoints-missing/endpoints-missing.component.html index 057ce86b12..05e5bd6c48 100644 --- a/src/frontend/packages/core/src/shared/components/endpoints-missing/endpoints-missing.component.html +++ b/src/frontend/packages/core/src/shared/components/endpoints-missing/endpoints-missing.component.html @@ -1,3 +1,4 @@ + [firstLine]="message.firstLine" [secondLine]="showDirectToEndpointMessage ? message.secondLine : null" + [toolbarLink]="message.toolbarLink"> \ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/endpoints-missing/endpoints-missing.component.ts b/src/frontend/packages/core/src/shared/components/endpoints-missing/endpoints-missing.component.ts index ae53fd4bb0..b8625eff53 100644 --- a/src/frontend/packages/core/src/shared/components/endpoints-missing/endpoints-missing.component.ts +++ b/src/frontend/packages/core/src/shared/components/endpoints-missing/endpoints-missing.component.ts @@ -23,6 +23,7 @@ export interface EndpointMissingMessageParts { export class EndpointsMissingComponent implements AfterViewInit, OnDestroy, OnInit { @Input() showToolbarHint = true; + @Input() showDirectToEndpointMessage = true; noContent$: Observable; snackBarText = { From 2e416c5c75ad4cdf5504b0aadd09af201bb83635 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Thu, 29 Aug 2019 15:35:35 +0100 Subject: [PATCH 075/648] Fix case where we have registered/registered & connected endpoints --- .../endpoints-page.component.ts | 50 ++++++------------- 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.ts b/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.ts index d9e111b29d..2e36019a86 100644 --- a/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.ts @@ -1,14 +1,14 @@ import { Component, + ComponentFactory, + ComponentFactoryResolver, + ComponentRef, + Inject, NgZone, OnDestroy, OnInit, ViewChild, ViewContainerRef, - ComponentRef, - ComponentFactoryResolver, - Inject, - ComponentFactory } from '@angular/core'; import { Store } from '@ngrx/store'; import { Subscription } from 'rxjs'; @@ -18,18 +18,19 @@ import { RouterNav } from '../../../../../store/src/actions/router.actions'; import { AppState } from '../../../../../store/src/app-state'; import { selectDashboardState } from '../../../../../store/src/selectors/dashboard.selectors'; import { CurrentUserPermissions } from '../../../core/current-user-permissions.config'; +import { Customizations, CustomizationsMetadata } from '../../../core/customizations.types'; import { EndpointsService } from '../../../core/endpoints.service'; import { getActionsFromExtensions, StratosActionMetadata, StratosActionType, } from '../../../core/extension/extension-service'; +import { safeUnsubscribe } from '../../../core/utils.service'; import { EndpointListHelper } from '../../../shared/components/list/list-types/endpoint/endpoint-list.helpers'; import { EndpointsListConfigService, } from '../../../shared/components/list/list-types/endpoint/endpoints-list-config.service'; import { ListConfig } from '../../../shared/components/list/list.component.types'; -import { Customizations, CustomizationsMetadata } from '../../../core/customizations.types'; @Component({ selector: 'app-endpoints-page', @@ -71,7 +72,7 @@ export class EndpointsPageComponent implements OnDestroy, OnInit { ).subscribe(); } - sub: Subscription; + sub: Subscription[] = []; public extensionActions: StratosActionMetadata[] = getActionsFromExtensions(StratosActionType.Endpoints); @@ -90,12 +91,14 @@ export class EndpointsPageComponent implements OnDestroy, OnInit { } ngOnInit() { - // Use custom component if specified - this.customNoEndpointsContainer.clear(); - if (this.customizations.noEndpointsComponent) { - const factory: ComponentFactory = this.resolver.resolveComponentFactory(this.customizations.noEndpointsComponent); - this.customContentComponentRef = this.customNoEndpointsContainer.createComponent(factory); - } + this.sub.push(this.endpointsService.haveRegistered$.subscribe(haveRegistered => { + // Use custom component if specified + this.customNoEndpointsContainer.clear(); + if (!haveRegistered && this.customizations.noEndpointsComponent) { + const factory: ComponentFactory = this.resolver.resolveComponentFactory(this.customizations.noEndpointsComponent); + this.customContentComponentRef = this.customNoEndpointsContainer.createComponent(factory); + } + })); this.endpointsService.checkAllEndpoints(); this.store.select(selectDashboardState).pipe( @@ -105,32 +108,11 @@ export class EndpointsPageComponent implements OnDestroy, OnInit { this.startEndpointHealthCheckPulse(); } }); - // Doesn't look like this is used (see connect-endpoint-dialog.component for actual handler) - // const params = queryParamMap(); - // if (params.cnsi_guid) { - // const guid = params.cnsi_guid; - // window.history.pushState({}, '', '/endpoints'); - // this.sub = this.endpointsService.endpoints$.pipe( - // delay(0), - // filter(ep => !!ep[guid]), - // map(ep => { - // const endpoint = ep[guid]; - // if (endpoint.connectionStatus === 'connected') { - // this.store.dispatch(new ShowSnackBar(`Connected endpoint '${endpoint.name}'`)); - // } else { - // this.store.dispatch(new ShowSnackBar(`A problem occurred connecting endpoint ${endpoint.name}`)); - // } - // }), - // first(), - // ).subscribe(); - // } } ngOnDestroy() { this.stopEndpointHealthCheckPulse(); - if (this.sub) { - this.sub.unsubscribe(); - } + safeUnsubscribe(...this.sub); if (this.customContentComponentRef) { this.customContentComponentRef.destroy(); } From 1bdb00015e8a13bbf825f21959aba2d10c531bfc Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 29 Aug 2019 16:15:34 +0100 Subject: [PATCH 076/648] Add CaaSP V4 support for Helm Client --- .../kubernetes/store/kubernetes.effects.ts | 7 ++- .../plugins/kubernetes/auth/kubeconfig.go | 19 +++---- src/jetstream/plugins/kubernetes/auth/oidc.go | 2 - .../plugins/kubernetes/auth/token.go | 55 +++++++++++++++++++ .../plugins/kubernetes/endpoint_config.go | 4 +- .../plugins/kubernetes/helm_client.go | 14 +++-- .../plugins/kubernetes/list_releases.go | 3 +- src/jetstream/plugins/kubernetes/main.go | 1 + 8 files changed, 82 insertions(+), 23 deletions(-) create mode 100644 src/jetstream/plugins/kubernetes/auth/token.go diff --git a/custom-src/frontend/app/custom/kubernetes/store/kubernetes.effects.ts b/custom-src/frontend/app/custom/kubernetes/store/kubernetes.effects.ts index 37e453ae14..0f351a0660 100644 --- a/custom-src/frontend/app/custom/kubernetes/store/kubernetes.effects.ts +++ b/custom-src/frontend/app/custom/kubernetes/store/kubernetes.effects.ts @@ -286,8 +286,11 @@ export class KubernetesEffects { const statefulSets = statefulesetResponse[kubeId].items as Array; const getChartName = (name: string, labelName: string): string => { - const releaseDeployment = deployments.filter(d => d.metadata.labels['app.kubernetes.io/instance'] === name); - const releaseStatefulSets = statefulSets.filter(d => d.metadata.labels['app.kubernetes.io/instance'] === name); + // Might not have a label (e.g. Dex) + const releaseDeployment = deployments.filter(d => + d.metadata.labels && d.metadata.labels['app.kubernetes.io/instance'] === name); + const releaseStatefulSets = statefulSets.filter(d => + d.metadata.labels && d.metadata.labels['app.kubernetes.io/instance'] === name); if (releaseDeployment.length !== 0) { return releaseDeployment[0].metadata.labels[labelName]; diff --git a/src/jetstream/plugins/kubernetes/auth/kubeconfig.go b/src/jetstream/plugins/kubernetes/auth/kubeconfig.go index 2adce58f80..f5f6a56436 100644 --- a/src/jetstream/plugins/kubernetes/auth/kubeconfig.go +++ b/src/jetstream/plugins/kubernetes/auth/kubeconfig.go @@ -2,7 +2,6 @@ package auth import ( "encoding/base64" - "encoding/json" "fmt" "io/ioutil" "time" @@ -11,6 +10,7 @@ import ( "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/labstack/echo" log "github.com/sirupsen/logrus" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" ) const AuthConnectTypeKubeConfig = "kubeconfig" @@ -31,6 +31,11 @@ func (c *KubeConfigAuth) GetName() string { return AuthConnectTypeKubeConfig } +func (c *KubeConfigAuth) AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { + log.Error("KubeConfigAuth: AddAuthInfo: Not supported") + return fmt.Errorf("Not supported: %s", tokenRec.AuthType) +} + func (c *KubeConfigAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { log.Debug("FetchToken (KubeConfigAuth)") @@ -61,18 +66,11 @@ func (c *KubeConfigAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Co // Check for Token == CaaSP V4 if len(kubeConfigUser.User.Token) > 0 { - tokenRecord := c.portalProxy.InitEndpointTokenRecord(getLargeExpiryTime(), kubeConfigUser.User.Token, "__NONE__", false) - tokenRecord.AuthType = interfaces.AuthTypeOIDC - - oauthMetadata := &interfaces.OAuth2Metadata{} - jsonString, err := json.Marshal(oauthMetadata) - if err == nil { - tokenRecord.Metadata = string(jsonString) - } + tokenRecord := NewKubeTokenAuthTokenRecord(c.portalProxy, kubeConfigUser.User.Token) // Could try and make a K8S Api call to validate the token // Or, maybe we can verify the access token with the auth URL ? - return &tokenRecord, &cnsiRecord, nil + return tokenRecord, &cnsiRecord, nil } return nil, nil, fmt.Errorf("OIDC: Unsupported authentication provider for user: %s", kubeConfigUser.User.AuthProvider.Name) @@ -106,6 +104,7 @@ func (c *KubeConfigAuth) GetCertAuth(cnsiRecord interfaces.CNSIRecord, user *con // Tokens lasts forever disconnected := false + tokenRecord := c.portalProxy.InitEndpointTokenRecord(getLargeExpiryTime(), accessToken, refreshToken, disconnected) tokenRecord.AuthType = authConnectTypeCertAuth return &tokenRecord, &cnsiRecord, nil diff --git a/src/jetstream/plugins/kubernetes/auth/oidc.go b/src/jetstream/plugins/kubernetes/auth/oidc.go index 81bdf0000b..e48f29f958 100644 --- a/src/jetstream/plugins/kubernetes/auth/oidc.go +++ b/src/jetstream/plugins/kubernetes/auth/oidc.go @@ -44,8 +44,6 @@ func (c *OIDCKubeAuth) GetName() string { } func (c *OIDCKubeAuth) AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { - log.Info("AddAuthInfo") - authInfo := &interfaces.OAuth2Metadata{} err := json.Unmarshal([]byte(tokenRec.Metadata), &authInfo) if err != nil { diff --git a/src/jetstream/plugins/kubernetes/auth/token.go b/src/jetstream/plugins/kubernetes/auth/token.go new file mode 100644 index 0000000000..4df2445ca6 --- /dev/null +++ b/src/jetstream/plugins/kubernetes/auth/token.go @@ -0,0 +1,55 @@ +package auth + +import ( + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" + "github.com/labstack/echo" + log "github.com/sirupsen/logrus" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +const AuthConnectTypeKubeToken = "k8sToken" + +// KubeTokenAuth uses a token (e.g. service account token) +type KubeTokenAuth struct { + portalProxy interfaces.PortalProxy +} + +// InitKubeTokenAuth +func InitKubeTokenAuth(portalProxy interfaces.PortalProxy) KubeAuthProvider { + return &KubeTokenAuth{portalProxy} +} + +func (c *KubeTokenAuth) GetName() string { + return AuthConnectTypeKubeToken +} + +func (c *KubeTokenAuth) AddAuthInfo(info *clientcmdapi.AuthInfo, tokenRec interfaces.TokenRecord) error { + log.Debug("AddAuthInfo: KubeTokenAuth") + // Just add the token in + info.Token = tokenRec.AuthToken + return nil +} + +func (c *KubeTokenAuth) FetchToken(cnsiRecord interfaces.CNSIRecord, ec echo.Context) (*interfaces.TokenRecord, *interfaces.CNSIRecord, error) { + log.Debug("FetchToken (KubeTokenAuth)") + + token := ec.FormValue("token") + + tokenRecord := NewKubeTokenAuthTokenRecord(c.portalProxy, token) + return tokenRecord, &cnsiRecord, nil +} + +func NewKubeTokenAuthTokenRecord(portalProxy interfaces.PortalProxy, token string) *interfaces.TokenRecord { + tokenRecord := portalProxy.InitEndpointTokenRecord(getLargeExpiryTime(), token, "__NONE__", false) + tokenRecord.AuthType = AuthConnectTypeKubeToken + + return &tokenRecord +} + +func (c *KubeTokenAuth) RegisterJetstreamAuthType(portal interfaces.PortalProxy) { + // Register auth type with Jetstream + c.portalProxy.AddAuthProvider(c.GetName(), interfaces.AuthProvider{ + Handler: c.portalProxy.DoOidcFlowRequest, + UserInfo: nil, + }) +} diff --git a/src/jetstream/plugins/kubernetes/endpoint_config.go b/src/jetstream/plugins/kubernetes/endpoint_config.go index 22f3521f10..161460ce06 100644 --- a/src/jetstream/plugins/kubernetes/endpoint_config.go +++ b/src/jetstream/plugins/kubernetes/endpoint_config.go @@ -1,7 +1,7 @@ package kubernetes import ( - "errors" + "fmt" log "github.com/sirupsen/logrus" @@ -50,7 +50,7 @@ func (c *KubernetesSpecification) addAuthInfoForEndpoint(info *clientcmdapi.Auth log.Debug("addAuthInfoForEndpoint") var authProvider = c.GetAuthProvider(tokenRec.AuthType) if authProvider == nil { - return errors.New("Unsupported auth type") + return fmt.Errorf("Unsupported auth type: %s", tokenRec.AuthType) } return authProvider.AddAuthInfo(info, tokenRec) diff --git a/src/jetstream/plugins/kubernetes/helm_client.go b/src/jetstream/plugins/kubernetes/helm_client.go index 7c04053da1..154c71a140 100644 --- a/src/jetstream/plugins/kubernetes/helm_client.go +++ b/src/jetstream/plugins/kubernetes/helm_client.go @@ -22,33 +22,35 @@ func (c *KubernetesSpecification) GetHelmClient(endpointGUID, userID string) (he cnsiRecord, err := p.GetCNSIRecord(endpointGUID) if err != nil { - return nil, nil, nil, errors.New("Can not get endpoint record") + return nil, nil, nil, errors.New("Helm: Can not get endpoint record") } tokenRecord, ok := p.GetCNSITokenRecord(endpointGUID, userID) if !ok { - return nil, nil, nil, errors.New("Can not get user token for endpoint") + return nil, nil, nil, errors.New("Helm: Can not get user token for endpoint") } + log.Error("GetHelmClient (2)") + config, err := c.GetConfigForEndpoint(cnsiRecord.APIEndpoint.String(), tokenRecord) if err != nil { + log.Errorf("Helm: Could not get config for endpoint: %s", err) return nil, nil, nil, errors.New("Can not get Kubernetes config for specified endpoint") } kubeClient, err := kubernetes.NewForConfig(config) if err != nil { - log.Error("Could not get kube client") + log.Errorf("Helm: Could not get kube client: %s", err) return nil, nil, nil, err } tillerTunnel, err := portforwarder.New("kube-system", kubeClient, config) if err != nil { - log.Error("Could not establish port forwarding for Tiller") - log.Error(err) + log.Error("Helm: Could not establish port forwarding for Tiller") return nil, nil, nil, err } - log.Debugf("Tiller tunnel is using Port: %d", tillerTunnel.Local) + log.Debugf("Helm: Tiller tunnel is using Port: %d", tillerTunnel.Local) tillerHost := fmt.Sprintf("127.0.0.1:%d", tillerTunnel.Local) client := newClient(tillerHost) diff --git a/src/jetstream/plugins/kubernetes/list_releases.go b/src/jetstream/plugins/kubernetes/list_releases.go index c920c888a5..5e7e920a83 100644 --- a/src/jetstream/plugins/kubernetes/list_releases.go +++ b/src/jetstream/plugins/kubernetes/list_releases.go @@ -55,7 +55,7 @@ func (c *KubernetesSpecification) listReleases(ep *interfaces.ConnectedEndpoint, log.Debugf("listReleases: START: %s", ep.GUID) client, _, tiller, err := c.GetHelmClient(ep.GUID, ep.Account) if err != nil { - log.Debugf("listReleases: CLIENT_ERROR: %s", ep.GUID) + log.Errorf("Helm: ListReleases could not get a Helm Client: %s", err) done <- response return } @@ -90,6 +90,7 @@ func (c *KubernetesSpecification) GetRelease(ec echo.Context) error { client, _, tiller, err := c.GetHelmClient(endpointGUID, userID) if err != nil { + log.Errorf("Helm: GetRelease could not get a Helm Client: %s", err) return err } diff --git a/src/jetstream/plugins/kubernetes/main.go b/src/jetstream/plugins/kubernetes/main.go index f439dee54e..cafcbca15d 100644 --- a/src/jetstream/plugins/kubernetes/main.go +++ b/src/jetstream/plugins/kubernetes/main.go @@ -96,6 +96,7 @@ func (c *KubernetesSpecification) Init() error { c.AddAuthProvider(auth.InitAzureKubeAuth(c.portalProxy)) c.AddAuthProvider(auth.InitOIDCKubeAuth(c.portalProxy)) c.AddAuthProvider(auth.InitKubeConfigAuth(c.portalProxy)) + c.AddAuthProvider(auth.InitKubeTokenAuth(c.portalProxy)) // Kube dashboard is enabled by Tech Preview mode c.portalProxy.GetConfig().PluginConfig[kubeDashboardPluginConfigSetting] = strconv.FormatBool(c.portalProxy.GetConfig().EnableTechPreview) From e657799147cc64977d580e5814aef5f71d5622b8 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Thu, 29 Aug 2019 16:38:02 +0100 Subject: [PATCH 077/648] Move 'disconnected' snackbar from endpoint missing component into endpoints page --- .../endpoints-page.component.ts | 43 ++++++++++++++++--- .../cf-endpoints-missing.component.ts | 5 +-- .../endpoints-missing.component.ts | 30 ++----------- 3 files changed, 42 insertions(+), 36 deletions(-) diff --git a/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.ts b/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.ts index 2e36019a86..9e323de169 100644 --- a/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.ts @@ -1,4 +1,5 @@ import { + AfterViewInit, Component, ComponentFactory, ComponentFactoryResolver, @@ -10,9 +11,10 @@ import { ViewChild, ViewContainerRef, } from '@angular/core'; +import { MatSnackBar, MatSnackBarRef, SimpleSnackBar } from '@angular/material'; import { Store } from '@ngrx/store'; -import { Subscription } from 'rxjs'; -import { first, map } from 'rxjs/operators'; +import { combineLatest, Subscription } from 'rxjs'; +import { delay, first, map, tap } from 'rxjs/operators'; import { RouterNav } from '../../../../../store/src/actions/router.actions'; import { AppState } from '../../../../../store/src/app-state'; @@ -41,18 +43,25 @@ import { ListConfig } from '../../../shared/components/list/list.component.types useClass: EndpointsListConfigService, }, EndpointListHelper] }) -export class EndpointsPageComponent implements OnDestroy, OnInit { +export class EndpointsPageComponent implements AfterViewInit, OnDestroy, OnInit { public canRegisterEndpoint = CurrentUserPermissions.ENDPOINT_REGISTER; private healthCheckTimeout: number; @ViewChild('customNoEndpoints', { read: ViewContainerRef }) customNoEndpointsContainer; customContentComponentRef: ComponentRef; + private snackBarRef: MatSnackBarRef; + private snackBarText = { + message: `There are no connected endpoints, connect with your personal credentials to get started.`, + action: 'Got it' + }; + constructor( public endpointsService: EndpointsService, public store: Store, private ngZone: NgZone, private resolver: ComponentFactoryResolver, + private snackBar: MatSnackBar, @Inject(Customizations) public customizations: CustomizationsMetadata ) { // Redirect to /applications if not enabled. @@ -72,7 +81,7 @@ export class EndpointsPageComponent implements OnDestroy, OnInit { ).subscribe(); } - sub: Subscription[] = []; + subs: Subscription[] = []; public extensionActions: StratosActionMetadata[] = getActionsFromExtensions(StratosActionType.Endpoints); @@ -90,8 +99,16 @@ export class EndpointsPageComponent implements OnDestroy, OnInit { clearInterval(this.healthCheckTimeout); } + private showSnackBar(show: boolean) { + if (!this.snackBarRef && show) { + this.snackBarRef = this.snackBar.open(this.snackBarText.message, this.snackBarText.action, {}); + } else if (this.snackBarRef && !show) { + this.snackBarRef.dismiss(); + } + } + ngOnInit() { - this.sub.push(this.endpointsService.haveRegistered$.subscribe(haveRegistered => { + this.subs.push(this.endpointsService.haveRegistered$.subscribe(haveRegistered => { // Use custom component if specified this.customNoEndpointsContainer.clear(); if (!haveRegistered && this.customizations.noEndpointsComponent) { @@ -110,13 +127,25 @@ export class EndpointsPageComponent implements OnDestroy, OnInit { }); } + ngAfterViewInit() { + this.subs.push(combineLatest( + this.endpointsService.haveRegistered$, + this.endpointsService.haveConnected$, + ).pipe( + delay(1), + tap(([hasRegistered, hasConnected]) => { + this.showSnackBar(hasRegistered && !hasConnected); + }), + ).subscribe()); + } + ngOnDestroy() { this.stopEndpointHealthCheckPulse(); - safeUnsubscribe(...this.sub); + safeUnsubscribe(...this.subs); if (this.customContentComponentRef) { this.customContentComponentRef.destroy(); } - + this.showSnackBar(false); } } diff --git a/src/frontend/packages/core/src/shared/components/cf-endpoints-missing/cf-endpoints-missing.component.ts b/src/frontend/packages/core/src/shared/components/cf-endpoints-missing/cf-endpoints-missing.component.ts index 6ca1978eec..d798611ea4 100644 --- a/src/frontend/packages/core/src/shared/components/cf-endpoints-missing/cf-endpoints-missing.component.ts +++ b/src/frontend/packages/core/src/shared/components/cf-endpoints-missing/cf-endpoints-missing.component.ts @@ -1,5 +1,4 @@ import { Component } from '@angular/core'; -import { MatSnackBar } from '@angular/material'; import { EndpointsService } from '../../../core/endpoints.service'; import { CloudFoundryService } from '../../data-services/cloud-foundry.service'; @@ -30,8 +29,8 @@ export class CfEndpointsMissingComponent extends EndpointsMissingComponent { showToolbarHint = false; showNoConnected = true; - constructor(snackBar: MatSnackBar, cloudFoundryService: CloudFoundryService, endpointsService: EndpointsService) { - super(snackBar, endpointsService); + constructor(cloudFoundryService: CloudFoundryService, endpointsService: EndpointsService) { + super(endpointsService); this.haveConnected$ = cloudFoundryService.hasConnectedCFEndpoints$; this.haveRegistered$ = cloudFoundryService.hasRegisteredCFEndpoints$; } diff --git a/src/frontend/packages/core/src/shared/components/endpoints-missing/endpoints-missing.component.ts b/src/frontend/packages/core/src/shared/components/endpoints-missing/endpoints-missing.component.ts index b8625eff53..92bffd84e1 100644 --- a/src/frontend/packages/core/src/shared/components/endpoints-missing/endpoints-missing.component.ts +++ b/src/frontend/packages/core/src/shared/components/endpoints-missing/endpoints-missing.component.ts @@ -1,7 +1,6 @@ -import { AfterViewInit, Component, Input, OnDestroy, OnInit } from '@angular/core'; -import { MatSnackBar, MatSnackBarRef, SimpleSnackBar } from '@angular/material'; +import { AfterViewInit, Component, Input, OnInit } from '@angular/core'; import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; -import { delay, map, startWith, tap } from 'rxjs/operators'; +import { delay, map, startWith } from 'rxjs/operators'; import { EndpointsService } from '../../../core/endpoints.service'; @@ -20,16 +19,12 @@ export interface EndpointMissingMessageParts { templateUrl: './endpoints-missing.component.html', styleUrls: ['./endpoints-missing.component.scss'] }) -export class EndpointsMissingComponent implements AfterViewInit, OnDestroy, OnInit { +export class EndpointsMissingComponent implements AfterViewInit, OnInit { @Input() showToolbarHint = true; @Input() showDirectToEndpointMessage = true; noContent$: Observable; - snackBarText = { - message: `There are no connected endpoints, connect with your personal credentials to get started.`, - action: 'Got it' - }; protected noneRegisteredText: EndpointMissingMessageParts = { firstLine: 'There are no registered endpoints', @@ -46,12 +41,11 @@ export class EndpointsMissingComponent implements AfterViewInit, OnDestroy, OnIn }, }; - private snackBarRef: MatSnackBarRef; protected showNoConnected = false; protected haveRegistered$: Observable; protected haveConnected$: Observable; - constructor(private snackBar: MatSnackBar, public endpointsService: EndpointsService) { + constructor(public endpointsService: EndpointsService) { this.haveRegistered$ = this.endpointsService.haveRegistered$; this.haveConnected$ = this.endpointsService.haveConnected$; } @@ -69,9 +63,6 @@ export class EndpointsMissingComponent implements AfterViewInit, OnDestroy, OnIn this.endpointsService.disablePersistenceFeatures$ ).pipe( delay(1), - tap(([hasRegistered, hasConnected]) => { - this.showSnackBar(hasRegistered && !hasConnected); - }), map(([hasRegistered, hasConnected, disablePersistenceFeatures]) => { if (!hasRegistered) { return this.removeAdvice(this.noneRegisteredText, disablePersistenceFeatures); @@ -96,17 +87,4 @@ export class EndpointsMissingComponent implements AfterViewInit, OnDestroy, OnIn } }; } - - ngOnDestroy() { - this.showSnackBar(false); - } - - private showSnackBar(show: boolean) { - if (!this.snackBarRef && show) { - this.snackBarRef = this.snackBar.open(this.snackBarText.message, this.snackBarText.action, {}); - } else if (this.snackBarRef && !show) { - this.snackBarRef.dismiss(); - } - } - } From 1ca0174d8ae432c8feeee67aba436a90af96efc3 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Thu, 29 Aug 2019 18:17:27 +0100 Subject: [PATCH 078/648] Fix failing e2e tests --- src/frontend/packages/core/src/app.component.spec.ts | 2 +- .../cf-endpoints-missing.component.spec.ts | 7 +++---- src/test-e2e/endpoints/endpoints-e2e.spec.ts | 2 +- src/test-e2e/endpoints/endpoints-unregister-e2e.spec.ts | 2 +- src/test-e2e/endpoints/endpoints.po.ts | 7 +++++-- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/frontend/packages/core/src/app.component.spec.ts b/src/frontend/packages/core/src/app.component.spec.ts index 1a13ecd2c7..d34174a375 100644 --- a/src/frontend/packages/core/src/app.component.spec.ts +++ b/src/frontend/packages/core/src/app.component.spec.ts @@ -1,10 +1,10 @@ import { async, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; +import { createBasicStoreModule } from '../test-framework/store-test-helper'; import { AppComponent } from './app.component'; import { LoggedInService } from './logged-in.service'; import { SharedModule } from './shared/shared.module'; -import { createBasicStoreModule } from '../test-framework/store-test-helper'; describe('AppComponent', () => { diff --git a/src/frontend/packages/core/src/shared/components/cf-endpoints-missing/cf-endpoints-missing.component.spec.ts b/src/frontend/packages/core/src/shared/components/cf-endpoints-missing/cf-endpoints-missing.component.spec.ts index 350b310ac5..81bbf4652a 100644 --- a/src/frontend/packages/core/src/shared/components/cf-endpoints-missing/cf-endpoints-missing.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/cf-endpoints-missing/cf-endpoints-missing.component.spec.ts @@ -1,11 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { CfEndpointsMissingComponent } from './cf-endpoints-missing.component'; -import { CoreModule } from '../../../core/core.module'; - import { RouterTestingModule } from '@angular/router/testing'; + import { createBasicStoreModule } from '../../../../test-framework/store-test-helper'; +import { CoreModule } from '../../../core/core.module'; import { SharedModule } from '../../shared.module'; +import { CfEndpointsMissingComponent } from './cf-endpoints-missing.component'; describe('CfEndpointsMissingComponent', () => { diff --git a/src/test-e2e/endpoints/endpoints-e2e.spec.ts b/src/test-e2e/endpoints/endpoints-e2e.spec.ts index 4aa2dfbaff..720839d523 100644 --- a/src/test-e2e/endpoints/endpoints-e2e.spec.ts +++ b/src/test-e2e/endpoints/endpoints-e2e.spec.ts @@ -19,7 +19,7 @@ describe('Endpoints', () => { it('Should reach endpoints dashboard after log in', () => { expect(endpointsPage.isActivePage()).toBeTruthy(); - expect(endpointsPage.isWelcomeMessageAdmin()).toBeTruthy(); + expect(endpointsPage.isWelcomeMessageAdmin(false)).toBeTruthy(); expect(endpointsPage.list.isPresent()).toBeFalsy(); }); diff --git a/src/test-e2e/endpoints/endpoints-unregister-e2e.spec.ts b/src/test-e2e/endpoints/endpoints-unregister-e2e.spec.ts index 2362f6773f..f9a647b0c0 100644 --- a/src/test-e2e/endpoints/endpoints-unregister-e2e.spec.ts +++ b/src/test-e2e/endpoints/endpoints-unregister-e2e.spec.ts @@ -37,7 +37,7 @@ describe('Endpoints', () => { ConfirmDialogComponent.expectDialogAndConfirm('Unregister', 'Unregister Endpoint'); endpointsPage.list.waitForNoLoadingIndicator(); // Should have removed the only row, so we should see welcome message again - expect(endpointsPage.isWelcomeMessageAdmin()).toBeTruthy(); + expect(endpointsPage.isWelcomeMessageAdmin(false)).toBeTruthy(); }); }); }); diff --git a/src/test-e2e/endpoints/endpoints.po.ts b/src/test-e2e/endpoints/endpoints.po.ts index 5a39f89adc..9f01d61af3 100644 --- a/src/test-e2e/endpoints/endpoints.po.ts +++ b/src/test-e2e/endpoints/endpoints.po.ts @@ -122,9 +122,12 @@ export class EndpointsPage extends Page { }); } - isWelcomeMessageAdmin() { + isWelcomeMessageAdmin(shouldHavePrompt = true) { return this.isWelcomeMessageNonAdmin().then(okay => { - return okay ? this.isWelcomePromptAdmin() : false; + if (okay) { + return shouldHavePrompt ? this.isWelcomePromptAdmin() : true; + } + return false; }); } From 9f14b7bddf0abea8ac3fceb25eed806599c9b781 Mon Sep 17 00:00:00 2001 From: Jordan Jensen Date: Wed, 7 Aug 2019 02:21:28 -0700 Subject: [PATCH 079/648] Expand ListConfig to get/set filters (SOC-9450) --- .../monocular-charts-list-config.service.ts | 2 + ...ocular-release-pods-list-config.service.ts | 2 + ...ar-release-services-list-config.service.ts | 2 + .../monocular-releases-list-config.service.ts | 2 + ...onocular-repository-list-config.service.ts | 2 + .../monocular-versions-list-config.service.ts | 2 + .../kubernetes-apps-list-config.service.ts | 2 + ...ubernetes-endpoints-list-config.service.ts | 2 + ...bernetes-namespaces-list-config.service.ts | 2 + .../kubernetes-nodes-data-source.ts | 79 ++++++++++++++++++- .../kubernetes-nodes-list-config.service.ts | 43 ++++++++-- .../kubernetes-pods-list-config.service.ts | 2 + .../kubernetes-service-list-config.service.ts | 3 +- .../app-event/cf-app-events-config.service.ts | 3 +- .../cf-app-instances-config.service.ts | 2 + .../cf-app-variables-list-config.service.ts | 2 + .../list-types/app/cf-app-config.service.ts | 3 +- .../list-types/base-cf/base-cf-list-config.ts | 2 + .../cf-endpoints-list-config.service.ts | 2 + .../cf-routes/cf-routes-list-config-base.ts | 2 + .../cf-routes-list-config.service.ts | 2 + .../cf-select-users-list-config.service.ts | 2 + .../cf-service-instances-list-config.base.ts | 2 + .../cf-services-list-config.service.ts | 2 + .../cf-user-service-instances-list-config.ts | 2 + .../cf-space-apps-list-config.service.ts | 2 + .../cf-spaces-list-config.service.ts | 2 + ...f-users-space-roles-list-config.service.ts | 2 + .../detach-apps-list-config.service.ts | 2 + .../endpoint/endpoints-list-config.service.ts | 2 + ...github-commits-list-config-base.service.ts | 2 + .../service-plans-list-config.service.ts | 2 + .../components/list/list.component.types.ts | 15 ++++ 33 files changed, 187 insertions(+), 13 deletions(-) diff --git a/custom-src/frontend/app/custom/helm/list-types/monocular-charts-list-config.service.ts b/custom-src/frontend/app/custom/helm/list-types/monocular-charts-list-config.service.ts index 551fff9625..30c24847b8 100644 --- a/custom-src/frontend/app/custom/helm/list-types/monocular-charts-list-config.service.ts +++ b/custom-src/frontend/app/custom/helm/list-types/monocular-charts-list-config.service.ts @@ -84,6 +84,8 @@ export class MonocularChartsListConfig implements IListConfig { getColumns = () => this.columns; getDataSource = () => this.AppsDataSource; getMultiFiltersConfigs = () => [this.createRepositoryFilterConfig()]; + getFilters = () => []; + setFilter = (id: string) => null; private createRepositoryFilterConfig(): IListMultiFilterConfig { return { diff --git a/custom-src/frontend/app/custom/helm/list-types/monocular-release-pods-list-config.service.ts b/custom-src/frontend/app/custom/helm/list-types/monocular-release-pods-list-config.service.ts index 22d21f4dc0..069659dfa7 100644 --- a/custom-src/frontend/app/custom/helm/list-types/monocular-release-pods-list-config.service.ts +++ b/custom-src/frontend/app/custom/helm/list-types/monocular-release-pods-list-config.service.ts @@ -98,5 +98,7 @@ export class HelmReleasePodsListConfig implements IListConfig { public getMultiActions = () => []; public getSingleActions = () => []; public getMultiFiltersConfigs = () => []; + public getFilters = () => []; + public setFilter = (id: string) => null; public getDataSource = () => this.dataSource; } diff --git a/custom-src/frontend/app/custom/helm/list-types/monocular-release-services-list-config.service.ts b/custom-src/frontend/app/custom/helm/list-types/monocular-release-services-list-config.service.ts index b1b46b43cb..b8309ab0b6 100644 --- a/custom-src/frontend/app/custom/helm/list-types/monocular-release-services-list-config.service.ts +++ b/custom-src/frontend/app/custom/helm/list-types/monocular-release-services-list-config.service.ts @@ -80,5 +80,7 @@ export class HelmReleaseServicesListConfig implements IListConfig []; public getSingleActions = () => []; public getMultiFiltersConfigs = () => []; + public getFilters = () => []; + public setFilter = (id: string) => null; public getDataSource = () => this.dataSource; } diff --git a/custom-src/frontend/app/custom/helm/list-types/monocular-releases-list-config.service.ts b/custom-src/frontend/app/custom/helm/list-types/monocular-releases-list-config.service.ts index 9512498e97..b6ab797c79 100644 --- a/custom-src/frontend/app/custom/helm/list-types/monocular-releases-list-config.service.ts +++ b/custom-src/frontend/app/custom/helm/list-types/monocular-releases-list-config.service.ts @@ -117,5 +117,7 @@ export class HelmReleasesListConfig implements IListConfig { public getMultiActions = () => []; public getSingleActions = () => []; public getMultiFiltersConfigs = () => []; + public getFilters = () => []; + public setFilter = (id: string) => null; public getDataSource = () => this.dataSource; } diff --git a/custom-src/frontend/app/custom/helm/list-types/monocular-repository-list-config.service.ts b/custom-src/frontend/app/custom/helm/list-types/monocular-repository-list-config.service.ts index 0e99e7dd86..d6d38b928b 100644 --- a/custom-src/frontend/app/custom/helm/list-types/monocular-repository-list-config.service.ts +++ b/custom-src/frontend/app/custom/helm/list-types/monocular-repository-list-config.service.ts @@ -96,5 +96,7 @@ export class MonocularRepositoryListConfig implements IListConfig public getMultiActions = () => []; public getSingleActions = () => []; public getMultiFiltersConfigs = () => []; + public getFilters = () => []; + public setFilter = (id: string) => null; public getDataSource = () => this.dataSource; } diff --git a/custom-src/frontend/app/custom/helm/list-types/monocular-versions-list-config.service.ts b/custom-src/frontend/app/custom/helm/list-types/monocular-versions-list-config.service.ts index 4da8970655..55b487e6e7 100644 --- a/custom-src/frontend/app/custom/helm/list-types/monocular-versions-list-config.service.ts +++ b/custom-src/frontend/app/custom/helm/list-types/monocular-versions-list-config.service.ts @@ -52,5 +52,7 @@ export class HelmVersionsListConfig implements IListConfig { public getMultiActions = () => []; public getSingleActions = () => []; public getMultiFiltersConfigs = () => []; + public getFilters = () => []; + public setFilter = (id: string) => null; public getDataSource = () => this.dataSource; } diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-apps/kubernetes-apps-list-config.service.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-apps/kubernetes-apps-list-config.service.ts index e984169e53..1e83b450bf 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-apps/kubernetes-apps-list-config.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-apps/kubernetes-apps-list-config.service.ts @@ -89,6 +89,8 @@ export class KubernetesAppsListConfigService implements IListConfig this.columns; getDataSource = () => this.AppsDataSource; getMultiFiltersConfigs = () => []; + getFilters = () => []; + setFilter = (id: string) => null; constructor( store: Store, diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-list-config.service.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-list-config.service.ts index 73f0cdd19f..409262d03c 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-list-config.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-list-config.service.ts @@ -53,5 +53,7 @@ export class KubernetesEndpointsListConfigService implements IListConfig []; public getSingleActions = () => []; public getMultiFiltersConfigs = () => []; + public getFilters = () => []; + public setFilter = (id: string) => null; public getDataSource = () => this.dataSource; } diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-list-config.service.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-list-config.service.ts index f4093d2b7c..fc4881c1a4 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-list-config.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-list-config.service.ts @@ -74,6 +74,8 @@ export class KubernetesNamespacesListConfigService implements IListConfig this.columns; getDataSource = () => this.podsDataSource; getMultiFiltersConfigs = () => []; + getFilters = () => []; + setFilter = (id: string) => null; getInitialised = () => this.initialised$; constructor( diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-data-source.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-data-source.ts index a64f8b5d5a..a1ba148334 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-data-source.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-data-source.ts @@ -3,20 +3,26 @@ import { Store } from '@ngrx/store'; import { getPaginationKey } from '../../../../../../store/src/actions/pagination.actions'; import { AppState } from '../../../../../../store/src/app-state'; import { entityFactory } from '../../../../../../store/src/helpers/entity-factory'; -import { ListDataSource } from '../../../../shared/components/list/data-sources-controllers/list-data-source'; +import { + DataFunction, + DataFunctionDefinition, + ListDataSource +} from '../../../../shared/components/list/data-sources-controllers/list-data-source'; import { IListConfig } from '../../../../shared/components/list/list.component.types'; import { BaseKubeGuid } from '../../kubernetes-page.types'; import { getKubeAPIResourceGuid } from '../../store/kube.selectors'; import { KubernetesNode } from '../../store/kube.types'; import { GetKubernetesNodes } from '../../store/kubernetes.actions'; import { kubernetesNodesSchemaKey } from '../../store/kubernetes.entities'; +import { PaginationEntityState } from '../../../../../../store/src/types/pagination.types'; export class KubernetesNodesDataSource extends ListDataSource { constructor( store: Store, kubeGuid: BaseKubeGuid, - listConfig: IListConfig + listConfig: IListConfig, + transformEntities: (DataFunction | DataFunctionDefinition)[] ) { super({ store, @@ -26,8 +32,75 @@ export class KubernetesNodesDataSource extends ListDataSource { paginationKey: getPaginationKey(kubernetesNodesSchemaKey, kubeGuid.guid), isLocal: true, listConfig, - transformEntities: [{ type: 'filter', field: 'metadata.name' }] + transformEntities }); } +} + +export class LabelsKubernetesNodesDataSource extends KubernetesNodesDataSource { + constructor( + store: Store, + kubeGuid: BaseKubeGuid, + listConfig: IListConfig + ) { + super( + store, + kubeGuid, + listConfig, + [ + (entities: KubernetesNode[], paginationStore: PaginationEntityState) => { + const filterString = paginationStore.clientPagination.filter.string.toUpperCase(); + return entities.filter(node => { + return Object.entries(node.metadata.labels).some(([label, value]) => { + label = label.toUpperCase(); + value = value.toUpperCase(); + return label.includes(filterString) || value.includes(filterString); + }); + }); + } + ] + ); + } +} + +export class NameKubernetesNodesDataSource extends KubernetesNodesDataSource { + + constructor( + store: Store, + kubeGuid: BaseKubeGuid, + listConfig: IListConfig + ) { + super( + store, + kubeGuid, + listConfig, + [{ type: 'filter', field: 'metadata.name' }] + ); + } +} + +export class IPAddressKubernetesNodesDataSource extends KubernetesNodesDataSource { + + constructor( + store: Store, + kubeGuid: BaseKubeGuid, + listConfig: IListConfig + ) { + super( + store, + kubeGuid, + listConfig, + [ + (entities: KubernetesNode[], paginationState: PaginationEntityState) => { + return entities.filter(node => { + const internalIP: string = node.status.addresses.find(address => { + return address.type === 'InternalIP'; + }).address; + return internalIP.toUpperCase().includes(paginationState.clientPagination.filter.string.toUpperCase()); + }); + } + ] + ); + } } diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts index 97033b1eaa..b4b496ae2c 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts @@ -3,7 +3,7 @@ import { Store } from '@ngrx/store'; import { AppState } from '../../../../../../store/src/app-state'; import { ITableColumn } from '../../../../shared/components/list/list-table/table.types'; -import { IListConfig, ListViewTypes } from '../../../../shared/components/list/list.component.types'; +import { IListConfig, IListFilter, ListViewTypes } from '../../../../shared/components/list/list.component.types'; import { BaseKubeGuid } from '../../kubernetes-page.types'; import { ConditionType, KubernetesNode } from '../../store/kube.types'; import { defaultHelmKubeListPageSize } from '../kube-helm-list-types'; @@ -11,13 +11,15 @@ import { getConditionSort } from '../kube-sort.helper'; import { ConditionCellComponent, SubtleConditionCellComponent } from './condition-cell/condition-cell.component'; import { KubernetesNodeCapacityComponent } from './kubernetes-node-capacity/kubernetes-node-capacity.component'; import { KubernetesNodeLinkComponent } from './kubernetes-node-link/kubernetes-node-link.component'; -import { KubernetesNodesDataSource } from './kubernetes-nodes-data-source'; +import { + LabelsKubernetesNodesDataSource, + NameKubernetesNodesDataSource, + IPAddressKubernetesNodesDataSource +} from './kubernetes-nodes-data-source'; import { NodePodCountComponent } from './node-pod-count/node-pod-count.component'; @Injectable() export class KubernetesNodesListConfigService implements IListConfig { - nodesDataSource: KubernetesNodesDataSource; - columns: Array> = [ { columnId: 'name', headerCell: () => 'Name', @@ -68,6 +70,8 @@ export class KubernetesNodesListConfigService implements IListConfig[]; + filterSelected: IListFilter; pageSizeOptions = defaultHelmKubeListPageSize; viewType = ListViewTypes.TABLE_ONLY; @@ -82,14 +86,39 @@ export class KubernetesNodesListConfigService implements IListConfig []; getSingleActions = () => []; getColumns = () => this.columns; - getDataSource = () => this.nodesDataSource; + getDataSource = () => this.filterSelected.dataSource; getMultiFiltersConfigs = () => []; + getFilters = (): IListFilter[] => this.filters; + setFilter = (id: string) => { + this.filterSelected = this.filters.find(filter => filter.id === id); + } constructor( store: Store, kubeId: BaseKubeGuid, ) { - this.nodesDataSource = new KubernetesNodesDataSource(store, kubeId, this); - } + this.filters = [ + { + dataSource: new NameKubernetesNodesDataSource(store, kubeId, this), + default: true, + id: 'name', + label: 'Name', + placeholder: 'Filter by Name' + }, + { + dataSource: new LabelsKubernetesNodesDataSource(store, kubeId, this), + id: 'labels', + label: 'Labels', + placeholder: 'Filter by Labels' + }, + { + dataSource: new IPAddressKubernetesNodesDataSource(store, kubeId, this), + id: 'ip-address', + label: 'IP Address', + placeholder: 'Filter by IP Address' + } + ]; + this.filterSelected = this.filters.find(filter => filter.default === true); + } } diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pods-list-config.service.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pods-list-config.service.ts index 3c097b2cbd..629af156ed 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pods-list-config.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pods-list-config.service.ts @@ -103,6 +103,8 @@ export class KubernetesPodsListConfigService implements IListConfig this.columns; getDataSource = () => this.podsDataSource; getMultiFiltersConfigs = () => []; + getFilters = () => []; + setFilter = (id: string) => null; constructor( store: Store, diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-service-list-config.service.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-service-list-config.service.ts index b2dc99e0a6..6da50d7cb7 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-service-list-config.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-service-list-config.service.ts @@ -60,5 +60,6 @@ export abstract class BaseKubernetesServicesListConfig implements IListConfig []; getColumns = () => this.columns; getMultiFiltersConfigs = () => []; - + getFilters = () => []; + setFilter = (id: string) => null; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/app-event/cf-app-events-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/app-event/cf-app-events-config.service.ts index a4fc8ce123..9146ab9467 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/app-event/cf-app-events-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/app-event/cf-app-events-config.service.ts @@ -52,5 +52,6 @@ export class CfAppEventsConfigService extends ListConfig implements getColumns = () => this.columns; getDataSource = () => this.eventSource; getMultiFiltersConfigs = () => []; - + getFilters = () => []; + setFilter = (id: string) => null; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.ts index ba3a782089..54dad54486 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.ts @@ -220,6 +220,8 @@ export class CfAppInstancesConfigService implements IListConfig getColumns = () => this.columns; getDataSource = () => this.instancesSource; getMultiFiltersConfigs = () => []; + getFilters = () => []; + setFilter = (id: string) => null; getInitialised = () => this.initialised$; private createMetricsResults(entityServiceFactory: EntityServiceFactory) { diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/app-variables/cf-app-variables-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/app-variables/cf-app-variables-list-config.service.ts index 63c03ed470..1c288d4be0 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/app-variables/cf-app-variables-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/app-variables/cf-app-variables-list-config.service.ts @@ -131,6 +131,8 @@ export class CfAppVariablesListConfigService implements IListConfig this.columns; getDataSource = () => this.envVarsDataSource; getMultiFiltersConfigs = () => []; + getFilters = () => []; + setFilter = (id: string) => null; constructor( private store: Store, diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/app/cf-app-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/app/cf-app-config.service.ts index d7cd8565c7..9abc1afdcf 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/app/cf-app-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/app/cf-app-config.service.ts @@ -144,6 +144,7 @@ export class CfAppConfigService extends ListConfig implements IList getColumns = () => this.columns; getDataSource = () => this.appsDataSource; getMultiFiltersConfigs = () => this.multiFilterConfigs; + getFilters = () => []; + setFilter = (id: string) => null; getInitialised = () => this.initialised$; - } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/base-cf/base-cf-list-config.ts b/src/frontend/packages/core/src/shared/components/list/list-types/base-cf/base-cf-list-config.ts index a2156d434d..5f0844b271 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/base-cf/base-cf-list-config.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/base-cf/base-cf-list-config.ts @@ -17,4 +17,6 @@ export class BaseCfListConfig implements IListConfig { getMultiActions = () => []; getSingleActions = () => []; getMultiFiltersConfigs = () => []; + getFilters = () => []; + setFilter = (id: string) => null; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/cf-endpoints/cf-endpoints-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/cf-endpoints/cf-endpoints-list-config.service.ts index f351d6db32..cd617b7535 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/cf-endpoints/cf-endpoints-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/cf-endpoints/cf-endpoints-list-config.service.ts @@ -49,5 +49,7 @@ export class CFEndpointsListConfigService implements IListConfig public getMultiActions = () => []; public getSingleActions = () => []; public getMultiFiltersConfigs = () => []; + public getFilters = () => []; + public setFilter = (id: string) => null; public getDataSource = () => this.dataSource; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/cf-routes/cf-routes-list-config-base.ts b/src/frontend/packages/core/src/shared/components/list/list-types/cf-routes/cf-routes-list-config-base.ts index bbc1f804df..459a476e3a 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/cf-routes/cf-routes-list-config-base.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/cf-routes/cf-routes-list-config-base.ts @@ -205,6 +205,8 @@ export abstract class CfRoutesListConfigBase implements IListConfig getColumns = () => this.columns; getMultiFiltersConfigs = () => []; + getFilters = () => []; + setFilter = (id: string) => null; /** * Creates an instance of CfRoutesListConfigBase. diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/cf-routes/cf-routes-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/cf-routes/cf-routes-list-config.service.ts index 0a2cce34e2..9b96a62922 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/cf-routes/cf-routes-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/cf-routes/cf-routes-list-config.service.ts @@ -69,6 +69,8 @@ export class CfRoutesListConfigService extends CfRoutesListConfigBase implements createCfOrgSpaceFilterConfig('space', 'Space', cfOrgSpaceService.space), ]; this.getMultiFiltersConfigs = () => multiFilterConfigs; + this.getFilters = () => []; + this.setFilter = (id: string) => null; initCfOrgSpaceService(store, cfOrgSpaceService, this.dataSource.masterAction.entityKey, this.dataSource.masterAction.paginationKey).subscribe(); diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/cf-select-users/cf-select-users-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/cf-select-users/cf-select-users-list-config.service.ts index 1717819c40..5b4173600f 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/cf-select-users/cf-select-users-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/cf-select-users/cf-select-users-list-config.service.ts @@ -132,6 +132,8 @@ export class CfSelectUsersListConfigService implements IListConfig>[] => []; getSingleActions = () => []; getMultiFiltersConfigs = () => []; + getFilters = () => []; + setFilter = (id: string) => null; getDataSource = () => this.dataSource; getInitialised = () => this.initialised; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts b/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts index a16964b67b..1890f455ad 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts @@ -193,6 +193,8 @@ export class CfServiceInstancesListConfigBase implements IListConfig []; getSingleActions = () => [this.listActionEdit, this.listActionDetach, this.listActionDelete]; getMultiFiltersConfigs = () => []; + getFilters = () => []; + setFilter = (id: string) => null; getColumns = () => this.serviceInstanceColumns; getDataSource = () => null; diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.ts index c28b53a669..8abe831628 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.ts @@ -84,5 +84,7 @@ export class CfServicesListConfigService implements IListConfig { getMultiActions = () => []; getSingleActions = () => []; getMultiFiltersConfigs = () => this.multiFilterConfigs; + getFilters = () => []; + setFilter = (id: string) => null; getDataSource = () => this.dataSource; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-user-service-instances-list-config.ts b/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-user-service-instances-list-config.ts index c0053902e0..e8ef1501f2 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-user-service-instances-list-config.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-user-service-instances-list-config.ts @@ -197,6 +197,8 @@ export class CfUserServiceInstancesListConfigBase implements IListConfig []; getSingleActions = () => [this.listActionEdit, this.listActionDetach, this.listActionDelete]; getMultiFiltersConfigs = () => []; + getFilters = () => []; + setFilter = (id: string) => null; getColumns = () => this.serviceInstanceColumns; getDataSource = () => this.dataSource; diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-list-config.service.ts index bbb6c860de..ed82576976 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-list-config.service.ts @@ -88,5 +88,7 @@ export class CfSpaceAppsListConfigService implements IListConfig { getMultiActions = () => []; getSingleActions = () => []; getMultiFiltersConfigs = () => []; + getFilters = () => []; + setFilter = (id: string) => null; getDataSource = () => this.dataSource; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/cf-spaces/cf-spaces-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/cf-spaces/cf-spaces-list-config.service.ts index 27ff550d79..6935a22986 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/cf-spaces/cf-spaces-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/cf-spaces/cf-spaces-list-config.service.ts @@ -55,5 +55,7 @@ export class CfSpacesListConfigService implements IListConfig []; getSingleActions = () => []; getMultiFiltersConfigs = () => []; + getFilters = () => []; + setFilter = (id: string) => null; getDataSource = () => this.dataSource; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/cf-users-org-space-roles/cf-users-space-roles-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/cf-users-org-space-roles/cf-users-space-roles-list-config.service.ts index ca630bca18..70517de420 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/cf-users-org-space-roles/cf-users-space-roles-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/cf-users-org-space-roles/cf-users-space-roles-list-config.service.ts @@ -85,6 +85,8 @@ export class CfUsersSpaceRolesListConfigService implements IListConfig []; getSingleActions = () => []; getMultiFiltersConfigs = () => []; + getFilters = () => []; + setFilter = (id: string) => null; getDataSource = () => this.dataSource; public getInitialised = () => this.initialised; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/detach-apps/detach-apps-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/detach-apps/detach-apps-list-config.service.ts index b37519b820..394ea1be37 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/detach-apps/detach-apps-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/detach-apps/detach-apps-list-config.service.ts @@ -57,5 +57,7 @@ export class DetachAppsListConfigService implements IListConfig { getMultiActions = () => []; getSingleActions = () => []; getMultiFiltersConfigs = () => []; + getFilters = () => []; + setFilter = (id: string) => null; getDataSource = () => this.dataSource; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts index 88ea39eef8..d8d6cb9fa4 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts @@ -134,4 +134,6 @@ export class EndpointsListConfigService implements IListConfig { public getColumns = () => this.columns; public getDataSource = () => this.dataSource; public getMultiFiltersConfigs = () => []; + public getFilters = () => []; + public setFilter = (id: string) => null; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/github-commits/github-commits-list-config-base.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/github-commits/github-commits-list-config-base.service.ts index c24b228713..585b8a8171 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/github-commits/github-commits-list-config-base.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/github-commits/github-commits-list-config-base.service.ts @@ -92,6 +92,8 @@ export abstract class GithubCommitsListConfigServiceBase implements IListConfig< public getMultiActions = () => []; public getSingleActions = () => []; public getMultiFiltersConfigs = () => []; + public getFilters = () => []; + public setFilter = (id: string) => null; public getDataSource = () => this.dataSource; public getInitialised = () => this.initialised; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/service-plans/service-plans-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/service-plans/service-plans-list-config.service.ts index 5892db9199..8da99f93f3 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/service-plans/service-plans-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/service-plans/service-plans-list-config.service.ts @@ -107,6 +107,8 @@ export class ServicePlansListConfigService implements IListConfig []; getSingleActions = () => []; getMultiFiltersConfigs = () => []; + getFilters = () => []; + setFilter = (id: string) => null; getColumns = () => this.columns; getDataSource = () => this.dataSource; } diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.types.ts b/src/frontend/packages/core/src/shared/components/list/list.component.types.ts index 8c0775fb64..97114515cb 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.types.ts +++ b/src/frontend/packages/core/src/shared/components/list/list.component.types.ts @@ -47,6 +47,11 @@ export interface IListConfig { * to the data sources transformEntities collection should be used to apply these custom settings to the data. */ getMultiFiltersConfigs: () => IListMultiFilterConfig[]; + /** + * Collection of filters + */ + getFilters: () => IListFilter[]; + setFilter: (id: string) => void; /** * Fetch an observable that will emit once the underlying config components have been created. For instance if the data source requires * something from the store which requires an async call @@ -120,6 +125,14 @@ export interface IListMultiFilterConfig { select: BehaviorSubject; } +export interface IListFilter { + dataSource: ListDataSource; + default?: boolean; + id: string; + label: string; + placeholder: string; +} + export interface IListMultiFilterConfigItem { label: string; item: any; @@ -144,6 +157,8 @@ export class ListConfig implements IListConfig { getColumns = (): ITableColumn[] => null; getDataSource = (): ListDataSource => null; getMultiFiltersConfigs = (): IListMultiFilterConfig[] => []; + getFilters = (): IListFilter[] => []; + setFilter = (id: string) => null; getInitialised = () => observableOf(true); } From f9ff28fbe58123621368e606ae979a608eb5459d Mon Sep 17 00:00:00 2001 From: Jordan Jensen Date: Wed, 7 Aug 2019 02:24:56 -0700 Subject: [PATCH 080/648] Show and select filters from ListConfig (SOC-9450) --- .../components/list/list.component.html | 25 +++++++++++++++++-- .../shared/components/list/list.component.ts | 19 ++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.html b/src/frontend/packages/core/src/shared/components/list/list.component.html index 01ffbfc939..9f6d4a7f8b 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.html +++ b/src/frontend/packages/core/src/shared/components/list/list.component.html @@ -69,12 +69,26 @@
    +
    + + Filter Selection + + + {{ filter.label }} + + + +
    + placeholder="{{ filterSelected?.placeholder || config.text?.filter || 'Filter'}}">
    @@ -201,4 +215,11 @@
    -
    \ No newline at end of file +
    + + + + diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.ts b/src/frontend/packages/core/src/shared/components/list/list.component.ts index c822bbd1ff..f56c4b241d 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list.component.ts @@ -69,6 +69,7 @@ import { defaultPaginationPageSizeOptionsTable, IGlobalListAction, IListConfig, + IListFilter, IMultiListAction, IOptionalAction, ListConfig, @@ -176,6 +177,9 @@ export class ListComponent implements OnInit, OnChanges, OnDestroy, AfterView }; private filterString = ''; private sortColumns: ITableColumn[]; + // private filterColumns: ITableColumn[]; + private filterColumns: IListFilter[]; + private filterSelected: IListFilter; private paginationWidgetToStore: Subscription; private filterWidgetToStore: Subscription; @@ -392,6 +396,13 @@ export class ListComponent implements OnInit, OnChanges, OnDestroy, AfterView return column.sort; }); + this.filterColumns = this.config.getFilters(); + if (!this.filterSelected) { + this.filterSelected = this.filterColumns.find(filterConfig => { + return filterConfig.default === true; + }); + } + const sortStoreToWidget = this.paginationController.sort$.pipe(tap((sort: ListSort) => { this.headerSort.value = sort.field; this.headerSort.direction = sort.direction; @@ -561,6 +572,9 @@ export class ListComponent implements OnInit, OnChanges, OnDestroy, AfterView this.uberSub, this.multiFilterChangesSub ); + if (this.filterColumns) { + this.filterColumns.forEach(filterConfig => filterConfig.dataSource.destroy()); + } if (this.dataSource) { this.dataSource.destroy(); } @@ -591,6 +605,11 @@ export class ListComponent implements OnInit, OnChanges, OnDestroy, AfterView }); } + updateListFilter(filterSelected: IListFilter, filterString: string) { + this.config.setFilter(filterSelected.id); + this.initialise(); + } + executeActionMultiple(listActionConfig: IMultiListAction) { const result = listActionConfig.action(Array.from(this.dataSource.selectedRows.values())); if (isObservable(result)) { From bc111215002407168c69701a378d91ee3a8ea0f4 Mon Sep 17 00:00:00 2001 From: Jordan Jensen Date: Fri, 9 Aug 2019 17:59:38 -0700 Subject: [PATCH 081/648] Reduce filters to single data source (SOC-9450) - Use action to set the filterKey - if the filterKey is not set use the default filter --- .../monocular-charts-list-config.service.ts | 1 - ...ocular-release-pods-list-config.service.ts | 1 - ...ar-release-services-list-config.service.ts | 1 - .../monocular-releases-list-config.service.ts | 1 - ...onocular-repository-list-config.service.ts | 1 - .../monocular-versions-list-config.service.ts | 1 - .../kubernetes-apps-list-config.service.ts | 1 - ...ubernetes-endpoints-list-config.service.ts | 1 - ...bernetes-namespaces-list-config.service.ts | 1 - .../kubernetes-nodes-data-source.ts | 69 -------------- .../kubernetes-nodes-list-config.service.ts | 93 ++++++++++++------- .../kubernetes-pods-list-config.service.ts | 1 - .../kubernetes-service-list-config.service.ts | 1 - .../list-data-source.ts | 3 +- .../local-list-controller.ts | 1 + .../app-event/cf-app-events-config.service.ts | 1 - .../cf-app-instances-config.service.ts | 1 - .../cf-app-variables-list-config.service.ts | 1 - .../list-types/app/cf-app-config.service.ts | 1 - .../list-types/base-cf/base-cf-list-config.ts | 1 - .../cf-endpoints-list-config.service.ts | 1 - .../cf-routes/cf-routes-list-config-base.ts | 1 - .../cf-routes-list-config.service.ts | 1 - .../cf-select-users-list-config.service.ts | 1 - .../cf-service-instances-list-config.base.ts | 1 - .../cf-services-list-config.service.ts | 1 - .../cf-user-service-instances-list-config.ts | 1 - .../cf-space-apps-list-config.service.ts | 1 - .../cf-spaces-list-config.service.ts | 1 - ...f-users-space-roles-list-config.service.ts | 1 - .../detach-apps-list-config.service.ts | 1 - .../endpoint/endpoints-list-config.service.ts | 1 - ...github-commits-list-config-base.service.ts | 1 - .../service-plans-list-config.service.ts | 1 - .../shared/components/list/list.component.ts | 38 ++++---- .../components/list/list.component.types.ts | 11 +-- .../store/src/actions/list.actions.ts | 1 + .../store/src/actions/pagination.actions.ts | 10 ++ ...agination-reducer-set-client-filter-key.ts | 22 +++++ .../pagination-reducer/pagination.reducer.ts | 4 + .../store/src/types/pagination.types.ts | 1 + 41 files changed, 127 insertions(+), 156 deletions(-) create mode 100644 src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-set-client-filter-key.ts diff --git a/custom-src/frontend/app/custom/helm/list-types/monocular-charts-list-config.service.ts b/custom-src/frontend/app/custom/helm/list-types/monocular-charts-list-config.service.ts index 30c24847b8..e87a82a970 100644 --- a/custom-src/frontend/app/custom/helm/list-types/monocular-charts-list-config.service.ts +++ b/custom-src/frontend/app/custom/helm/list-types/monocular-charts-list-config.service.ts @@ -85,7 +85,6 @@ export class MonocularChartsListConfig implements IListConfig { getDataSource = () => this.AppsDataSource; getMultiFiltersConfigs = () => [this.createRepositoryFilterConfig()]; getFilters = () => []; - setFilter = (id: string) => null; private createRepositoryFilterConfig(): IListMultiFilterConfig { return { diff --git a/custom-src/frontend/app/custom/helm/list-types/monocular-release-pods-list-config.service.ts b/custom-src/frontend/app/custom/helm/list-types/monocular-release-pods-list-config.service.ts index 069659dfa7..476e89492b 100644 --- a/custom-src/frontend/app/custom/helm/list-types/monocular-release-pods-list-config.service.ts +++ b/custom-src/frontend/app/custom/helm/list-types/monocular-release-pods-list-config.service.ts @@ -99,6 +99,5 @@ export class HelmReleasePodsListConfig implements IListConfig { public getSingleActions = () => []; public getMultiFiltersConfigs = () => []; public getFilters = () => []; - public setFilter = (id: string) => null; public getDataSource = () => this.dataSource; } diff --git a/custom-src/frontend/app/custom/helm/list-types/monocular-release-services-list-config.service.ts b/custom-src/frontend/app/custom/helm/list-types/monocular-release-services-list-config.service.ts index b8309ab0b6..5e6b9e6237 100644 --- a/custom-src/frontend/app/custom/helm/list-types/monocular-release-services-list-config.service.ts +++ b/custom-src/frontend/app/custom/helm/list-types/monocular-release-services-list-config.service.ts @@ -81,6 +81,5 @@ export class HelmReleaseServicesListConfig implements IListConfig []; public getMultiFiltersConfigs = () => []; public getFilters = () => []; - public setFilter = (id: string) => null; public getDataSource = () => this.dataSource; } diff --git a/custom-src/frontend/app/custom/helm/list-types/monocular-releases-list-config.service.ts b/custom-src/frontend/app/custom/helm/list-types/monocular-releases-list-config.service.ts index b6ab797c79..7f55c74e42 100644 --- a/custom-src/frontend/app/custom/helm/list-types/monocular-releases-list-config.service.ts +++ b/custom-src/frontend/app/custom/helm/list-types/monocular-releases-list-config.service.ts @@ -118,6 +118,5 @@ export class HelmReleasesListConfig implements IListConfig { public getSingleActions = () => []; public getMultiFiltersConfigs = () => []; public getFilters = () => []; - public setFilter = (id: string) => null; public getDataSource = () => this.dataSource; } diff --git a/custom-src/frontend/app/custom/helm/list-types/monocular-repository-list-config.service.ts b/custom-src/frontend/app/custom/helm/list-types/monocular-repository-list-config.service.ts index d6d38b928b..ab9eeb5447 100644 --- a/custom-src/frontend/app/custom/helm/list-types/monocular-repository-list-config.service.ts +++ b/custom-src/frontend/app/custom/helm/list-types/monocular-repository-list-config.service.ts @@ -97,6 +97,5 @@ export class MonocularRepositoryListConfig implements IListConfig public getSingleActions = () => []; public getMultiFiltersConfigs = () => []; public getFilters = () => []; - public setFilter = (id: string) => null; public getDataSource = () => this.dataSource; } diff --git a/custom-src/frontend/app/custom/helm/list-types/monocular-versions-list-config.service.ts b/custom-src/frontend/app/custom/helm/list-types/monocular-versions-list-config.service.ts index 55b487e6e7..9084ee4b17 100644 --- a/custom-src/frontend/app/custom/helm/list-types/monocular-versions-list-config.service.ts +++ b/custom-src/frontend/app/custom/helm/list-types/monocular-versions-list-config.service.ts @@ -53,6 +53,5 @@ export class HelmVersionsListConfig implements IListConfig { public getSingleActions = () => []; public getMultiFiltersConfigs = () => []; public getFilters = () => []; - public setFilter = (id: string) => null; public getDataSource = () => this.dataSource; } diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-apps/kubernetes-apps-list-config.service.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-apps/kubernetes-apps-list-config.service.ts index 1e83b450bf..28080d06f6 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-apps/kubernetes-apps-list-config.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-apps/kubernetes-apps-list-config.service.ts @@ -90,7 +90,6 @@ export class KubernetesAppsListConfigService implements IListConfig this.AppsDataSource; getMultiFiltersConfigs = () => []; getFilters = () => []; - setFilter = (id: string) => null; constructor( store: Store, diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-list-config.service.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-list-config.service.ts index 409262d03c..84904843f9 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-list-config.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-list-config.service.ts @@ -54,6 +54,5 @@ export class KubernetesEndpointsListConfigService implements IListConfig []; public getMultiFiltersConfigs = () => []; public getFilters = () => []; - public setFilter = (id: string) => null; public getDataSource = () => this.dataSource; } diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-list-config.service.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-list-config.service.ts index fc4881c1a4..84344a31c0 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-list-config.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-list-config.service.ts @@ -75,7 +75,6 @@ export class KubernetesNamespacesListConfigService implements IListConfig this.podsDataSource; getMultiFiltersConfigs = () => []; getFilters = () => []; - setFilter = (id: string) => null; getInitialised = () => this.initialised$; constructor( diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-data-source.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-data-source.ts index a1ba148334..1c9a075977 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-data-source.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-data-source.ts @@ -14,7 +14,6 @@ import { getKubeAPIResourceGuid } from '../../store/kube.selectors'; import { KubernetesNode } from '../../store/kube.types'; import { GetKubernetesNodes } from '../../store/kubernetes.actions'; import { kubernetesNodesSchemaKey } from '../../store/kubernetes.entities'; -import { PaginationEntityState } from '../../../../../../store/src/types/pagination.types'; export class KubernetesNodesDataSource extends ListDataSource { @@ -36,71 +35,3 @@ export class KubernetesNodesDataSource extends ListDataSource { }); } } - -export class LabelsKubernetesNodesDataSource extends KubernetesNodesDataSource { - - constructor( - store: Store, - kubeGuid: BaseKubeGuid, - listConfig: IListConfig - ) { - super( - store, - kubeGuid, - listConfig, - [ - (entities: KubernetesNode[], paginationStore: PaginationEntityState) => { - const filterString = paginationStore.clientPagination.filter.string.toUpperCase(); - return entities.filter(node => { - return Object.entries(node.metadata.labels).some(([label, value]) => { - label = label.toUpperCase(); - value = value.toUpperCase(); - return label.includes(filterString) || value.includes(filterString); - }); - }); - } - ] - ); - } -} - -export class NameKubernetesNodesDataSource extends KubernetesNodesDataSource { - - constructor( - store: Store, - kubeGuid: BaseKubeGuid, - listConfig: IListConfig - ) { - super( - store, - kubeGuid, - listConfig, - [{ type: 'filter', field: 'metadata.name' }] - ); - } -} - -export class IPAddressKubernetesNodesDataSource extends KubernetesNodesDataSource { - - constructor( - store: Store, - kubeGuid: BaseKubeGuid, - listConfig: IListConfig - ) { - super( - store, - kubeGuid, - listConfig, - [ - (entities: KubernetesNode[], paginationState: PaginationEntityState) => { - return entities.filter(node => { - const internalIP: string = node.status.addresses.find(address => { - return address.type === 'InternalIP'; - }).address; - return internalIP.toUpperCase().includes(paginationState.clientPagination.filter.string.toUpperCase()); - }); - } - ] - ); - } -} diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts index b4b496ae2c..fa0fb9aebc 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts @@ -3,6 +3,8 @@ import { Store } from '@ngrx/store'; import { AppState } from '../../../../../../store/src/app-state'; import { ITableColumn } from '../../../../shared/components/list/list-table/table.types'; +import { DataFunction } from '../../../../shared/components/list/data-sources-controllers/list-data-source'; +import { PaginationEntityState } from '../../../../../../store/src/types/pagination.types'; import { IListConfig, IListFilter, ListViewTypes } from '../../../../shared/components/list/list.component.types'; import { BaseKubeGuid } from '../../kubernetes-page.types'; import { ConditionType, KubernetesNode } from '../../store/kube.types'; @@ -11,15 +13,19 @@ import { getConditionSort } from '../kube-sort.helper'; import { ConditionCellComponent, SubtleConditionCellComponent } from './condition-cell/condition-cell.component'; import { KubernetesNodeCapacityComponent } from './kubernetes-node-capacity/kubernetes-node-capacity.component'; import { KubernetesNodeLinkComponent } from './kubernetes-node-link/kubernetes-node-link.component'; -import { - LabelsKubernetesNodesDataSource, - NameKubernetesNodesDataSource, - IPAddressKubernetesNodesDataSource -} from './kubernetes-nodes-data-source'; +import { KubernetesNodesDataSource } from './kubernetes-nodes-data-source'; import { NodePodCountComponent } from './node-pod-count/node-pod-count.component'; +export enum KubernetesNodesListFilterKeys { + NAME = 'name', + IP_ADDRESS = 'ip-address', + LABELS = 'labels' +} + @Injectable() export class KubernetesNodesListConfigService implements IListConfig { + dataSource: KubernetesNodesDataSource; + columns: Array> = [ { columnId: 'name', headerCell: () => 'Name', @@ -70,8 +76,24 @@ export class KubernetesNodesListConfigService implements IListConfig[]; - filterSelected: IListFilter; + filters: IListFilter[] = [ + { + default: true, + key: KubernetesNodesListFilterKeys.NAME, + label: 'Name', + placeholder: 'Filter by Name' + }, + { + key: KubernetesNodesListFilterKeys.LABELS, + label: 'Labels', + placeholder: 'Filter by Labels' + }, + { + key: KubernetesNodesListFilterKeys.IP_ADDRESS, + label: 'IP Address', + placeholder: 'Filter by IP Address' + } + ]; pageSizeOptions = defaultHelmKubeListPageSize; viewType = ListViewTypes.TABLE_ONLY; @@ -86,39 +108,46 @@ export class KubernetesNodesListConfigService implements IListConfig []; getSingleActions = () => []; getColumns = () => this.columns; - getDataSource = () => this.filterSelected.dataSource; + getDataSource = () => this.dataSource; getMultiFiltersConfigs = () => []; - getFilters = (): IListFilter[] => this.filters; - setFilter = (id: string) => { - this.filterSelected = this.filters.find(filter => filter.id === id); - } + getFilters = (): IListFilter[] => this.filters; constructor( store: Store, kubeId: BaseKubeGuid, ) { - this.filters = [ - { - dataSource: new NameKubernetesNodesDataSource(store, kubeId, this), - default: true, - id: 'name', - label: 'Name', - placeholder: 'Filter by Name' - }, - { - dataSource: new LabelsKubernetesNodesDataSource(store, kubeId, this), - id: 'labels', - label: 'Labels', - placeholder: 'Filter by Labels' - }, - { - dataSource: new IPAddressKubernetesNodesDataSource(store, kubeId, this), - id: 'ip-address', - label: 'IP Address', - placeholder: 'Filter by IP Address' + const transformEntities: DataFunction[] = [ + (entities: KubernetesNode[], paginationState: PaginationEntityState) => { + const filterKey = paginationState.clientPagination.filter.filterKey; + const filterString = paginationState.clientPagination.filter.string.toUpperCase(); + + switch (filterKey) { + case KubernetesNodesListFilterKeys.IP_ADDRESS: + return entities.filter(node => { + const internalIP: string = node.status.addresses.find(address => { + return address.type === 'InternalIP'; + }).address; + return internalIP.toUpperCase().includes(filterString); + }); + + case KubernetesNodesListFilterKeys.LABELS: + return entities.filter(node => { + return Object.entries(node.metadata.labels).some(([label, value]) => { + label = label.toUpperCase(); + value = value.toUpperCase(); + return label.includes(filterString) || value.includes(filterString); + }); + }); + + case KubernetesNodesListFilterKeys.NAME: + default: + return entities.filter(node => { + return node.metadata.name.toUpperCase().includes(filterString); + }); + } } ]; - this.filterSelected = this.filters.find(filter => filter.default === true); + this.dataSource = new KubernetesNodesDataSource(store, kubeId, this, transformEntities); } } diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pods-list-config.service.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pods-list-config.service.ts index 629af156ed..a389753548 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pods-list-config.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pods-list-config.service.ts @@ -104,7 +104,6 @@ export class KubernetesPodsListConfigService implements IListConfig this.podsDataSource; getMultiFiltersConfigs = () => []; getFilters = () => []; - setFilter = (id: string) => null; constructor( store: Store, diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-service-list-config.service.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-service-list-config.service.ts index 6da50d7cb7..2579b31e86 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-service-list-config.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-service-list-config.service.ts @@ -61,5 +61,4 @@ export abstract class BaseKubernetesServicesListConfig implements IListConfig this.columns; getMultiFiltersConfigs = () => []; getFilters = () => []; - setFilter = (id: string) => null; } diff --git a/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source.ts b/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source.ts index eda6983908..1365cf3d45 100644 --- a/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source.ts +++ b/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source.ts @@ -471,7 +471,8 @@ export abstract class ListDataSource extends DataSource implements return this.pagination$.pipe( map(pag => ({ string: this.isLocal ? pag.clientPagination.filter.string : this.getFilterFromParams(pag), - items: { ...pag.clientPagination.filter.items } + items: { ...pag.clientPagination.filter.items }, + filterKey: pag.clientPagination.filter.filterKey, })), tag('list-filter') ); diff --git a/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/local-list-controller.ts b/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/local-list-controller.ts index 21cb9b6085..84888c0ce9 100644 --- a/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/local-list-controller.ts +++ b/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/local-list-controller.ts @@ -136,6 +136,7 @@ export class LocalListController { + (paginationEntity.params['order-direction-field'] || '') + ',' + (paginationEntity.params['order-direction'] || '') + ',' + paginationEntity.clientPagination.filter.string + ',' + + paginationEntity.clientPagination.filter.filterKey + ',' + paginationEntity.forcedLocalPage + Object.values(paginationEntity.clientPagination.filter.items); // Some outlier cases actually fetch independently from this list (looking at you app variables) diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/app-event/cf-app-events-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/app-event/cf-app-events-config.service.ts index 9146ab9467..2c23cde46c 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/app-event/cf-app-events-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/app-event/cf-app-events-config.service.ts @@ -53,5 +53,4 @@ export class CfAppEventsConfigService extends ListConfig implements getDataSource = () => this.eventSource; getMultiFiltersConfigs = () => []; getFilters = () => []; - setFilter = (id: string) => null; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.ts index 54dad54486..82c3f8b737 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.ts @@ -221,7 +221,6 @@ export class CfAppInstancesConfigService implements IListConfig getDataSource = () => this.instancesSource; getMultiFiltersConfigs = () => []; getFilters = () => []; - setFilter = (id: string) => null; getInitialised = () => this.initialised$; private createMetricsResults(entityServiceFactory: EntityServiceFactory) { diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/app-variables/cf-app-variables-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/app-variables/cf-app-variables-list-config.service.ts index 1c288d4be0..1e6cc952ae 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/app-variables/cf-app-variables-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/app-variables/cf-app-variables-list-config.service.ts @@ -132,7 +132,6 @@ export class CfAppVariablesListConfigService implements IListConfig this.envVarsDataSource; getMultiFiltersConfigs = () => []; getFilters = () => []; - setFilter = (id: string) => null; constructor( private store: Store, diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/app/cf-app-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/app/cf-app-config.service.ts index 9abc1afdcf..d311bf322f 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/app/cf-app-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/app/cf-app-config.service.ts @@ -145,6 +145,5 @@ export class CfAppConfigService extends ListConfig implements IList getDataSource = () => this.appsDataSource; getMultiFiltersConfigs = () => this.multiFilterConfigs; getFilters = () => []; - setFilter = (id: string) => null; getInitialised = () => this.initialised$; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/base-cf/base-cf-list-config.ts b/src/frontend/packages/core/src/shared/components/list/list-types/base-cf/base-cf-list-config.ts index 5f0844b271..97edb4b016 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/base-cf/base-cf-list-config.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/base-cf/base-cf-list-config.ts @@ -18,5 +18,4 @@ export class BaseCfListConfig implements IListConfig { getSingleActions = () => []; getMultiFiltersConfigs = () => []; getFilters = () => []; - setFilter = (id: string) => null; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/cf-endpoints/cf-endpoints-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/cf-endpoints/cf-endpoints-list-config.service.ts index cd617b7535..91fd7eba14 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/cf-endpoints/cf-endpoints-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/cf-endpoints/cf-endpoints-list-config.service.ts @@ -50,6 +50,5 @@ export class CFEndpointsListConfigService implements IListConfig public getSingleActions = () => []; public getMultiFiltersConfigs = () => []; public getFilters = () => []; - public setFilter = (id: string) => null; public getDataSource = () => this.dataSource; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/cf-routes/cf-routes-list-config-base.ts b/src/frontend/packages/core/src/shared/components/list/list-types/cf-routes/cf-routes-list-config-base.ts index 459a476e3a..40bcf2a8be 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/cf-routes/cf-routes-list-config-base.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/cf-routes/cf-routes-list-config-base.ts @@ -206,7 +206,6 @@ export abstract class CfRoutesListConfigBase implements IListConfig getColumns = () => this.columns; getMultiFiltersConfigs = () => []; getFilters = () => []; - setFilter = (id: string) => null; /** * Creates an instance of CfRoutesListConfigBase. diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/cf-routes/cf-routes-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/cf-routes/cf-routes-list-config.service.ts index 9b96a62922..dfd9b266b0 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/cf-routes/cf-routes-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/cf-routes/cf-routes-list-config.service.ts @@ -70,7 +70,6 @@ export class CfRoutesListConfigService extends CfRoutesListConfigBase implements ]; this.getMultiFiltersConfigs = () => multiFilterConfigs; this.getFilters = () => []; - this.setFilter = (id: string) => null; initCfOrgSpaceService(store, cfOrgSpaceService, this.dataSource.masterAction.entityKey, this.dataSource.masterAction.paginationKey).subscribe(); diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/cf-select-users/cf-select-users-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/cf-select-users/cf-select-users-list-config.service.ts index 5b4173600f..8c2be47670 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/cf-select-users/cf-select-users-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/cf-select-users/cf-select-users-list-config.service.ts @@ -133,7 +133,6 @@ export class CfSelectUsersListConfigService implements IListConfig []; getMultiFiltersConfigs = () => []; getFilters = () => []; - setFilter = (id: string) => null; getDataSource = () => this.dataSource; getInitialised = () => this.initialised; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts b/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts index 1890f455ad..3d2d967356 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts @@ -194,7 +194,6 @@ export class CfServiceInstancesListConfigBase implements IListConfig [this.listActionEdit, this.listActionDetach, this.listActionDelete]; getMultiFiltersConfigs = () => []; getFilters = () => []; - setFilter = (id: string) => null; getColumns = () => this.serviceInstanceColumns; getDataSource = () => null; diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.ts index 8abe831628..bc171f066d 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.ts @@ -85,6 +85,5 @@ export class CfServicesListConfigService implements IListConfig { getSingleActions = () => []; getMultiFiltersConfigs = () => this.multiFilterConfigs; getFilters = () => []; - setFilter = (id: string) => null; getDataSource = () => this.dataSource; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-user-service-instances-list-config.ts b/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-user-service-instances-list-config.ts index e8ef1501f2..80e7f86a52 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-user-service-instances-list-config.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/cf-services/cf-user-service-instances-list-config.ts @@ -198,7 +198,6 @@ export class CfUserServiceInstancesListConfigBase implements IListConfig [this.listActionEdit, this.listActionDetach, this.listActionDelete]; getMultiFiltersConfigs = () => []; getFilters = () => []; - setFilter = (id: string) => null; getColumns = () => this.serviceInstanceColumns; getDataSource = () => this.dataSource; diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-list-config.service.ts index ed82576976..90c7061031 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-list-config.service.ts @@ -89,6 +89,5 @@ export class CfSpaceAppsListConfigService implements IListConfig { getSingleActions = () => []; getMultiFiltersConfigs = () => []; getFilters = () => []; - setFilter = (id: string) => null; getDataSource = () => this.dataSource; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/cf-spaces/cf-spaces-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/cf-spaces/cf-spaces-list-config.service.ts index 6935a22986..463195a0f5 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/cf-spaces/cf-spaces-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/cf-spaces/cf-spaces-list-config.service.ts @@ -56,6 +56,5 @@ export class CfSpacesListConfigService implements IListConfig []; getMultiFiltersConfigs = () => []; getFilters = () => []; - setFilter = (id: string) => null; getDataSource = () => this.dataSource; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/cf-users-org-space-roles/cf-users-space-roles-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/cf-users-org-space-roles/cf-users-space-roles-list-config.service.ts index 70517de420..d0332b6954 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/cf-users-org-space-roles/cf-users-space-roles-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/cf-users-org-space-roles/cf-users-space-roles-list-config.service.ts @@ -86,7 +86,6 @@ export class CfUsersSpaceRolesListConfigService implements IListConfig []; getMultiFiltersConfigs = () => []; getFilters = () => []; - setFilter = (id: string) => null; getDataSource = () => this.dataSource; public getInitialised = () => this.initialised; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/detach-apps/detach-apps-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/detach-apps/detach-apps-list-config.service.ts index 394ea1be37..50fc128462 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/detach-apps/detach-apps-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/detach-apps/detach-apps-list-config.service.ts @@ -58,6 +58,5 @@ export class DetachAppsListConfigService implements IListConfig { getSingleActions = () => []; getMultiFiltersConfigs = () => []; getFilters = () => []; - setFilter = (id: string) => null; getDataSource = () => this.dataSource; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts index d8d6cb9fa4..e7b3158731 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts @@ -135,5 +135,4 @@ export class EndpointsListConfigService implements IListConfig { public getDataSource = () => this.dataSource; public getMultiFiltersConfigs = () => []; public getFilters = () => []; - public setFilter = (id: string) => null; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/github-commits/github-commits-list-config-base.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/github-commits/github-commits-list-config-base.service.ts index 585b8a8171..8684e94c7d 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/github-commits/github-commits-list-config-base.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/github-commits/github-commits-list-config-base.service.ts @@ -93,7 +93,6 @@ export abstract class GithubCommitsListConfigServiceBase implements IListConfig< public getSingleActions = () => []; public getMultiFiltersConfigs = () => []; public getFilters = () => []; - public setFilter = (id: string) => null; public getDataSource = () => this.dataSource; public getInitialised = () => this.initialised; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/service-plans/service-plans-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/service-plans/service-plans-list-config.service.ts index 8da99f93f3..729fb7cbd1 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/service-plans/service-plans-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/service-plans/service-plans-list-config.service.ts @@ -108,7 +108,6 @@ export class ServicePlansListConfigService implements IListConfig []; getMultiFiltersConfigs = () => []; getFilters = () => []; - setFilter = (id: string) => null; getColumns = () => this.columns; getDataSource = () => this.dataSource; } diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.ts b/src/frontend/packages/core/src/shared/components/list/list.component.ts index f56c4b241d..68b48dfcc4 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list.component.ts @@ -49,7 +49,7 @@ import { ListView, SetListViewAction, } from '../../../../../store/src/actions/list.actions'; -import { SetPage } from '../../../../../store/src/actions/pagination.actions'; +import { SetPage, SetClientFilterKey } from '../../../../../store/src/actions/pagination.actions'; import { AppState } from '../../../../../store/src/app-state'; import { entityFactory } from '../../../../../store/src/helpers/entity-factory'; import { ActionState } from '../../../../../store/src/reducers/api-request-reducer/types'; @@ -177,9 +177,8 @@ export class ListComponent implements OnInit, OnChanges, OnDestroy, AfterView }; private filterString = ''; private sortColumns: ITableColumn[]; - // private filterColumns: ITableColumn[]; - private filterColumns: IListFilter[]; - private filterSelected: IListFilter; + private filterColumns: IListFilter[]; + private filterSelected: IListFilter; private paginationWidgetToStore: Subscription; private filterWidgetToStore: Subscription; @@ -396,20 +395,25 @@ export class ListComponent implements OnInit, OnChanges, OnDestroy, AfterView return column.sort; }); - this.filterColumns = this.config.getFilters(); - if (!this.filterSelected) { - this.filterSelected = this.filterColumns.find(filterConfig => { - return filterConfig.default === true; - }); - } - const sortStoreToWidget = this.paginationController.sort$.pipe(tap((sort: ListSort) => { this.headerSort.value = sort.field; this.headerSort.direction = sort.direction; })); + this.filterColumns = this.config.getFilters(); + const filterStoreToWidget = this.paginationController.filter$.pipe(tap((paginationFilter: ListFilter) => { this.filterString = paginationFilter.string; + + const filterKey = paginationFilter.filterKey; + if (filterKey) { + this.filterSelected = this.filterColumns.find(filterConfig => { + return filterConfig.key === filterKey; + }); + } else if (this.filterColumns) { + this.filterSelected = this.filterColumns.find(filterConfig => filterConfig.default); + } + // Pipe store values to filter managers. This ensures any changes such as automatically selected orgs/spaces are shown in the drop // downs (change org to one with one space results in that space being selected) Object.values(this.multiFilterManagers).forEach((filterManager: MultiFilterManager, index: number) => { @@ -572,9 +576,6 @@ export class ListComponent implements OnInit, OnChanges, OnDestroy, AfterView this.uberSub, this.multiFilterChangesSub ); - if (this.filterColumns) { - this.filterColumns.forEach(filterConfig => filterConfig.dataSource.destroy()); - } if (this.dataSource) { this.dataSource.destroy(); } @@ -605,9 +606,12 @@ export class ListComponent implements OnInit, OnChanges, OnDestroy, AfterView }); } - updateListFilter(filterSelected: IListFilter, filterString: string) { - this.config.setFilter(filterSelected.id); - this.initialise(); + updateListFilter(filterSelected: IListFilter, filterString: string) { + this.store.dispatch(new SetClientFilterKey( + this.dataSource.entityKey, + this.dataSource.paginationKey, + filterSelected.key + )); } executeActionMultiple(listActionConfig: IMultiListAction) { diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.types.ts b/src/frontend/packages/core/src/shared/components/list/list.component.types.ts index 97114515cb..b6032c5922 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.types.ts +++ b/src/frontend/packages/core/src/shared/components/list/list.component.types.ts @@ -50,8 +50,7 @@ export interface IListConfig { /** * Collection of filters */ - getFilters: () => IListFilter[]; - setFilter: (id: string) => void; + getFilters: () => IListFilter[]; /** * Fetch an observable that will emit once the underlying config components have been created. For instance if the data source requires * something from the store which requires an async call @@ -125,10 +124,9 @@ export interface IListMultiFilterConfig { select: BehaviorSubject; } -export interface IListFilter { - dataSource: ListDataSource; +export interface IListFilter { default?: boolean; - id: string; + key: string; label: string; placeholder: string; } @@ -157,8 +155,7 @@ export class ListConfig implements IListConfig { getColumns = (): ITableColumn[] => null; getDataSource = (): ListDataSource => null; getMultiFiltersConfigs = (): IListMultiFilterConfig[] => []; - getFilters = (): IListFilter[] => []; - setFilter = (id: string) => null; + getFilters = (): IListFilter[] => []; getInitialised = () => observableOf(true); } diff --git a/src/frontend/packages/store/src/actions/list.actions.ts b/src/frontend/packages/store/src/actions/list.actions.ts index 5291cba081..adccb7c3f9 100644 --- a/src/frontend/packages/store/src/actions/list.actions.ts +++ b/src/frontend/packages/store/src/actions/list.actions.ts @@ -20,6 +20,7 @@ export class ListFilter { items: { [key: string]: any; }; + filterKey?: string; } export const ListStateActionTypes = { diff --git a/src/frontend/packages/store/src/actions/pagination.actions.ts b/src/frontend/packages/store/src/actions/pagination.actions.ts index 0d2e20c2e0..4d1d99287c 100644 --- a/src/frontend/packages/store/src/actions/pagination.actions.ts +++ b/src/frontend/packages/store/src/actions/pagination.actions.ts @@ -12,6 +12,7 @@ export const SET_RESULT_COUNT = '[Pagination] Set result count'; export const SET_CLIENT_PAGE_SIZE = '[Pagination] Set client page size'; export const SET_CLIENT_PAGE = '[Pagination] Set client page'; export const SET_CLIENT_FILTER = '[Pagination] Set client filter'; +export const SET_CLIENT_FILTER_KEY = '[Pagination] Set client filter key'; export const SET_PARAMS = '[Pagination] Set Params'; export const SET_INITIAL_PARAMS = '[Pagination] Set initial params'; export const ADD_PARAMS = '[Pagination] Add Params'; @@ -114,6 +115,15 @@ export class SetClientFilter implements BasePaginatedAction { type = SET_CLIENT_FILTER; } +export class SetClientFilterKey implements BasePaginatedAction { + constructor( + public entityKey: string, + public paginationKey: string, + public filterKey: string, + ) {} + type = SET_CLIENT_FILTER_KEY; +} + export class SetParams implements BasePaginatedAction { constructor( public entityKey: string, diff --git a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-set-client-filter-key.ts b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-set-client-filter-key.ts new file mode 100644 index 0000000000..9e88445cc7 --- /dev/null +++ b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-set-client-filter-key.ts @@ -0,0 +1,22 @@ +import { PaginationEntityState } from '../../types/pagination.types'; +import { SetClientFilterKey } from '../../actions/pagination.actions'; +import { spreadClientPagination } from './pagination-reducer.helper'; + +export function paginationSetClientFilterKey(state: PaginationEntityState, action: SetClientFilterKey) { + const clientPagination = spreadClientPagination(state.clientPagination); + + return { + ...state, + error: false, + clientPagination: { + ...clientPagination, + filter: { + ...clientPagination.filter, + items: { + ...clientPagination.filter.items, + }, + filterKey: action.filterKey, + } + } + }; +} diff --git a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination.reducer.ts b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination.reducer.ts index d5fffcd8fd..59d5ed5763 100644 --- a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination.reducer.ts +++ b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination.reducer.ts @@ -12,6 +12,7 @@ import { REMOVE_PARAMS, RESET_PAGINATION, SET_CLIENT_FILTER, + SET_CLIENT_FILTER_KEY, SET_CLIENT_PAGE, SET_CLIENT_PAGE_SIZE, SET_INITIAL_PARAMS, @@ -34,6 +35,7 @@ import { paginationMaxReached } from './pagination-reducer-max-reached'; import { paginationRemoveParams } from './pagination-reducer-remove-params'; import { getDefaultPaginationEntityState, paginationResetPagination } from './pagination-reducer-reset-pagination'; import { paginationSetClientFilter } from './pagination-reducer-set-client-filter'; +import { paginationSetClientFilterKey } from './pagination-reducer-set-client-filter-key'; import { paginationSetClientPage } from './pagination-reducer-set-client-page'; import { paginationSetClientPageSize } from './pagination-reducer-set-client-page-size'; import { paginationSetPage } from './pagination-reducer-set-page'; @@ -79,6 +81,8 @@ const getPaginationUpdater = (types: [string, string, string]) => { return paginationSetClientPage(state, action); case SET_CLIENT_FILTER: return paginationSetClientFilter(state, action); + case SET_CLIENT_FILTER_KEY: + return paginationSetClientFilterKey(state, action); case SET_PAGE_BUSY: return paginationPageBusy(state, action); default: diff --git a/src/frontend/packages/store/src/types/pagination.types.ts b/src/frontend/packages/store/src/types/pagination.types.ts index 0f3fa22b30..65d70caf42 100644 --- a/src/frontend/packages/store/src/types/pagination.types.ts +++ b/src/frontend/packages/store/src/types/pagination.types.ts @@ -24,6 +24,7 @@ export interface PaginationClientFilter { items: { [key: string]: any; }; + filterKey?: string; } export interface PaginationClientPagination { From 0c91cf5c235febe931b84820e10a44f2ca2b6eac Mon Sep 17 00:00:00 2001 From: Jordan Jensen Date: Fri, 9 Aug 2019 18:07:50 -0700 Subject: [PATCH 082/648] Expand IListConfig getFilters comment (SOC-9450) --- .../core/src/shared/components/list/list.component.types.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.types.ts b/src/frontend/packages/core/src/shared/components/list/list.component.types.ts index b6032c5922..f7fa22a8d9 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.types.ts +++ b/src/frontend/packages/core/src/shared/components/list/list.component.types.ts @@ -48,7 +48,9 @@ export interface IListConfig { */ getMultiFiltersConfigs: () => IListMultiFilterConfig[]; /** - * Collection of filters + * Collection of filter definitions to support filtering across multiple fields in a list. + * When the filter is selected in a dropdown the filterString filters results using the chosen field. + * Combined with a transformEntities DataFunction that consumes the filterKey. */ getFilters: () => IListFilter[]; /** From f1d83c4e722c9d5320855861f0e30c7f2b3509a3 Mon Sep 17 00:00:00 2001 From: Jordan Jensen Date: Mon, 12 Aug 2019 17:14:19 -0700 Subject: [PATCH 083/648] Update list component tests (SOC-9450) --- .../shared/components/list/list.component.spec.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.spec.ts b/src/frontend/packages/core/src/shared/components/list/list.component.spec.ts index 939407b770..148e5d9ce0 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/list/list.component.spec.ts @@ -42,6 +42,7 @@ describe('ListComponent', () => { getInitialised: () => null, getMultiActions: () => null, getMultiFiltersConfigs: () => null, + getFilters: () => null, getSingleActions: () => null, isLocal: false, pageSizeOptions: [1], @@ -149,6 +150,7 @@ describe('ListComponent', () => { describe('Header', () => { it('Nothing enabled', () => { component.config.getMultiFiltersConfigs = () => []; + component.config.getFilters = () => []; component.config.enableTextFilter = false; component.config.viewType = ListViewTypes.CARD_ONLY; component.config.defaultView = 'card' as ListView; @@ -206,6 +208,19 @@ describe('ListComponent', () => { } ]; }; + component.config.getFilters = () => ([ + { + default: true, + key: 'a', + label: 'A', + placeholder: 'Filter by A' + }, + { + key: 'b', + label: 'B', + placeholder: 'Filter by B' + } + ]); component.config.enableTextFilter = true; component.config.viewType = ListViewTypes.CARD_ONLY; component.config.defaultView = 'card' as ListView; From d0bd4f37ab8a7adc35e909b0d77b22d9625f9edb Mon Sep 17 00:00:00 2001 From: Jordan Jensen Date: Fri, 23 Aug 2019 16:36:08 -0700 Subject: [PATCH 084/648] Handle no default filter selection better (SOC-9450) - Disable filter string if no default filter is selected --- .../kubernetes-nodes/kubernetes-nodes-list-config.service.ts | 5 +++-- .../core/src/shared/components/list/list.component.html | 4 ++-- .../core/src/shared/components/list/list.component.ts | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts index fa0fb9aebc..97a30a84e8 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts @@ -100,7 +100,7 @@ export class KubernetesNodesListConfigService implements IListConfig { return node.metadata.name.toUpperCase().includes(filterString); }); + default: + return entities; } } ]; diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.html b/src/frontend/packages/core/src/shared/components/list/list.component.html index 9f6d4a7f8b..ea7ecb4e46 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.html +++ b/src/frontend/packages/core/src/shared/components/list/list.component.html @@ -75,7 +75,7 @@ Filter Selection {{ filter.label }} @@ -87,7 +87,7 @@ [hidden]="!config.enableTextFilter || (!(hasRows$ | async) && !filter) || (dataSource.isAdding$ | async) || (dataSource.maxedResults$ | async)">
    diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.ts b/src/frontend/packages/core/src/shared/components/list/list.component.ts index 68b48dfcc4..4533e67732 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list.component.ts @@ -412,6 +412,7 @@ export class ListComponent implements OnInit, OnChanges, OnDestroy, AfterView }); } else if (this.filterColumns) { this.filterSelected = this.filterColumns.find(filterConfig => filterConfig.default); + this.updateListFilter(this.filterSelected); } // Pipe store values to filter managers. This ensures any changes such as automatically selected orgs/spaces are shown in the drop @@ -606,7 +607,7 @@ export class ListComponent implements OnInit, OnChanges, OnDestroy, AfterView }); } - updateListFilter(filterSelected: IListFilter, filterString: string) { + updateListFilter(filterSelected: IListFilter) { this.store.dispatch(new SetClientFilterKey( this.dataSource.entityKey, this.dataSource.paginationKey, From aef717614fd7216ddbe1ce125604cc0f1dc9f600 Mon Sep 17 00:00:00 2001 From: Jordan Jensen Date: Fri, 23 Aug 2019 16:46:18 -0700 Subject: [PATCH 085/648] Only update list filter if a default is found (SOC-9450) --- .../core/src/shared/components/list/list.component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.ts b/src/frontend/packages/core/src/shared/components/list/list.component.ts index 4533e67732..2d1e26df65 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list.component.ts @@ -412,7 +412,9 @@ export class ListComponent implements OnInit, OnChanges, OnDestroy, AfterView }); } else if (this.filterColumns) { this.filterSelected = this.filterColumns.find(filterConfig => filterConfig.default); - this.updateListFilter(this.filterSelected); + if (this.filterSelected) { + this.updateListFilter(this.filterSelected); + } } // Pipe store values to filter managers. This ensures any changes such as automatically selected orgs/spaces are shown in the drop From 24cfeccf267d7ded87c50510796b80c9f5c5673c Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 30 Aug 2019 08:32:46 +0100 Subject: [PATCH 086/648] Add classes so that e2e tests still work --- .../app/custom/suse-welcome/suse-welcome.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom-src/frontend/app/custom/suse-welcome/suse-welcome.component.html b/custom-src/frontend/app/custom/suse-welcome/suse-welcome.component.html index b2a52c6faa..77b4aa744f 100644 --- a/custom-src/frontend/app/custom/suse-welcome/suse-welcome.component.html +++ b/custom-src/frontend/app/custom/suse-welcome/suse-welcome.component.html @@ -1,8 +1,8 @@ -
    +
    settings_ethernet -
    There are no registered endpoints
    +
    There are no registered endpoints
    From e2ce7e2e61aa8dd01c5386d35101627e2fd93d37 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 30 Aug 2019 11:58:42 +0100 Subject: [PATCH 087/648] Remove debug logging message --- src/jetstream/plugins/kubernetes/helm_client.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/jetstream/plugins/kubernetes/helm_client.go b/src/jetstream/plugins/kubernetes/helm_client.go index 154c71a140..8cfeef9cb0 100644 --- a/src/jetstream/plugins/kubernetes/helm_client.go +++ b/src/jetstream/plugins/kubernetes/helm_client.go @@ -30,8 +30,6 @@ func (c *KubernetesSpecification) GetHelmClient(endpointGUID, userID string) (he return nil, nil, nil, errors.New("Helm: Can not get user token for endpoint") } - log.Error("GetHelmClient (2)") - config, err := c.GetConfigForEndpoint(cnsiRecord.APIEndpoint.String(), tokenRecord) if err != nil { log.Errorf("Helm: Could not get config for endpoint: %s", err) From 3376e76716565fad18c6f2cc971ad4304e7e66bf Mon Sep 17 00:00:00 2001 From: Jordan Jensen Date: Fri, 30 Aug 2019 16:26:31 -0700 Subject: [PATCH 088/648] Remove unnecessary additions (SOC-9450) --- .../core/src/shared/components/list/list.component.html | 7 ------- .../pagination-reducer-set-client-filter-key.ts | 3 --- 2 files changed, 10 deletions(-) diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.html b/src/frontend/packages/core/src/shared/components/list/list.component.html index ea7ecb4e46..6447c2150e 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.html +++ b/src/frontend/packages/core/src/shared/components/list/list.component.html @@ -216,10 +216,3 @@
    - - - - diff --git a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-set-client-filter-key.ts b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-set-client-filter-key.ts index 9e88445cc7..5db751052d 100644 --- a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-set-client-filter-key.ts +++ b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-set-client-filter-key.ts @@ -12,9 +12,6 @@ export function paginationSetClientFilterKey(state: PaginationEntityState, actio ...clientPagination, filter: { ...clientPagination.filter, - items: { - ...clientPagination.filter.items, - }, filterKey: action.filterKey, } } From 24727d3932a93f197bf8e2fed0c60568519ad52e Mon Sep 17 00:00:00 2001 From: Jordan Jensen Date: Fri, 30 Aug 2019 16:39:23 -0700 Subject: [PATCH 089/648] Return entities if no filter string (SOC-9450) --- .../kubernetes-nodes-list-config.service.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts index 97a30a84e8..bb7438bbdd 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts @@ -118,8 +118,13 @@ export class KubernetesNodesListConfigService implements IListConfig[] = [ (entities: KubernetesNode[], paginationState: PaginationEntityState) => { + const filterString = paginationState.clientPagination.filter.string; + + if (!filterString) { + return entities; + } + const filterKey = paginationState.clientPagination.filter.filterKey; - const filterString = paginationState.clientPagination.filter.string.toUpperCase(); switch (filterKey) { case KubernetesNodesListFilterKeys.IP_ADDRESS: From ad904b558cd9910d5969fd66a9eec3313198d73f Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 3 Sep 2019 12:09:23 +0100 Subject: [PATCH 090/648] 2.5.1 version bump and changelog --- CHANGELOG.md | 18 ++++++++++++++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c061df0859..42e05420c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Change Log +## 2.5.1 + +[Full Changelog](https://github.com/cloudfoundry-incubator/stratos/compare/2.5.0...2.5.1) + +This release contains a number of fixes and improvements: + +**Fixes:** + +- Allow DB directory to be specified for SQLite [\#194](https://github.com/SUSE/stratos/pull/193) + +**Improvements:** + +- Tech preview for helm feature [\#190](https://github.com/SUSE/stratos/pull/190) +- Add custom welcome message on endpoints page [\#194](https://github.com/SUSE/stratos/pull/194) +- Add support for connecting CaaSP V4 endpoints [\#191](https://github.com/SUSE/stratos/pull/191) + +> Note: Tech preview features can be enabled when deploying via Helm by setting the Helm chart value `console.techPreview` to `true` - e.g. add `--set console.techPreview=true` when installing. + ## 2.5.0 [Full Changelog](https://github.com/cloudfoundry-incubator/stratos/compare/2.4.0...2.5.0) diff --git a/package-lock.json b/package-lock.json index 0bd98c85ce..47d82c0c4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "stratos", - "version": "2.5.0", + "version": "2.5.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 0b136f7d32..0a2c43eda8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stratos", - "version": "2.5.0", + "version": "2.5.1", "description": "Stratos Console", "main": "index.js", "scripts": { From 1560dde5a2a1b83ba4c6281ab5a4289eb6758d03 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 3 Sep 2019 13:48:50 +0100 Subject: [PATCH 091/648] Autoscaler Tweaks --- .../autoscaler-available.ts | 30 +++++++ .../autoscaler-helpers/autoscaler-util.ts | 22 ++--- .../autoscaler-tab-extension.component.html | 88 +++++++------------ .../autoscaler-tab-extension.component.scss | 24 ++++- .../autoscaler-tab-extension.component.ts | 75 ++++++++-------- .../edit-autoscaler-policy-service.ts | 6 +- ...dit-autoscaler-policy-step1.component.html | 2 + ...dit-autoscaler-policy-step2.component.html | 9 +- ...dit-autoscaler-policy-step2.component.scss | 4 + .../edit-autoscaler-policy-step2.component.ts | 28 +++++- ...dit-autoscaler-policy-step3.component.html | 2 + ...dit-autoscaler-policy-step4.component.html | 2 + .../edit-autoscaler-policy-step4.component.ts | 14 +-- .../edit-autoscaler-policy.component.html | 9 +- .../edit-autoscaler-policy.component.scss | 13 --- .../edit-autoscaler-policy.component.ts | 6 +- .../card-autoscaler-default.component.html | 67 +++++--------- .../card-autoscaler-default.component.scss | 52 ++--------- .../src/store/app-autoscaler.actions.ts | 17 +++- .../src/store/app-autoscaler.types.ts | 7 ++ .../src/store/autoscaler.effects.ts | 51 ++++++++--- .../src/store/autoscaler.store.module.ts | 6 ++ .../card-cf-info/card-cf-info.component.html | 18 ++-- .../card-cf-info/card-cf-info.component.ts | 14 ++- .../plugins/autoscaler/autoscaler.go | 9 ++ src/jetstream/plugins/autoscaler/main.go | 1 + 26 files changed, 325 insertions(+), 251 deletions(-) create mode 100644 src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-available.ts diff --git a/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-available.ts b/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-available.ts new file mode 100644 index 0000000000..c5f16c883b --- /dev/null +++ b/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-available.ts @@ -0,0 +1,30 @@ +import { Observable } from 'rxjs'; +import { filter, map, publishReplay, refCount, startWith } from 'rxjs/operators'; + +import { EntityServiceFactory } from '../../../../core/src/core/entity-service-factory.service'; +import { APIResource, EntityInfo } from '../../../../store/src/types/api.types'; +import { GetAppAutoscalerInfoAction } from '../../store/app-autoscaler.actions'; +import { AutoscalerInfo } from '../../store/app-autoscaler.types'; + +export const fetchAutoscalerInfo = ( + endpointGuid: string, + esf: EntityServiceFactory): Observable>> => { + const action = new GetAppAutoscalerInfoAction(endpointGuid); + const entityService = esf.create>(action.entityKey, action.entity, endpointGuid, action); + return entityService.entityObs$.pipe( + filter(entityInfo => + !!entityInfo && + !!entityInfo.entityRequestInfo && + !entityInfo.entityRequestInfo.fetching + ), + publishReplay(1), + refCount() + ); +}; + +export const isAutoscalerEnabled = (endpointGuid: string, esf: EntityServiceFactory): Observable => { + return fetchAutoscalerInfo(endpointGuid, esf).pipe( + map(entityInfo => !entityInfo.entityRequestInfo.error), + startWith(false) + ); +}; diff --git a/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-util.ts b/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-util.ts index 46fd4b6d21..a1b84d299c 100644 --- a/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-util.ts +++ b/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-util.ts @@ -106,28 +106,28 @@ export class AutoscalerConstants { } export const PolicyAlert = { - alertInvalidPolicyMinimumRange: 'The Minimum Instance Count must be a integer less than the Maximum Instance Count.', - alertInvalidPolicyMaximumRange: 'The Maximum Instance Count must be a integer greater than the Minimum Instance Count.', + alertInvalidPolicyMinimumRange: 'The Minimum Instance Count must be an integer less than the Maximum Instance Count.', + alertInvalidPolicyMaximumRange: 'The Maximum Instance Count must be an integer greater than the Minimum Instance Count.', alertInvalidPolicyInitialMaximumRange: - 'The Initial Minimum Instance Count must be a integer in the range of Minimum Instance Count to Maximum Instance Count.', + 'The Initial Minimum Instance Count must be an integer between Minimum Instance Count and Maximum Instance Count.', alertInvalidPolicyTriggerUpperThresholdRange: 'The Upper Threshold value must be an integer greater than the Lower Threshold value.', - alertInvalidPolicyTriggerLowerThresholdRange: 'The Lower Threshold value must be an integer in the range of 1 to (Upper Threshold-1).', + alertInvalidPolicyTriggerLowerThresholdRange: 'The Lower Threshold value must be an integer between 1 and (Upper Threshold-1).', alertInvalidPolicyTriggerThreshold100: 'The Lower/Upper Threshold value of memoryutil must be an integer below or equal to 100.', - alertInvalidPolicyTriggerStepPercentageRange: 'The Instance Step Up/Down percentage must be a integer greater than 1.', - alertInvalidPolicyTriggerStepRange: 'The Instance Step Up/Down value must be a integer in the range of 1 to (Maximum Instance-1).', + alertInvalidPolicyTriggerStepPercentageRange: 'The Instance Step Up/Down percentage must be an integer greater than 1.', + alertInvalidPolicyTriggerStepRange: 'The Instance Step Up/Down value must be an integer between 1 and (Maximum Instance-1).', alertInvalidPolicyTriggerBreachDurationRange: - `The breach duration value must be an integer in the range of ${AutoscalerConstants.PolicyDefaultSetting.breach_duration_secs_min} to + `The breach duration value must be an integer between ${AutoscalerConstants.PolicyDefaultSetting.breach_duration_secs_min} and ${AutoscalerConstants.PolicyDefaultSetting.breach_duration_secs_max} seconds.`, alertInvalidPolicyTriggerCooldownRange: - `The cooldown period value must be an integer in the range of ${AutoscalerConstants.PolicyDefaultSetting.cool_down_secs_min} to + `The cooldown period value must be an integer between ${AutoscalerConstants.PolicyDefaultSetting.cool_down_secs_min} and ${AutoscalerConstants.PolicyDefaultSetting.breach_duration_secs_max} seconds.`, - alertInvalidPolicyScheduleDateBeforeNow: 'Start/End date should be after or equal to current date.', + alertInvalidPolicyScheduleDateBeforeNow: 'Start/End date should be after or equal to the current date.', alertInvalidPolicyScheduleEndDateBeforeStartDate: 'Start date must be earlier than the end date.', alertInvalidPolicyScheduleEndTimeBeforeStartTime: 'Start time must be earlier than the end time.', alertInvalidPolicyScheduleRepeatOn: 'Please select at least one "Repeat On" day.', alertInvalidPolicyScheduleEndDateTimeBeforeStartDateTime: 'Start date and time must be earlier than the end date and time.', - alertInvalidPolicyScheduleStartDateTimeBeforeNow: 'Start date and time must be after or equal to current date time.', - alertInvalidPolicyScheduleEndDateTimeBeforeNow: 'End date and time must be after or equal the current date and time.', + alertInvalidPolicyScheduleStartDateTimeBeforeNow: 'Start date and time must be after or equal to the current date time.', + alertInvalidPolicyScheduleEndDateTimeBeforeNow: 'End date and time must be after or equal to the current date and time.', alertInvalidPolicyScheduleRecurringConflict: 'Recurring schedule configuration conflict occurs.', alertInvalidPolicyScheduleSpecificConflict: 'Specific date configuration conflict occurs.', alertInvalidPolicyTriggerScheduleEmpty: 'At least one Scaling Rule or Schedule should be defined.', diff --git a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.html b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.html index 1ea6e15f7a..413228ea3d 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.html +++ b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.html @@ -1,52 +1,29 @@ + + + + + + + + +
    - - - - - Status - - - - - - - - - - - - - - - Status - - - - - - - - - - + @@ -59,7 +36,6 @@ - - - @@ -204,17 +175,15 @@ No recurring schedule.
    - - -
    - - + + + {{noPolicyMessageFirstLine}}. {{noPolicyMessageSecondLine.text}} + + Latest Events @@ -259,5 +228,8 @@ + + \ No newline at end of file diff --git a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.scss b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.scss index 2388c6fd13..b0f618d8fa 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.scss +++ b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.scss @@ -17,15 +17,17 @@ } &__latest-metrics { + flex: 2; min-height: 200px; } } + .app-metadata { display: flex; flex-direction: row; &__two-cols { - flex: 1; + padding-right: 40px; app-metadata-item:first-child { margin-top: 0; } @@ -52,6 +54,13 @@ table { } .autoscaler-tile-events { + &__no-policy { + align-items: center; + display: flex; + margin-bottom: 12px; + padding: 14px; + } + &__header { display: flex; flex: 1; @@ -69,3 +78,16 @@ table { } } } +.nav-button-with-text { + margin: 0 10px 0 0; + padding: 0 5px; + + &__span { + align-items: center; + display: flex; + } + + &__icon { + margin-right: 5px; + } +} diff --git a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.ts b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.ts index b011679585..6b65ae93db 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.ts @@ -2,8 +2,8 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { MatSnackBar, MatSnackBarRef, SimpleSnackBar } from '@angular/material'; import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; -import { Observable, Subscription } from 'rxjs'; -import { distinctUntilChanged, filter, first, map, publishReplay, refCount, startWith } from 'rxjs/operators'; +import { combineLatest, Observable, Subscription } from 'rxjs'; +import { distinctUntilChanged, filter, first, map, publishReplay, refCount } from 'rxjs/operators'; import { EntityService } from '../../../../core/src/core/entity-service'; import { EntityServiceFactory } from '../../../../core/src/core/entity-service-factory.service'; @@ -23,6 +23,7 @@ import { ActionState } from '../../../../store/src/reducers/api-request-reducer/ import { getPaginationObservables } from '../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper'; import { selectUpdateInfo } from '../../../../store/src/selectors/api.selectors'; import { APIResource } from '../../../../store/src/types/api.types'; +import { isAutoscalerEnabled } from '../../core/autoscaler-helpers/autoscaler-available'; import { AutoscalerConstants } from '../../core/autoscaler-helpers/autoscaler-util'; import { AutoscalerPaginationParams, @@ -33,9 +34,7 @@ import { UpdateAppAutoscalerPolicyAction, } from '../../store/app-autoscaler.actions'; import { - AppAutoscalerFetchPolicyFailedResponse, AppAutoscalerMetricData, - AppAutoscalerPolicy, AppAutoscalerPolicyLocal, AppAutoscalerScalingHistory, AppScalingTrigger, @@ -46,25 +45,6 @@ import { appAutoscalerScalingHistorySchemaKey, } from '../../store/autoscaler.store.module'; -const enableAutoscaler = (appGuid: string, endpointGuid: string, esf: EntityServiceFactory): Observable => { - // This will eventual be moved out into a service and made generic to the cf (one call per cf, rather than one call per app - See #3583) - const action = new GetAppAutoscalerPolicyAction(appGuid, endpointGuid); - const entityService = esf.create(action.entityKey, action.entity, action.guid, action); - return entityService.entityObs$.pipe( - filter(entityInfo => - !!entityInfo && !!entityInfo.entityRequestInfo && !!entityInfo.entityRequestInfo.response && - !entityInfo.entityRequestInfo.fetching), - map(entityInfo => { - // Autoscaler feature should be enabled if either .. - // 1) There's an autoscaler policy - // 2) There's a 404 no policy error (as opposed to a 404 url not found error) - const noPolicySet = (entityInfo.entityRequestInfo.response as AppAutoscalerFetchPolicyFailedResponse).noPolicy; - return !!entityInfo.entity || noPolicySet; - }), - startWith(true) - ); -}; - @StratosTab({ type: StratosTabType.Application, label: 'Autoscale', @@ -73,8 +53,7 @@ const enableAutoscaler = (appGuid: string, endpointGuid: string, esf: EntityServ iconFont: 'stratos-icons', hidden: (store: Store, esf: EntityServiceFactory, activatedRoute: ActivatedRoute) => { const endpointGuid = getGuids('cf')(activatedRoute) || window.location.pathname.split('/')[2]; - const appGuid = getGuids()(activatedRoute) || window.location.pathname.split('/')[3]; - return enableAutoscaler(appGuid, endpointGuid, esf).pipe(map(enabled => !enabled)); + return isAutoscalerEnabled(endpointGuid, esf).pipe(map(enabled => !enabled)); } }) @Component({ @@ -100,14 +79,20 @@ export class AutoscalerTabExtensionComponent implements OnInit, OnDestroy { appAutoscalerScalingHistory$: Observable; appAutoscalerAppMetricNames$: Observable; + public showNoPolicyMessage$: Observable; + public showAutoscalerHistory$: Observable; + + public noPolicyMessageFirstLine = 'This application has no Autoscaler Policy'; + public noPolicyMessageSecondLine = { + text: 'To create a policy click the + icon above' + }; + private appAutoscalerPolicyErrorSub: Subscription; private appAutoscalerScalingHistoryErrorSub: Subscription; private appAutoscalerPolicySnackBarRef: MatSnackBarRef; private appAutoscalerScalingHistorySnackBarRef: MatSnackBarRef; private scalingHistoryAction: GetAppAutoscalerScalingHistoryAction; - private detachConfirmOk = 0; - appAutoscalerAppMetrics = {}; paramsMetrics: AutoscalerPaginationParams = { @@ -190,6 +175,24 @@ export class AutoscalerTabExtensionComponent implements OnInit, OnDestroy { refCount() ); this.initErrorSub(); + + this.showAutoscalerHistory$ = combineLatest([ + this.appAutoscalerPolicy$, + this.appAutoscalerScalingHistory$ + ]).pipe( + map(([policy, history]) => !!policy || (!!history && history.total_results > 0)), + publishReplay(1), + refCount() + ); + + this.showNoPolicyMessage$ = combineLatest([ + this.appAutoscalerPolicy$, + this.appAutoscalerScalingHistory$ + ]).pipe( + map(([policy, history]) => !policy && (!history || history.total_results === 0)), + publishReplay(1), + refCount() + ); } getAppMetric(metricName: string, trigger: AppScalingTrigger, params: AutoscalerPaginationParams) { @@ -227,7 +230,7 @@ export class AutoscalerTabExtensionComponent implements OnInit, OnDestroy { } this.appAutoscalerPolicyErrorSub = this.appAutoscalerPolicyService.entityMonitor.entityRequest$.pipe( - filter(request => !!request.error), + filter(request => !!request.error && !request.response.noPolicy), map(request => { const msg = request.message; request.error = false; @@ -259,14 +262,12 @@ export class AutoscalerTabExtensionComponent implements OnInit, OnDestroy { disableAutoscaler() { const confirmation = new ConfirmationDialogConfig( - 'Detach And Delete Policy', - 'Are you sure you want to detach and delete the policy?', - 'Detach and Delete', + 'Delete Policy', + 'Are you sure you want to delete the policy?', + 'Delete', true ); - this.detachConfirmOk = this.detachConfirmOk === 1 ? 0 : 1; this.confirmDialog.open(confirmation, () => { - this.detachConfirmOk = 2; const doUpdate = () => this.detachPolicy(); doUpdate().pipe( first(), @@ -289,14 +290,18 @@ export class AutoscalerTabExtensionComponent implements OnInit, OnDestroy { return this.store.select(actionState).pipe(filter(item => !!item)); } - updatePolicyPage = () => { + updatePolicyPage = (isCreate = false) => { + const query = isCreate ? { + create: isCreate + } : {}; this.store.dispatch(new RouterNav({ path: [ 'autoscaler', this.applicationService.cfGuid, this.applicationService.appGuid, 'edit-autoscaler-policy' - ] + ], + query })); } diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-service.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-service.ts index 4d5752f7b2..c7a8dcad17 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-service.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-service.ts @@ -8,7 +8,7 @@ import { entityFactory } from '../../../../store/src/helpers/entity-factory'; import { EntityInfo } from '../../../../store/src/types/api.types'; import { autoscalerTransformArrayToMap } from '../../core/autoscaler-helpers/autoscaler-transform-policy'; import { GetAppAutoscalerPolicyAction } from '../../store/app-autoscaler.actions'; -import { AppAutoscalerPolicy, AppAutoscalerPolicyLocal } from '../../store/app-autoscaler.types'; +import { AppAutoscalerPolicyLocal } from '../../store/app-autoscaler.types'; import { appAutoscalerPolicySchemaKey } from '../../store/autoscaler.store.module'; @Injectable() @@ -47,13 +47,13 @@ export class EditAutoscalerPolicyService { first(), ).subscribe((({ entity }) => { if (entity && entity.entity) { - this.stateSubject.next(entity.entity); + this.setState(entity.entity); } })); } setState(state: AppAutoscalerPolicyLocal) { - const {...newState} = state; + const newState = JSON.parse(JSON.stringify(state)); this.stateSubject.next(newState); } diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component.html b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component.html index eab22c9e5a..a05d996466 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component.html +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component.html @@ -1,4 +1,6 @@
    + Use minimum and maximum instance counts to provide default values for your policy. +
    diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.html b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.html index 5f15c5934d..aa6cb4f718 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.html +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.html @@ -1,4 +1,6 @@
    +

    Add scaling rules that work with built-in metrics to scale your application. Metrics are averaged over all the + instances of your application.

    @@ -34,11 +36,14 @@ - {{getMetricUnit(rule.metric_type)}} for + + {{metricUnit$ | async}} + + for - seconsds, then {{editScaleType=='upper'?'add':'remove'}} + seconds, then {{editScaleType=='upper'?'add':'remove'}} diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.scss b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.scss index 2e060b8936..3ed560932a 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.scss +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.scss @@ -19,6 +19,10 @@ .autoscaler-policy-edit-trigger-item-desc { margin-bottom: .5em; } + .autoscaler-policy-edit-trigger-item__unit { + display: inline-block; + width: 23px; + } mat-error { font-size: 85%; } diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.ts index 29165b0c00..ff2306b454 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.ts @@ -1,8 +1,10 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { AbstractControl, FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms'; import { ErrorStateMatcher, ShowOnDirtyErrorStateMatcher } from '@angular/material'; -import { Observable } from 'rxjs'; +import { BehaviorSubject, Observable, Subscription } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { safeUnsubscribe } from '../../../../../core/src/core/utils.service'; import { ApplicationService } from '../../../../../core/src/features/applications/application.service'; import { AutoscalerConstants, @@ -15,7 +17,11 @@ import { getThresholdMin, numberWithFractionOrExceedRange, } from '../../../core/autoscaler-helpers/autoscaler-validation'; -import { AppAutoscalerPolicy, AppAutoscalerPolicyLocal, AppAutoscalerInvalidPolicyError } from '../../../store/app-autoscaler.types'; +import { + AppAutoscalerInvalidPolicyError, + AppAutoscalerPolicy, + AppAutoscalerPolicyLocal, +} from '../../../store/app-autoscaler.types'; import { EditAutoscalerPolicy } from '../edit-autoscaler-policy-base-step'; import { EditAutoscalerPolicyService } from '../edit-autoscaler-policy-service'; @@ -27,10 +33,12 @@ import { EditAutoscalerPolicyService } from '../edit-autoscaler-policy-service'; { provide: ErrorStateMatcher, useClass: ShowOnDirtyErrorStateMatcher } ] }) -export class EditAutoscalerPolicyStep2Component extends EditAutoscalerPolicy implements OnInit { +export class EditAutoscalerPolicyStep2Component extends EditAutoscalerPolicy implements OnInit, OnDestroy { policyAlert = PolicyAlert; metricTypes = AutoscalerConstants.MetricTypes; + private metricUnitSubject = new BehaviorSubject(this.metricTypes[0]); + metricUnit$: Observable; operatorTypes = AutoscalerConstants.UpperOperators.concat(AutoscalerConstants.LowerOperators); editTriggerForm: FormGroup; appAutoscalerPolicy$: Observable; @@ -41,6 +49,7 @@ export class EditAutoscalerPolicyStep2Component extends EditAutoscalerPolicy imp private editMetricType = ''; private editScaleType = 'upper'; private editAdjustmentType = 'value'; + private subs: Subscription[] = []; constructor( public applicationService: ApplicationService, @@ -63,6 +72,12 @@ export class EditAutoscalerPolicyStep2Component extends EditAutoscalerPolicy imp ]], adjustment_type: [0, this.validateTriggerAdjustmentType()] }); + + this.metricUnit$ = this.metricUnitSubject.asObservable(); + + this.subs.push(this.editTriggerForm.get('metric_type').valueChanges.pipe( + map(value => this.getMetricUnit(value)), + ).subscribe(unit => this.metricUnitSubject.next(unit))); } addTrigger = () => { @@ -92,6 +107,7 @@ export class EditAutoscalerPolicyStep2Component extends EditAutoscalerPolicy imp cool_down_secs: this.currentPolicy.scaling_rules_form[index].cool_down_secs, adjustment_type: this.editAdjustmentType }); + this.metricUnitSubject.next(this.getMetricUnit(this.editMetricType)); } finishTrigger() { @@ -185,4 +201,8 @@ export class EditAutoscalerPolicyStep2Component extends EditAutoscalerPolicy imp getMetricUnit(metricType: string) { return AutoscalerConstants.getMetricUnit(metricType); } + + ngOnDestroy() { + safeUnsubscribe(...this.subs); + } } diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step3/edit-autoscaler-policy-step3.component.html b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step3/edit-autoscaler-policy-step3.component.html index 4ed111d0e6..f5c4e8f174 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step3/edit-autoscaler-policy-step3.component.html +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step3/edit-autoscaler-policy-step3.component.html @@ -1,4 +1,6 @@
    +

    Add schedules that reoccur to overwrite the default instance limits for specific time periods. During these time + periods, all dynamic scaling rules are still effective.

    diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.html b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.html index 948cf3574a..91654b3744 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.html +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.html @@ -1,4 +1,6 @@
    +

    Add schedules for specific dates and times that overwrite the default instance limits. Like reoccuring schedules, + during these time periods all dynamic scaling rules are still effective.

    diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.ts index f43e297500..4c7c246c40 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.ts @@ -20,10 +20,11 @@ import { } from '../../../core/autoscaler-helpers/autoscaler-validation'; import { UpdateAppAutoscalerPolicyAction } from '../../../store/app-autoscaler.actions'; import { + AppAutoscalerInvalidPolicyError, AppAutoscalerPolicy, AppAutoscalerPolicyLocal, AppSpecificDate, - AppAutoscalerInvalidPolicyError } from '../../../store/app-autoscaler.types'; +} from '../../../store/app-autoscaler.types'; import { appAutoscalerPolicySchemaKey } from '../../../store/autoscaler.store.module'; import { EditAutoscalerPolicy } from '../edit-autoscaler-policy-base-step'; import { EditAutoscalerPolicyService } from '../edit-autoscaler-policy-service'; @@ -91,19 +92,12 @@ export class EditAutoscalerPolicyStep4Component extends EditAutoscalerPolicy imp const waitForAppAutoscalerUpdateStatus$ = this.updateAppAutoscalerPolicyService.entityMonitor.entityRequest$.pipe( filter(request => { if (request.message && request.message.indexOf('fetch policy') >= 0) { - request.message = ''; return false; } else { return !!request.error || !!request.response; } }), - map(request => { - const msg = request.message; - request.error = false; - request.response = null; - request.message = ''; - return msg; - }), + map(request => request.message), distinctUntilChanged(), ).pipe(map( errorMessage => { @@ -127,7 +121,7 @@ export class EditAutoscalerPolicyStep4Component extends EditAutoscalerPolicy imp } addSpecificDate = () => { - const {...newSchedule} = AutoscalerConstants.PolicyDefaultSpecificDate; + const { ...newSchedule } = AutoscalerConstants.PolicyDefaultSpecificDate; this.currentPolicy.schedules.specific_date.push(newSchedule); this.editSpecificDate(this.currentPolicy.schedules.specific_date.length - 1); } diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy.component.html b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy.component.html index adc4db13ee..09913641f6 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy.component.html +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy.component.html @@ -1,6 +1,7 @@
    -

    Edit AutoScaler Policy: {{ (applicationService.application$ | async)?.app.entity.name }}

    +

    {{isCreate ? 'Create' : 'Edit' }} AutoScaler Policy: + {{ (applicationService.application$ | async)?.app.entity.name }}

    - +
    -
    + +
    + + \ No newline at end of file diff --git a/src/frontend/packages/cf-autoscaler/src/shared/card-autoscaler-default/card-autoscaler-default.component.scss b/src/frontend/packages/cf-autoscaler/src/shared/card-autoscaler-default/card-autoscaler-default.component.scss index 03cff2642b..96e3d7bc25 100644 --- a/src/frontend/packages/cf-autoscaler/src/shared/card-autoscaler-default/card-autoscaler-default.component.scss +++ b/src/frontend/packages/cf-autoscaler/src/shared/card-autoscaler-default/card-autoscaler-default.component.scss @@ -1,52 +1,10 @@ .card-autoscaler-default { - &__left { - border-right: 1px solid #808080; - display: inline-block; - padding-right: 1em; - } - - &__right { - display: inline-block; - padding-left: 1em; - } - - &__actions { - bottom: 24px; - padding-top: 0; - position: absolute; - right: 24px; - text-align: right; - } - - app-metadata-item { - .metadata-item__content { - flex-direction: row; - } - } - - .metadata-item__content { - display: inline; - } - - .metadata-item__value { - display: inline; - margin-right: 10px; - } - - .metadata-item__label { - display: inline; - margin-right: 10px; - } -} + &__min-max { + display: flex; + flex-direction: row; -.app-metadata { - display: flex; - flex-direction: row; - &__two-cols { - flex: 1; - margin-right: 1em; - app-metadata-item:first-child { - margin-top: 0; + app-metadata-item { + padding-right: 10px; } } } diff --git a/src/frontend/packages/cf-autoscaler/src/store/app-autoscaler.actions.ts b/src/frontend/packages/cf-autoscaler/src/store/app-autoscaler.actions.ts index 0fa992e75b..954eaf467f 100644 --- a/src/frontend/packages/cf-autoscaler/src/store/app-autoscaler.actions.ts +++ b/src/frontend/packages/cf-autoscaler/src/store/app-autoscaler.actions.ts @@ -9,6 +9,7 @@ import { AppAutoscalerPolicyLocal, AppScalingTrigger } from './app-autoscaler.ty import { appAutoscalerAppMetricSchemaKey, appAutoscalerHealthSchemaKey, + appAutoscalerInfoSchemaKey, appAutoscalerPolicySchemaKey, appAutoscalerPolicyTriggerSchemaKey, appAutoscalerScalingHistorySchemaKey, @@ -45,14 +46,28 @@ export const DETACH_APP_AUTOSCALER_POLICY = '[New App Autoscaler] Detach policy' export const APP_AUTOSCALER_HEALTH = '[New App Autoscaler] Fetch Health'; export const APP_AUTOSCALER_SCALING_HISTORY = '[New App Autoscaler] Fetch Scaling History'; export const FETCH_APP_AUTOSCALER_METRIC = '[New App Autoscaler] Fetch Metric'; +export const AUTOSCALER_INFO = '[Autoscaler] Fetch Info'; export const UPDATE_APP_AUTOSCALER_POLICY_STEP = '[Edit Autoscaler Policy] Step'; +export class GetAppAutoscalerInfoAction implements IRequestAction { + public guid: string; + constructor( + public endpointGuid: string, + ) { + this.guid = endpointGuid; + } + type = AUTOSCALER_INFO; + entity = entityFactory(appAutoscalerInfoSchemaKey); + entityKey = appAutoscalerInfoSchemaKey; +} + export class GetAppAutoscalerHealthAction implements IRequestAction { + public guid: string; constructor( - public guid: string, public endpointGuid: string, ) { + this.guid = endpointGuid; } type = APP_AUTOSCALER_HEALTH; entity = entityFactory(appAutoscalerHealthSchemaKey); diff --git a/src/frontend/packages/cf-autoscaler/src/store/app-autoscaler.types.ts b/src/frontend/packages/cf-autoscaler/src/store/app-autoscaler.types.ts index d7cb19c63d..541d124d38 100644 --- a/src/frontend/packages/cf-autoscaler/src/store/app-autoscaler.types.ts +++ b/src/frontend/packages/cf-autoscaler/src/store/app-autoscaler.types.ts @@ -1,5 +1,12 @@ import { AutoscalerQuery } from './app-autoscaler.actions'; +export interface AutoscalerInfo { + name: string; + build: string; + support: string; + description: string; +} + export interface AppAutoscalerPolicy { instance_min_count: number; instance_max_count: number; diff --git a/src/frontend/packages/cf-autoscaler/src/store/autoscaler.effects.ts b/src/frontend/packages/cf-autoscaler/src/store/autoscaler.effects.ts index 03e76301f8..218ec8c126 100644 --- a/src/frontend/packages/cf-autoscaler/src/store/autoscaler.effects.ts +++ b/src/frontend/packages/cf-autoscaler/src/store/autoscaler.effects.ts @@ -12,7 +12,7 @@ import { resultPerPageParamDefault, } from '../../../store/src/reducers/pagination-reducer/pagination-reducer.types'; import { selectPaginationState } from '../../../store/src/selectors/pagination.selectors'; -import { APIResource, NormalizedResponse } from '../../../store/src/types/api.types'; +import { APIResource, NormalizedResponse, PaginationResponse } from '../../../store/src/types/api.types'; import { PaginatedAction, PaginationEntityState, PaginationParam } from '../../../store/src/types/pagination.types'; import { StartRequestAction, @@ -30,28 +30,29 @@ import { APP_AUTOSCALER_POLICY, APP_AUTOSCALER_POLICY_TRIGGER, APP_AUTOSCALER_SCALING_HISTORY, + AUTOSCALER_INFO, + AutoscalerPaginationParams, AutoscalerQuery, DETACH_APP_AUTOSCALER_POLICY, DetachAppAutoscalerPolicyAction, FETCH_APP_AUTOSCALER_METRIC, GetAppAutoscalerHealthAction, + GetAppAutoscalerInfoAction, GetAppAutoscalerMetricAction, GetAppAutoscalerPolicyAction, GetAppAutoscalerPolicyTriggerAction, GetAppAutoscalerScalingHistoryAction, UPDATE_APP_AUTOSCALER_POLICY, UpdateAppAutoscalerPolicyAction, - AutoscalerPaginationParams, } from './app-autoscaler.actions'; import { + AppAutoscalerEvent, AppAutoscalerFetchPolicyFailedResponse, - AppAutoscalerMetricDataLocal, - AppScalingTrigger, AppAutoscalerMetricData, + AppAutoscalerMetricDataLocal, AppAutoscalerPolicyLocal, - AppAutoscalerEvent, + AppScalingTrigger, } from './app-autoscaler.types'; -import { PaginationResponse } from '../../../store/src/types/api.types'; const { proxyAPIVersion } = environment; const commonPrefix = `/pp/${proxyAPIVersion}/autoscaler`; @@ -68,6 +69,34 @@ export class AutoscalerEffects { private store: Store, ) { } + @Effect() + fetchAutoscalerInfo$ = this.actions$.pipe( + ofType(AUTOSCALER_INFO), + mergeMap(action => { + const actionType = 'fetch'; + this.store.dispatch(new StartRequestAction(action, actionType)); + const options = new RequestOptions(); + options.url = `${commonPrefix}/info`; + options.method = 'get'; + options.headers = this.addHeaders(action.endpointGuid); + return this.http + .request(new Request(options)).pipe( + mergeMap(response => { + const autoscalerInfo = response.json(); + const mappedData = { + entities: { [action.entityKey]: {} }, + result: [] + } as NormalizedResponse; + this.transformData(action.entityKey, mappedData, action.endpointGuid, autoscalerInfo); + return [ + new WrapperRequestActionSuccess(mappedData, action, actionType) + ]; + }), + catchError(err => [ + new WrapperRequestActionFailed(createAutoscalerRequestMessage('fetch autoscaler info', err), action, actionType) + ])); + })); + @Effect() fetchAppAutoscalerHealth$ = this.actions$.pipe( ofType(APP_AUTOSCALER_HEALTH), @@ -86,7 +115,7 @@ export class AutoscalerEffects { entities: { [action.entityKey]: {} }, result: [] } as NormalizedResponse; - this.transformData(action.entityKey, mappedData, action.guid, healthInfo); + this.transformData(action.entityKey, mappedData, action.endpointGuid, healthInfo); return [ new WrapperRequestActionSuccess(mappedData, action, actionType) ]; @@ -348,14 +377,14 @@ export class AutoscalerEffects { mappedData.result.push(id); } - transformData(key: string, mappedData: NormalizedResponse, appGuid: string, data: any) { - mappedData.entities[key][appGuid] = { + transformData(key: string, mappedData: NormalizedResponse, guid: string, data: any) { + mappedData.entities[key][guid] = { entity: data, metadata: { - guid: appGuid + guid } }; - mappedData.result.push(appGuid); + mappedData.result.push(guid); } transformEventData(key: string, mappedData: NormalizedResponse, appGuid: string, data: PaginationResponse) { diff --git a/src/frontend/packages/cf-autoscaler/src/store/autoscaler.store.module.ts b/src/frontend/packages/cf-autoscaler/src/store/autoscaler.store.module.ts index 702c816075..725050dc18 100644 --- a/src/frontend/packages/cf-autoscaler/src/store/autoscaler.store.module.ts +++ b/src/frontend/packages/cf-autoscaler/src/store/autoscaler.store.module.ts @@ -6,12 +6,18 @@ import { ExtensionEntitySchema } from '../../../core/src/core/extension/extensio import { getAPIResourceGuid } from '../../../store/src/selectors/api.selectors'; export const appAutoscalerHealthSchemaKey = 'autoscalerHealth'; +export const appAutoscalerInfoSchemaKey = 'autoscalerInfo'; export const appAutoscalerPolicySchemaKey = 'autoscalerPolicy'; export const appAutoscalerPolicyTriggerSchemaKey = 'autoscalerPolicyTrigger'; export const appAutoscalerScalingHistorySchemaKey = 'autoscalerScalingHistory'; export const appAutoscalerAppMetricSchemaKey = 'autoscalerAppMetric'; export const autoscalerEntities: ExtensionEntitySchema[] = [ + { + entityKey: appAutoscalerInfoSchemaKey, + definition: {}, + options: { idAttribute: getAPIResourceGuid } + }, { entityKey: appAutoscalerPolicySchemaKey, definition: {}, diff --git a/src/frontend/packages/core/src/shared/components/cards/card-cf-info/card-cf-info.component.html b/src/frontend/packages/core/src/shared/components/cards/card-cf-info/card-cf-info.component.html index 4a9d17d0d3..9ce13dc103 100644 --- a/src/frontend/packages/core/src/shared/components/cards/card-cf-info/card-cf-info.component.html +++ b/src/frontend/packages/core/src/shared/components/cards/card-cf-info/card-cf-info.component.html @@ -8,21 +8,29 @@
    +
    diff --git a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.ts b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.ts index fda5d8da05..9bb9288676 100644 --- a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.ts +++ b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.ts @@ -7,10 +7,11 @@ import { SetPollingEnabledAction, SetSessionTimeoutAction } from '../../../../.. import { DashboardOnlyAppState } from '../../../../../store/src/app-state'; import { selectDashboardState } from '../../../../../store/src/selectors/dashboard.selectors'; import { UserProfileInfo } from '../../../../../store/src/types/user-profile.types'; +import { ThemeService } from '../../../core/theme.service'; +import { UserService } from '../../../core/user.service'; import { ConfirmationDialogConfig } from '../../../shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from '../../../shared/components/confirmation-dialog.service'; import { UserProfileService } from '../user-profile.service'; -import { UserService } from '../../../core/user.service'; @Component({ selector: 'app-profile-info', @@ -60,6 +61,7 @@ export class ProfileInfoComponent implements OnInit { private store: Store, private confirmDialog: ConfirmationDialogService, public userService: UserService, + public themeService: ThemeService ) { this.isError$ = userProfileService.isError$; this.userProfile$ = userProfileService.userProfile$; diff --git a/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.theme.scss b/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.theme.scss index 502ecae635..9e52da867e 100644 --- a/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.theme.scss +++ b/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.theme.scss @@ -3,6 +3,6 @@ $subdued: map-get($app-theme, subdued-color); .app-no-content-container { - color: $subdued; + color: $subdued; // TODO: RC $subdued contrast(?) } } diff --git a/src/frontend/packages/store/src/actions/dashboard-actions.ts b/src/frontend/packages/store/src/actions/dashboard-actions.ts index 5eb18cd3fa..c3f3c180cf 100644 --- a/src/frontend/packages/store/src/actions/dashboard-actions.ts +++ b/src/frontend/packages/store/src/actions/dashboard-actions.ts @@ -1,5 +1,6 @@ import { Action } from '@ngrx/store'; +import { StratosTheme } from '../../../core/src/core/theme.service'; import { DashboardState } from '../reducers/dashboard-reducer'; export const OPEN_SIDE_NAV = '[Dashboard] Open side nav'; @@ -17,6 +18,7 @@ export const CLOSE_SIDE_HELP = '[Dashboard] Close side help'; export const TIMEOUT_SESSION = '[Dashboard] Timeout Session'; export const ENABLE_POLLING = '[Dashboard] Enable Polling'; +export const SET_STRATOS_THEME = '[Dashboard] Set Theme'; export const HYDRATE_DASHBOARD_STATE = '[Dashboard] Hydrate dashboard state'; @@ -78,3 +80,7 @@ export class HydrateDashboardStateAction implements Action { type = HYDRATE_DASHBOARD_STATE; } +export class SetThemeAction implements Action { + constructor(public theme: StratosTheme) { } + type = SET_STRATOS_THEME; +} diff --git a/src/frontend/packages/store/src/reducers/dashboard-reducer.ts b/src/frontend/packages/store/src/reducers/dashboard-reducer.ts index e3ec5194de..dc0841e78e 100644 --- a/src/frontend/packages/store/src/reducers/dashboard-reducer.ts +++ b/src/frontend/packages/store/src/reducers/dashboard-reducer.ts @@ -1,23 +1,24 @@ +import { ThemeService } from '../../../core/src/core/theme.service'; import { CLOSE_SIDE_HELP, CLOSE_SIDE_NAV, DISABLE_SIDE_NAV_MOBILE_MODE, ENABLE_POLLING, ENABLE_SIDE_NAV_MOBILE_MODE, + HYDRATE_DASHBOARD_STATE, + HydrateDashboardStateAction, OPEN_SIDE_NAV, SET_HEADER_EVENT, + SET_STRATOS_THEME, SetHeaderEvent, SetPollingEnabledAction, + SetSessionTimeoutAction, + SetThemeAction, SHOW_SIDE_HELP, + TIMEOUT_SESSION, TOGGLE_HEADER_EVENT, TOGGLE_SIDE_NAV, } from '../actions/dashboard-actions'; -import { - HYDRATE_DASHBOARD_STATE, - HydrateDashboardStateAction, - SetSessionTimeoutAction, - TIMEOUT_SESSION, -} from '../actions/dashboard-actions'; export interface DashboardState { timeoutSession: boolean; @@ -29,6 +30,7 @@ export interface DashboardState { headerEventMinimized: boolean; sideHelpOpen: boolean; sideHelpDocument: string; + theme: string; } export const defaultDashboardState: DashboardState = { @@ -41,6 +43,7 @@ export const defaultDashboardState: DashboardState = { headerEventMinimized: false, sideHelpOpen: false, sideHelpDocument: null, + theme: ThemeService.themes[0].key }; export function dashboardReducer(state: DashboardState = defaultDashboardState, action): DashboardState { @@ -93,6 +96,12 @@ export function dashboardReducer(state: DashboardState = defaultDashboardState, ...state, ...hydrateDashboardStateAction.dashboardState }; + case SET_STRATOS_THEME: + const setThemeAction = action as SetThemeAction; + return { + ...state, + theme: setThemeAction.theme.key + }; default: return state; } From 3a16e7b4afbda8761a640f62c2641707a077f11d Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Thu, 31 Oct 2019 17:37:07 +0000 Subject: [PATCH 148/648] WIP --- .../core/sass/components/mat-tabs.theme.scss | 2 +- .../packages/core/src/app.component.html | 2 +- .../packages/core/src/app.component.ts | 3 - .../packages/core/src/core/core.module.ts | 4 +- .../packages/core/src/core/theme.service.ts | 72 +++++++++++++++++++ .../page-header.component.theme.scss | 3 + .../packages/core/src/shared/shared.module.ts | 8 +-- .../store/src/effects/dashboard.effects.ts | 24 +++++++ .../store/src/reducers/dashboard-reducer.ts | 7 +- .../packages/store/src/store.module.ts | 4 +- 10 files changed, 114 insertions(+), 15 deletions(-) create mode 100644 src/frontend/packages/core/src/core/theme.service.ts create mode 100644 src/frontend/packages/store/src/effects/dashboard.effects.ts diff --git a/src/frontend/packages/core/sass/components/mat-tabs.theme.scss b/src/frontend/packages/core/sass/components/mat-tabs.theme.scss index 29f7023533..90bc38cd86 100644 --- a/src/frontend/packages/core/sass/components/mat-tabs.theme.scss +++ b/src/frontend/packages/core/sass/components/mat-tabs.theme.scss @@ -13,7 +13,7 @@ .mat-tab-nav-bar.mat-primary.mat-background-primary { .mat-ink-bar { - //background-color: $tabs-ink-color; + //background-color: $tabs-ink-color; // TODO: RC } } diff --git a/src/frontend/packages/core/src/app.component.html b/src/frontend/packages/core/src/app.component.html index fb3c6ff6b1..16017c9762 100644 --- a/src/frontend/packages/core/src/app.component.html +++ b/src/frontend/packages/core/src/app.component.html @@ -1,4 +1,4 @@ - +
    {{userId}}
    diff --git a/src/frontend/packages/core/src/app.component.ts b/src/frontend/packages/core/src/app.component.ts index 154fb8b2b0..f011cfd52b 100644 --- a/src/frontend/packages/core/src/app.component.ts +++ b/src/frontend/packages/core/src/app.component.ts @@ -12,9 +12,6 @@ import { LoggedInService } from './logged-in.service'; selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], - providers: [ - ThemeService - ] }) export class AppComponent implements OnInit, OnDestroy, AfterContentInit { diff --git a/src/frontend/packages/core/src/core/core.module.ts b/src/frontend/packages/core/src/core/core.module.ts index 8b43843475..00dcb21516 100644 --- a/src/frontend/packages/core/src/core/core.module.ts +++ b/src/frontend/packages/core/src/core/core.module.ts @@ -26,6 +26,7 @@ import { PageHeaderService } from './page-header-service/page-header.service'; import { PageNotFoundComponentComponent } from './page-not-found-component/page-not-found-component.component'; import { SafeImgPipe } from './safe-img.pipe'; import { StatefulIconComponent } from './stateful-icon/stateful-icon.component'; +import { ThemeService } from './theme.service'; import { TruncatePipe } from './truncate.pipe'; import { UserService } from './user.service'; import { UtilsService } from './utils.service'; @@ -72,7 +73,8 @@ import { WindowRef } from './window-ref/window-ref.service'; EndpointsService, UserService, EntityServiceFactory, - CurrentUserPermissionsService + CurrentUserPermissionsService, + ThemeService ], declarations: [ StatefulIconComponent, diff --git a/src/frontend/packages/core/src/core/theme.service.ts b/src/frontend/packages/core/src/core/theme.service.ts new file mode 100644 index 0000000000..450460f7cd --- /dev/null +++ b/src/frontend/packages/core/src/core/theme.service.ts @@ -0,0 +1,72 @@ +import { OverlayContainer } from '@angular/cdk/overlay'; +import { Injectable } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { SetThemeAction } from '../../../store/src/actions/dashboard-actions'; +import { DashboardOnlyAppState } from '../../../store/src/app-state'; +import { selectDashboardState } from '../../../store/src/selectors/dashboard.selectors'; + +export interface StratosTheme { + key: string; + label: string; + styleName: string; +} + +const coreThemes: StratosTheme[] = [{ + key: 'default', + label: 'Light', + styleName: 'default' +}, { + key: 'dark', + label: 'Dark', + styleName: 'dark-theme' +}]; + +@Injectable() +export class ThemeService { + + constructor(private store: Store, private overlayContainer: OverlayContainer) { } + + getThemes(): StratosTheme[] { + return coreThemes; + } + + getTheme(): Observable { + return this.store.select(selectDashboardState).pipe( + map(dashboardState => this.findTheme(dashboardState.themeKey)) + ); + } + + setTheme(theme: StratosTheme) { + this.setOverlay(theme); + this.store.dispatch(new SetThemeAction(theme)); + } + + /** + * Initialize the service with a value that already exists in the store + */ + initialize(themeKey: string) { + this.setOverlay(this.findTheme(themeKey)); + } + + private findTheme(themeKey: string): StratosTheme { + const themes = this.getThemes(); + return themes.find(theme => theme.key === themeKey) || themes[0]; + } + + /** + * Overlays require the theme specifically set, see https://material.angular.io/guide/theming#multiple-themes + * `Multiple themes and overlay-based components` + */ + private setOverlay(newTheme: StratosTheme) { + this.getThemes().forEach(theme => { + if (newTheme.key === theme.key) { + this.overlayContainer.getContainerElement().classList.add(theme.styleName); + } else { + this.overlayContainer.getContainerElement().classList.remove(theme.styleName); + } + }); + } +} diff --git a/src/frontend/packages/core/src/shared/components/page-header/page-header.component.theme.scss b/src/frontend/packages/core/src/shared/components/page-header/page-header.component.theme.scss index 736c50de6b..e3f260264e 100644 --- a/src/frontend/packages/core/src/shared/components/page-header/page-header.component.theme.scss +++ b/src/frontend/packages/core/src/shared/components/page-header/page-header.component.theme.scss @@ -18,6 +18,9 @@ &__menu-separator { background-color: mat-color($foreground, divider); } + &__menu-inner { + color: mat-contrast($primary, 500); + } &__underflow { background-color: mat-color($primary); } diff --git a/src/frontend/packages/core/src/shared/shared.module.ts b/src/frontend/packages/core/src/shared/shared.module.ts index 58dffe7526..a6bd15ef03 100644 --- a/src/frontend/packages/core/src/shared/shared.module.ts +++ b/src/frontend/packages/core/src/shared/shared.module.ts @@ -58,8 +58,12 @@ import { TableCellStatusDirective } from './components/list/list-table/table-cel import { TableComponent } from './components/list/list-table/table.component'; import { listTableComponents } from './components/list/list-table/table.types'; import { EndpointCardComponent } from './components/list/list-types/endpoint/endpoint-card/endpoint-card.component'; +import { EndpointListHelper } from './components/list/list-types/endpoint/endpoint-list.helpers'; +import { EndpointsListConfigService } from './components/list/list-types/endpoint/endpoints-list-config.service'; import { ListComponent } from './components/list/list.component'; import { ListConfig } from './components/list/list.component.types'; +import { ListHostDirective } from './components/list/simple-list/list-host.directive'; +import { SimpleListComponent } from './components/list/simple-list/simple-list.component'; import { LoadingPageComponent } from './components/loading-page/loading-page.component'; import { LogViewerComponent } from './components/log-viewer/log-viewer.component'; import { MarkdownContentObserverDirective } from './components/markdown-preview/markdown-content-observer.directive'; @@ -112,10 +116,6 @@ import { ValuesPipe } from './pipes/values.pipe'; import { CloudFoundryUserProvidedServicesService } from './services/cloud-foundry-user-provided-services.service'; import { MetricsRangeSelectorService } from './services/metrics-range-selector.service'; import { UserPermissionDirective } from './user-permission.directive'; -import { SimpleListComponent } from './components/list/simple-list/simple-list.component'; -import { ListHostDirective } from './components/list/simple-list/list-host.directive'; -import { EndpointListHelper } from './components/list/list-types/endpoint/endpoint-list.helpers'; -import { EndpointsListConfigService } from './components/list/list-types/endpoint/endpoints-list-config.service'; /* tslint:disable:max-line-length */ diff --git a/src/frontend/packages/store/src/effects/dashboard.effects.ts b/src/frontend/packages/store/src/effects/dashboard.effects.ts new file mode 100644 index 0000000000..a5f138677d --- /dev/null +++ b/src/frontend/packages/store/src/effects/dashboard.effects.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { Actions, Effect, ofType } from '@ngrx/effects'; +import { map } from 'rxjs/operators'; + +import { ThemeService } from '../../../core/src/core/theme.service'; +import { HYDRATE_DASHBOARD_STATE, HydrateDashboardStateAction } from '../actions/dashboard-actions'; + + +@Injectable() +export class DashboardEffect { + + constructor( + private actions$: Actions, + private themeService: ThemeService + ) { } + + @Effect({ dispatch: false }) hydrate$ = this.actions$.pipe( + ofType(HYDRATE_DASHBOARD_STATE), + map(({ dashboardState }) => { + // Ensure the previous theme is applied + this.themeService.initialize(dashboardState.themeKey); + }) + ); +} diff --git a/src/frontend/packages/store/src/reducers/dashboard-reducer.ts b/src/frontend/packages/store/src/reducers/dashboard-reducer.ts index dc0841e78e..4d6705b343 100644 --- a/src/frontend/packages/store/src/reducers/dashboard-reducer.ts +++ b/src/frontend/packages/store/src/reducers/dashboard-reducer.ts @@ -1,4 +1,3 @@ -import { ThemeService } from '../../../core/src/core/theme.service'; import { CLOSE_SIDE_HELP, CLOSE_SIDE_NAV, @@ -30,7 +29,7 @@ export interface DashboardState { headerEventMinimized: boolean; sideHelpOpen: boolean; sideHelpDocument: string; - theme: string; + themeKey: string; } export const defaultDashboardState: DashboardState = { @@ -43,7 +42,7 @@ export const defaultDashboardState: DashboardState = { headerEventMinimized: false, sideHelpOpen: false, sideHelpDocument: null, - theme: ThemeService.themes[0].key + themeKey: null }; export function dashboardReducer(state: DashboardState = defaultDashboardState, action): DashboardState { @@ -100,7 +99,7 @@ export function dashboardReducer(state: DashboardState = defaultDashboardState, const setThemeAction = action as SetThemeAction; return { ...state, - theme: setThemeAction.theme.key + themeKey: setThemeAction.theme.key }; default: return state; diff --git a/src/frontend/packages/store/src/store.module.ts b/src/frontend/packages/store/src/store.module.ts index fb81148945..2cb41036b4 100644 --- a/src/frontend/packages/store/src/store.module.ts +++ b/src/frontend/packages/store/src/store.module.ts @@ -7,6 +7,7 @@ import { ActionHistoryEffect } from './effects/action-history.effects'; import { APIEffect } from './effects/api.effects'; import { AppEffects } from './effects/app.effects'; import { AuthEffect } from './effects/auth.effects'; +import { DashboardEffect } from './effects/dashboard.effects'; import { EndpointApiError } from './effects/endpoint-api-errors.effects'; import { EndpointsEffect } from './effects/endpoint.effects'; import { MetricsEffect } from './effects/metrics.effects'; @@ -22,8 +23,8 @@ import { UpdateAppEffects } from './effects/update-app-effects'; import { UserFavoritesEffect } from './effects/user-favorites-effect'; import { UserProfileEffect } from './effects/user-profile.effects'; import { UsersRolesEffects } from './effects/users-roles.effects'; -import { AppReducersModule } from './reducers.module'; import { PipelineHttpClient } from './entity-request-pipeline/pipline-http-client.service'; +import { AppReducersModule } from './reducers.module'; @NgModule({ @@ -35,6 +36,7 @@ import { PipelineHttpClient } from './entity-request-pipeline/pipline-http-clien HttpModule, HttpClientModule, EffectsModule.forRoot([ + DashboardEffect, APIEffect, EndpointApiError, AuthEffect, From 275fcc8acd0f38948cc9262c419cb25a23ccbfb4 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 1 Nov 2019 09:30:55 +0000 Subject: [PATCH 149/648] WIP --- .../shared/components/list/list.component.theme.scss | 12 +++++++++++- .../page-header/page-header.component.theme.scss | 3 +++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.theme.scss b/src/frontend/packages/core/src/shared/components/list/list.component.theme.scss index 6c51012b83..fd7ed26cbf 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.theme.scss +++ b/src/frontend/packages/core/src/shared/components/list/list.component.theme.scss @@ -4,10 +4,20 @@ $primary: map-get($theme, primary); $foreground-colors: map-get($theme, foreground); $background-colors: map-get($theme, background); + $is-dark: map-get($theme, is-dark); + + @if $is-dark == true { + $header-selected-color: lighten(mat-color($background-colors, background), 20%); + } @else { + $header-selected-color: darken(mat-color($background-colors, background), 20%); + } + + .list-component { &__header { &--selected { - background-color: mat-color($primary, 50); + // background-color: mat-color($app-background-color, 50); + background-color: $header-selected-color; } &__right, &__left--multi-filters { diff --git a/src/frontend/packages/core/src/shared/components/page-header/page-header.component.theme.scss b/src/frontend/packages/core/src/shared/components/page-header/page-header.component.theme.scss index e3f260264e..89d558be42 100644 --- a/src/frontend/packages/core/src/shared/components/page-header/page-header.component.theme.scss +++ b/src/frontend/packages/core/src/shared/components/page-header/page-header.component.theme.scss @@ -21,6 +21,9 @@ &__menu-inner { color: mat-contrast($primary, 500); } + &__history { + color: mat-contrast($primary, 500); + } &__underflow { background-color: mat-color($primary); } From 0908ed2560bd56619ec9c993b0531cd9e1c75533 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 1 Nov 2019 15:48:20 +0000 Subject: [PATCH 150/648] Fixes --- .../packages/core/sass/_all-theme.scss | 2 ++ .../profile-info/profile-info.component.html | 9 +++++++-- .../code-block/code-block.component.theme.scss | 18 ++++++++++++------ .../meta-card-item.component.scss | 1 - .../meta-card-item.component.theme.scss | 12 ++++++++++++ .../components/list/list.component.theme.scss | 5 ++--- .../page-header.component.theme.scss | 3 ++- 7 files changed, 37 insertions(+), 13 deletions(-) create mode 100644 src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.theme.scss diff --git a/src/frontend/packages/core/sass/_all-theme.scss b/src/frontend/packages/core/sass/_all-theme.scss index f04d29af12..878cd20149 100644 --- a/src/frontend/packages/core/sass/_all-theme.scss +++ b/src/frontend/packages/core/sass/_all-theme.scss @@ -33,6 +33,7 @@ @import '../src/shared/components/app-action-monitor-icon/app-action-monitor-icon.component.theme'; @import '../src/shared/components/upload-progress-indicator/upload-progress-indicator.component.theme'; @import '../src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component.theme'; +@import '../src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.theme'; @import '../src/shared/components/start-end-date/start-end-date.component.theme'; @import '../src/shared/components/metrics-chart/metrics-chart.component.theme'; @import '../src/shared/components/metrics-range-selector/metrics-range-selector.component.theme'; @@ -148,6 +149,7 @@ $side-nav-light-active: #484848; @include page-side-nav-theme($theme, $app-theme); @include cf-admin-add-user-warning($theme, $app-theme); @include entity-summary-title-theme($theme, $app-theme); + @include app-meta-card-item-theme($theme, $app-theme) } @function app-generate-nav-theme($theme, $nav-theme: null) { diff --git a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html index bfa8e45225..0a4c2df935 100644 --- a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html +++ b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html @@ -97,14 +97,19 @@

    User Profile

    - + + {{ theme.label }} + + +
    diff --git a/src/frontend/packages/core/src/shared/components/code-block/code-block.component.theme.scss b/src/frontend/packages/core/src/shared/components/code-block/code-block.component.theme.scss index 1010773073..bfac5a8b6e 100644 --- a/src/frontend/packages/core/src/shared/components/code-block/code-block.component.theme.scss +++ b/src/frontend/packages/core/src/shared/components/code-block/code-block.component.theme.scss @@ -1,16 +1,22 @@ @import '~@angular/material/theming'; @mixin display-value-theme($theme, $app-theme) { + $is-dark: map-get($theme, is-dark); $primary: map-get($theme, primary); $background-colors: map-get($theme, background); $foreground-colors: map-get($theme, foreground); .app-code-block { - background-color: mat-color($foreground-colors, text); - color: darken(mat-color($background-colors, background), 2%); - &___copied-icon { - color: mat-color($primary); + $background-color: mat-color($foreground-colors, text); + $color: darken(mat-color($background-colors, background), 2%); + @if $is-dark == true { + // See the app variable and cf cli pages + $background-color: lighten(mat-color($background-colors, background), 5%); + $color: mat-color($foreground-colors, text); } - &__copied-div { - background-color: mat-color($foreground-colors, text); + + background-color: $background-color; + color: $color; + &__copied-icon { + color: mat-color($primary); } } } diff --git a/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.scss b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.scss index eefbc889df..397814ab33 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.scss +++ b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.scss @@ -51,7 +51,6 @@ line-height: 18px; position: relative; &::after { - background: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 1) 75%); bottom: 0; content: ''; height: 1.2em; diff --git a/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.theme.scss b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.theme.scss new file mode 100644 index 0000000000..890951c936 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.theme.scss @@ -0,0 +1,12 @@ +@mixin app-meta-card-item-theme($theme, $app-theme) { + $backgrounds: map-get($theme, background); + $background: mat-color($backgrounds, card); + + .meta-card-item-long-text-fixed { + .meta-card-item__value { + &::after { + background: linear-gradient(to right, rgba(255, 255, 255, 0), $background 50%); + } + } + } +} diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.theme.scss b/src/frontend/packages/core/src/shared/components/list/list.component.theme.scss index fd7ed26cbf..c7e86ff821 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.theme.scss +++ b/src/frontend/packages/core/src/shared/components/list/list.component.theme.scss @@ -6,17 +6,16 @@ $background-colors: map-get($theme, background); $is-dark: map-get($theme, is-dark); + $header-selected-color: #fff; @if $is-dark == true { $header-selected-color: lighten(mat-color($background-colors, background), 20%); } @else { - $header-selected-color: darken(mat-color($background-colors, background), 20%); + $header-selected-color: mat-color($primary, 50); } - .list-component { &__header { &--selected { - // background-color: mat-color($app-background-color, 50); background-color: $header-selected-color; } &__right, diff --git a/src/frontend/packages/core/src/shared/components/page-header/page-header.component.theme.scss b/src/frontend/packages/core/src/shared/components/page-header/page-header.component.theme.scss index 89d558be42..65ed55245f 100644 --- a/src/frontend/packages/core/src/shared/components/page-header/page-header.component.theme.scss +++ b/src/frontend/packages/core/src/shared/components/page-header/page-header.component.theme.scss @@ -7,6 +7,7 @@ $error: map-get($status-colors, danger); $warning: map-get($status-colors, warning); $info: lighten($primmary-color, 20%); + $subdued: map-get($app-theme, subdued-color); .page-header { &__warning-count { background-color: mat-color($primary); @@ -22,7 +23,7 @@ color: mat-contrast($primary, 500); } &__history { - color: mat-contrast($primary, 500); + color: $subdued; } &__underflow { background-color: mat-color($primary); From d0f8bc8fd7e8473b9617ba0eea50426eab6c61f5 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 1 Nov 2019 16:15:13 +0000 Subject: [PATCH 151/648] Working version resets db passwords --- deploy/Dockerfile.bk | 1 - deploy/Dockerfile.init | 2 +- deploy/containers/config-init/config-init.sh | 316 ++++++++++++++++++ deploy/containers/nginx/conf/nginx.k8s.conf | 4 +- deploy/containers/nginx/run-nginx.sh | 18 +- deploy/db/mariadb-entrypoint.sh | 10 + deploy/db/scripts/README.md | 3 + deploy/db/scripts/config-init.sh | 147 -------- .../console/templates/config-init.yaml | 10 +- .../console/templates/deployment.yaml | 13 +- .../kubernetes/console/templates/secrets.yaml | 19 +- .../kubernetes/console/templates/volumes.yaml | 4 +- package-lock.json | 2 +- package.json | 2 +- 14 files changed, 369 insertions(+), 182 deletions(-) create mode 100755 deploy/containers/config-init/config-init.sh create mode 100644 deploy/db/scripts/README.md delete mode 100755 deploy/db/scripts/config-init.sh diff --git a/deploy/Dockerfile.bk b/deploy/Dockerfile.bk index 93e6e13a1a..a4cc1706b6 100644 --- a/deploy/Dockerfile.bk +++ b/deploy/Dockerfile.bk @@ -15,7 +15,6 @@ RUN chmod +x /srv/jetstream FROM splatform/stratos-bk-base:opensuse as prod-build RUN zypper in -y curl COPY deploy/containers/proxy/entrypoint.sh /entrypoint.sh -COPY /deploy/db/scripts/run-preflight-job.sh /run-preflight-job.sh COPY /deploy/tools/generate_cert.sh /generate_cert.sh COPY --from=common-build /srv /srv RUN mkdir /srv/templates diff --git a/deploy/Dockerfile.init b/deploy/Dockerfile.init index f19e3ef5f1..5a653516f0 100644 --- a/deploy/Dockerfile.init +++ b/deploy/Dockerfile.init @@ -1,4 +1,4 @@ FROM splatform/stratos-bk-base:opensuse RUN zypper in -y curl openssh jq -COPY /deploy/db/scripts/config-init.sh /config-init.sh +COPY /deploy/containers/config-init/config-init.sh /config-init.sh COPY /deploy/tools/generate_cert.sh /generate_cert.sh diff --git a/deploy/containers/config-init/config-init.sh b/deploy/containers/config-init/config-init.sh new file mode 100755 index 0000000000..f14d8fa382 --- /dev/null +++ b/deploy/containers/config-init/config-init.sh @@ -0,0 +1,316 @@ +#!/bin/bash + +echo "============================================" +echo "Stratos Configuration Init Job" +echo "============================================" +echo "" +echo "Generating/Rotating Secrets" +echo "" +echo "NAMESPACE : ${NAMESPACE}" +echo "RELEASE_NAME : ${RELEASE_NAME}" +echo "RELEASE_REVISION : ${RELEASE_REVISION}" +echo "IS_UPGRADE : ${IS_UPGRADE}" +echo "CONSOLE_TLS_SECRET_NAME : ${CONSOLE_TLS_SECRET_NAME}" +echo "ENCRYPTION_KEY_VOLUME : ${ENCRYPTION_KEY_VOLUME}" +echo "ENCRYPTION_KEY_FILENAME : ${ENCRYPTION_KEY_FILENAME}" +echo "CONSOLE_PROXY_CERT_PATH : ${CONSOLE_PROXY_CERT_PATH}" +echo "CONSOLE_PROXY_CERT_KEY_PATH : ${CONSOLE_PROXY_CERT_KEY_PATH}" +echo "" + +# Kubernetes token +KUBE_TOKEN=$( /dev/null 2>&1 <<'EOF' + {} +EOF + DELETED=$? + if [ $DELETED -ne 0 ]; then + echo "Unable to delete secret: ${RELEASE_NAME}-${PREFIX}-${REVISION} - error code: ${DELETED}" + fi +} + +function generateCert { + if [ -n "${CONSOLE_PROXY_CERT_PATH}" ] && [ -n "${CONSOLE_PROXY_CERT_KEY_PATH}" ]; then + if [ -f "${CONSOLE_PROXY_CERT_PATH}" ] && [ -f "${CONSOLE_PROXY_CERT_KEY_PATH}" ]; then + echo "Found existing certificate on encryption key volume - going to use it" + CERT_CRT=$(cat ${CONSOLE_PROXY_CERT_PATH} | base64 -w 0) + CERT_KEY=$(cat ${CONSOLE_PROXY_CERT_KEY_PATH} | base64 -w 0) + return + fi + fi + + # Not going to support this going forward + if [ -n "${CONSOLE_CERT}" ] && [ -n "${CONSOLE_CERT_KEY}" ]; then + echo "Got a certificate in the vars CONSOLE_CERT and CONSOLE_CERT_KEY - going to use it" + fi + + echo "Using cert generator to generate a self-signed certificate ..." + export CERTS_PATH=./certs + export DEV_CERTS_DOMAIN=tls + mkdir -p ${CERTS_PATH} + /generate_cert.sh + CERT_CRT=$(cat ${CERTS_PATH}/tls.crt | base64 -w 0) + CERT_KEY=$(cat ${CERTS_PATH}/tls.key | base64 -w 0) + rm -rf ${CERTS_PATH} +} + +### +# --delete flag is used in the cleanup hook to remove the secret +### +if [ "$1" == "--delete" ]; then + echo "Cleanup up - delete hook" + # Delete the old secret + deleteSecret "key" ${RELEASE_REVISION} + deleteSecret "cert" ${RELEASE_REVISION} + exit 0 +fi + +############################################################################################################################### +# Encryption key +############################################################################################################################### + +KEY="" + +# If RELEASE_REVISION is 1 then this is a new install +# Otherwise it is an upgrade and we need to read the previous secret and +# migrate it to a new secret and change the values if needed + +if [ ${RELEASE_REVISION} -eq 1 ]; then + # Check whether the secret already exists + echo "Fresh installation - checking encryption key secret does not already exist ..." + curl -k -s \ + --fail \ + -H "Authorization: Bearer $KUBE_TOKEN" \ + -H 'Content-Type: application/json' \ + ${KUBE_API_SERVER}/api/v1/namespaces/${NAMESPACE}/secrets/${RELEASE_NAME}-key-${RELEASE_REVISION} > ./key.json 2>&1 + + EXISTS=$? + if [ $EXISTS -eq 0 ]; then + KEY=$(cat key.json | jq -r .data.key) + echo "Read Encryption Key from previous secret (although was not expecting this secret to be present)" + deleteSecret "key" ${RELEASE_REVISION} + else + echo "Fresh installation - generating a new Encryption Key" + # Generate a random encryption key + KEY=$(openssl enc -aes-256-cbc -k secret -P -md sha1 | grep key | cut -d '=' -f2 | base64 -w 0) + fi + rm -f key.json +else + echo "Upgrade - Looking for previous secret to migrate" + PREVIOUS_REVISION=$(($RELEASE_REVISION - 1)) + + # Check whether the secret already exists + curl -k -s \ + --fail \ + -H "Authorization: Bearer $KUBE_TOKEN" \ + -H 'Content-Type: application/json' \ + ${KUBE_API_SERVER}/api/v1/namespaces/${NAMESPACE}/secrets/${RELEASE_NAME}-key-${PREVIOUS_REVISION} > ./key.json 2>&1 + + EXISTS=$? + if [ $EXISTS -ne 0 ]; then + # This could be an upgrade from a version that did NOT have the config-init job + # Look for the encryption key volume + + echo "Could not find existing Encryption Key Secret - checking volume" + if [ ${ENCRYPTION_KEY_VOLUME} -a ${ENCRYPTION_KEY_FILENAME} ]; then + ekFile="${ENCRYPTION_KEY_VOLUME}/${ENCRYPTION_KEY_FILENAME}" + if [ -f "${ekFile}" ]; then + echo "Found encryption key file on the legacy encryption key volume" + KEY=$(cat ${ekFile} | base64 -w 0) + else + echo "Could not read encryption key file from legacy encryption key volume" + exit 1 + fi + else + echo "Encryption Key secret does not exist - this should have been created by a previous installation or upgrade" + echo "Generating a new one - this may result in existing tokens not being usable" + KEY=$(openssl enc -aes-256-cbc -k secret -P -md sha1 | grep key | cut -d '=' -f2 | base64 -w 0) + fi + else + KEY=$(cat key.json | jq -r .data.key) + echo "Read Encryption Key from previous secret" + + # Only delete the previous secret if it already exists + deleteSecret "key" ${PREVIOUS_REVISION} + fi + + rm -f key.json +fi + +# We will create a new secret for the encryption key +cat << EOF > create-key-secret.yaml +{ + "kind": "Secret", + "apiVersion": "v1", + "type": "Opaque", + "metadata": { + "name": "@@NAME@@" + }, + "data": { + "key": "@@KEY@@" + } +} +EOF + +NAME="${RELEASE_NAME}-key-${RELEASE_REVISION}" + +sed -i.bak "s/@@NAME@@/${NAME}/g" create-key-secret.yaml +sed -i.bak "s/@@KEY@@/${KEY}/g" create-key-secret.yaml + +echo "Creating secret for the Encryption Key: ${RELEASE_NAME}-key-${RELEASE_REVISION}" + +# Create secret for the Encryption Key +curl -k -s \ + --fail \ + -X POST \ + -d @create-key-secret.yaml \ + -H "Authorization: Bearer $KUBE_TOKEN" \ + -H 'Accept: application/json' \ + -H 'Content-Type: application/json' \ + ${KUBE_API_SERVER}/api/v1/namespaces/${NAMESPACE}/secrets > /dev/null 2>&1 + +RET_CREATE=$? +echo "Create Encryption Key secret exit code: $RET_CREATE" +rm -rf create-key-secret.yaml +if [ $RET_CREATE -ne 0 ]; then + echo "Error creating Encryption Key secret" + exit $RET_CREATE +fi + +echo "Encryption key init complete" + +############################################################################################################################### +# TLS Certificate +############################################################################################################################### + +echo "Checking TLS Certificate ..." + +# Start with these empty to indicate we won't create a secret for the TLS certificate +CERT_CRT="" +CERT_KEY="" + +if [ ${RELEASE_REVISION} -eq 1 ]; then + echo "Fresh installation - checking whether we need to generate a certificate" + + # If CONSOLE_TLS_SECRET_NAME is set, then we don't need to create a certificate + if [ -n "${CONSOLE_TLS_SECRET_NAME}" ]; then + echo "Console is using secret ${CONSOLE_TLS_SECRET_NAME} to provide TLS certificate - nothing to do" + else + # Need a new certificate + echo "New certificate needed ..." + generateCert + echo "${CERT_CRT}" + echo "${CERT_KEY}" + fi +else + echo "Upgrade - checking whether we need to generate a certificate" + + # If we have a TLS Secret name, then we don't need the generated cert - so delete the previous one, if there was one + if [ -n "${CONSOLE_TLS_SECRET_NAME}" ]; then + echo "Console is using secret ${CONSOLE_TLS_SECRET_NAME} to provide TLS certificate - removing previous generated certificate (if any)" + PREVIOUS_REVISION=$(($RELEASE_REVISION - 1)) + + # Note: This will log an error if the certificate was not previously created - we just safely ignore this + echo "Trying to delete previous generated secret if there was one ..." + deleteSecret "cert" ${PREVIOUS_REVISION} + else + # Try and get the previous cert secret - if there isn't one, then possibly the user switched from providing us one + # to wanting us to generate one + curl -k -s \ + --fail \ + -H "Authorization: Bearer $KUBE_TOKEN" \ + -H 'Content-Type: application/json' \ + ${KUBE_API_SERVER}/api/v1/namespaces/${NAMESPACE}/secrets/${RELEASE_NAME}-cert-${PREVIOUS_REVISION} > ./cert.json 2>&1 + + EXISTS=$? + if [ $EXISTS -ne 0 ]; then + echo "Could not find previous cert secret - will create a new one" + # Need a new certificate + generateCert + echo "${CERT_CRT}" + echo "${CERT_KEY}" + else + echo "Found previous secret ... going to use it" + + cat cert.json + # Just copy the secret over with a new name + CERT_CRT=$(cat cert.json | jq -r '.data["tls.crt"]') + CERT_KEY=$(cat cert.json | jq -r '.data["tls.key"]') + + echo "Read Certificate from previous secret" + echo ${CERT_CRT} + echo ${CERT_KEY} + rm -rf cert.json + + # Only delete the previous secret if it already exists + deleteSecret "cert" ${PREVIOUS_REVISION} + fi + fi +fi + +# If we have CERT_CRT and CERT_KEY then we need to create a new certificate +if [ -z "${CERT_CRT}" ] && [ -z "${CERT_KEY}" ]; then + echo "CERT_CRT and CERT_KEY are empty - nothing to do" +else + echo "CERT_CRT and CERT_KEY are NOT empty - going to create secret" + + # We will create a new secret for the certificate + cat << EOF > create-cert-secret.yaml + { + "kind": "Secret", + "apiVersion": "v1", + "type": "kubernetes.io/tls", + "metadata": { + "name": "@@NAME@@" + }, + "data": { + "tls.crt": "@@CRT@@", + "tls.key": "@@KEY@@" + } + } +EOF + + NAME="${RELEASE_NAME}-cert-${RELEASE_REVISION}" + + sed -i.bak "s/@@NAME@@/${NAME}/g" create-cert-secret.yaml + sed -i.bak "s/@@CRT@@/${CERT_CRT}/g" create-cert-secret.yaml + sed -i.bak "s/@@KEY@@/${CERT_KEY}/g" create-cert-secret.yaml + + # Create secret for the Certificate + echo "Creating secret for the Certificate: ${RELEASE_NAME}-cert-${RELEASE_REVISION}" + curl -k -s \ + --fail \ + -X POST \ + -d @create-cert-secret.yaml \ + -H "Authorization: Bearer $KUBE_TOKEN" \ + -H 'Accept: application/json' \ + -H 'Content-Type: application/json' \ + ${KUBE_API_SERVER}/api/v1/namespaces/${NAMESPACE}/secrets > /dev/null 2>&1 + + RET_CREATE=$? + echo "" + echo "Create Certificate secret exit code: $RET_CREATE" + rm -rf create-cert-secret.yaml + if [ $RET_CREATE -ne 0 ]; then + echo "Error creating Certificate secret" + exit $RET_CREATE + fi +fi + +echo "" +echo "============================================" +echo "Stratos init job complete" +echo "============================================" diff --git a/deploy/containers/nginx/conf/nginx.k8s.conf b/deploy/containers/nginx/conf/nginx.k8s.conf index 7deaa27595..2d15a1c960 100644 --- a/deploy/containers/nginx/conf/nginx.k8s.conf +++ b/deploy/containers/nginx/conf/nginx.k8s.conf @@ -45,8 +45,8 @@ http { server { listen 443 ssl; - ssl_certificate /ENCRYPTION_KEY_VOLUME/console.crt; - ssl_certificate_key /ENCRYPTION_KEY_VOLUME/console.key; + ssl_certificate /CONSOLE_CERT_PATH/tls.crt; + ssl_certificate_key /CONSOLE_CERT_PATH/tls.key; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5; diff --git a/deploy/containers/nginx/run-nginx.sh b/deploy/containers/nginx/run-nginx.sh index 2fc3c9ca4f..295b66688e 100755 --- a/deploy/containers/nginx/run-nginx.sh +++ b/deploy/containers/nginx/run-nginx.sh @@ -1,12 +1,20 @@ #!/bin/bash -sed -i -e 's@ENCRYPTION_KEY_VOLUME@'"${ENCRYPTION_KEY_VOLUME}"'@g' /etc/nginx/nginx.conf -echo "Checking if certificate has been written to the encryption volume!" + +echo "============================================" +echo "Stratos UI Container (nginx)" +echo "============================================" +echo "" + +sed -i -e 's@CONSOLE_CERT_PATH@'"${CONSOLE_CERT_PATH}"'@g' /etc/nginx/nginx.conf +echo "Checking for certificate at ${CONSOLE_CERT_PATH} ..." + while : do - if [ -f /${ENCRYPTION_KEY_VOLUME}/console.crt ]; then + if [ -f /${CONSOLE_CERT_PATH}/tls.crt ]; then break; fi sleep 1; done -echo "TLS certificate detected continuing, starting nginx." -nginx -g "daemon off;" \ No newline at end of file + +echo "TLS certificate detected ... starting nginx." +nginx -g "daemon off;" diff --git a/deploy/db/mariadb-entrypoint.sh b/deploy/db/mariadb-entrypoint.sh index 45e0471437..5f717f83ba 100755 --- a/deploy/db/mariadb-entrypoint.sh +++ b/deploy/db/mariadb-entrypoint.sh @@ -40,6 +40,16 @@ EOSQL echo 'FLUSH PRIVILEGES ;' >> "$tempSqlFile" set -- "$@" --init-file="$tempSqlFile" +else + echo "Updating passwords - writing to init-file" + tempSqlFile='/tmp/mysql-first-time.sql' + cat > "$tempSqlFile" <<-EOSQL + use mysql; + update user SET PASSWORD=PASSWORD("${MYSQL_ROOT_PASSWORD}") WHERE USER='root'; + update user SET PASSWORD=PASSWORD("${MYSQL_PASSWORD}") WHERE USER='${MYSQL_USER}'; + FLUSH PRIVILEGES; +EOSQL + set -- "$@" --init-file="$tempSqlFile" fi chown -R mysql:mysql "$MYSQL_DATADIR" diff --git a/deploy/db/scripts/README.md b/deploy/db/scripts/README.md new file mode 100644 index 0000000000..7050fecf94 --- /dev/null +++ b/deploy/db/scripts/README.md @@ -0,0 +1,3 @@ +# Deprecated + +This folder can be removed \ No newline at end of file diff --git a/deploy/db/scripts/config-init.sh b/deploy/db/scripts/config-init.sh deleted file mode 100755 index ca8db7d4a2..0000000000 --- a/deploy/db/scripts/config-init.sh +++ /dev/null @@ -1,147 +0,0 @@ -#!/bin/bash - -echo "============================================" -echo "Stratos Configuration Init Job" -echo "============================================" -echo "" -echo "Generating/Rotating Secrets" -echo "" - -env - -# Kubernetes token -KUBE_TOKEN=$( /dev/null 2>&1 <<'EOF' - {} -EOF - DELETED=$? - if [ $DELETED -ne 0 ]; then - echo "Unable to delete secret: ${RELEASE_NAME}-${PREFIX}-${REVISION} - error code: ${DELETED}" - fi -} - -### -# --delete flag is used in the cleanup hook to remove the secret -### -if [ "$1" == "--delete" ]; then - echo "Cleanup up - delete hook" - # Delete the old secret - deleteSecret "key" ${RELEASE_REVISION} - deleteSecret "cert" ${RELEASE_REVISION} - exit 0 -fi - -############################################################################################################################### -# Encryption key -############################################################################################################################### - -KEY="" - -# If RELEASE_REVISION is 1 then this is a new install -# Otherwise it is an upgrade and we need to read the previous secret and -# migrate it to a new secret and change the values if needed - -if [ ${RELEASE_REVISION} -eq 1 ]; then - echo "Fresh installation - generating a new Encryption Key" - - # Generate a random encryption key - KEY=$(openssl enc -aes-256-cbc -k secret -P -md sha1 | grep key | cut -d '=' -f2 | base64 -w 0) -else - echo "Upgrade - Looking for previous secret to migrate" - - PREVIOUS_REVISION=$(($RELEASE_REVISION - 1)) - - # Check whether the secret already exists - curl -k -s \ - --fail \ - -H "Authorization: Bearer $KUBE_TOKEN" \ - -H 'Content-Type: application/json' \ - ${KUBE_API_SERVER}/api/v1/namespaces/${NAMESPACE}/secrets/${RELEASE_NAME}-key-${PREVIOUS_REVISION} > ./key.json 2>&1 - - EXISTS=$? - if [ $EXISTS -ne 0 ]; then - # This could be an upgrade from a version that did NOT have the config-init job - # Look for the encryption key volume - - echo "Could not find existing Encryption Key Secret - checking volume" - if [ ${ENCRYPTION_KEY_VOLUME} -a ${ENCRYPTION_KEY_FILENAME} ]; then - ekFile="${ENCRYPTION_KEY_VOLUME}/${ENCRYPTION_KEY_FILENAME}" - if [ -f "${ekFile}" ]; then - echo "Found encryption key file on the legacy encryption key volume" - KEY=$(cat ${ekFile} | base64 -w 0) - else - echo "Could not read encrpytion key file from legacy encryption key volume" - exit 1 - fi - else - echo "Encryption Key secret does not exist - this should have been created by a previous installation or upgrade" - echo "Generating a new one - this may result in existing tokens not being usable" - KEY=$(openssl enc -aes-256-cbc -k secret -P -md sha1 | grep key | cut -d '=' -f2 | base64 -w 0) - fi - else - KEY=$(cat key.json | jq -r .data.key) - echo "Read Encryption Key from previous secret" - - # Only delete the previous secret if it already exists - deleteSecret "key" ${PREVIOUS_REVISION} - fi - - rm -f key.json -fi - -# We will create a new secret for the encryption key -cat << EOF > create-key-secret.yaml -{ - "kind": "Secret", - "apiVersion": "v1", - "type": "Opaque", - "metadata": { - "name": "@@NAME@@" - }, - "data": { - "key": "@@KEY@@" - } -} -EOF - -NAME="${RELEASE_NAME}-key-${RELEASE_REVISION}" - -sed -i.bak "s/@@NAME@@/${NAME}/g" create-key-secret.yaml -sed -i.bak "s/@@KEY@@/${KEY}/g" create-key-secret.yaml - -echo "Creating secret for the Encryption Key: ${RELEASE_NAME}-key-${RELEASE_REVISION}" - -# Patch secret for the Encryption Key -curl -k -s \ - --fail \ - -X POST \ - -d @create-key-secret.yaml \ - -H "Authorization: Bearer $KUBE_TOKEN" \ - -H 'Accept: application/json' \ - -H 'Content-Type: application/json' \ - ${KUBE_API_SERVER}/api/v1/namespaces/${NAMESPACE}/secrets > /dev/null 2>&1 - -RET_CREATE=$? -echo "Create Encryption Key secret exit code: $RET_CREATE" -rm -rf create-key-secret.yaml -if [ $RET_CREATE -ne 0 ]; then - echo "Error creating Encryption Key secret" - exit $RET_CREATE -fi - -echo "" -echo "Stratos init job complete" diff --git a/deploy/kubernetes/console/templates/config-init.yaml b/deploy/kubernetes/console/templates/config-init.yaml index cb0bc0ada6..1659199f12 100644 --- a/deploy/kubernetes/console/templates/config-init.yaml +++ b/deploy/kubernetes/console/templates/config-init.yaml @@ -114,10 +114,12 @@ spec: value: | {{ .Values.consoleCertKey | indent 12 }} {{- end }} + - name: CONSOLE_TLS_SECRET_NAME + value: "{{ default "" .Values.console.tlsSecretName }}" - name: CONSOLE_PROXY_CERT_PATH - value: "/{{ .Release.Name }}-encryption-key-volume/console.crt" + value: "/{{ .Release.Name }}-encryption-key-volume/console.crt" - name: CONSOLE_PROXY_CERT_KEY_PATH - value: "/{{ .Release.Name }}-encryption-key-volume/console.key" + value: "/{{ .Release.Name }}-encryption-key-volume/console.key" image: {{.Values.kube.registry.hostname}}/{{.Values.kube.organization}}/{{default "stratos-config-init" .Values.images.configInit}}:{{.Values.consoleVersion}} command: ["/config-init.sh"] imagePullPolicy: {{.Values.imagePullPolicy}} @@ -187,8 +189,8 @@ spec: imagePullSecrets: - name: {{.Values.dockerRegistrySecret}} {{- end }} - restartPolicy: "OnFailure" + restartPolicy: "Never" {{- if and (eq (printf "%s" .Values.kube.auth) "rbac") (.Capabilities.APIVersions.Has "rbac.authorization.k8s.io/v1") }} serviceAccountName: "config-init" {{- end }} - terminationGracePeriodSeconds: 600 + terminationGracePeriodSeconds: 30 diff --git a/deploy/kubernetes/console/templates/deployment.yaml b/deploy/kubernetes/console/templates/deployment.yaml index 7f58f41c15..d5988f80af 100644 --- a/deploy/kubernetes/console/templates/deployment.yaml +++ b/deploy/kubernetes/console/templates/deployment.yaml @@ -32,6 +32,13 @@ spec: - containerPort: 443 name: https protocol: TCP + env: + - name: CONSOLE_CERT_PATH + value: "/{{ .Release.Name }}-cert-volume" + volumeMounts: + - mountPath: "/{{ .Release.Name }}-cert-volume" + name: "{{ .Release.Name }}-cert-volume" + readOnly: true - image: {{.Values.kube.registry.hostname}}/{{.Values.kube.organization}}/{{.Values.images.proxy}}:{{.Values.consoleVersion}} imagePullPolicy: {{.Values.imagePullPolicy}} name: proxy @@ -231,7 +238,11 @@ spec: secretName: "{{ .Release.Name }}-key-{{ .Release.Revision }}" - name: "{{ .Release.Name }}-cert-volume" secret: - secretName: "{{ .Release.Name }}-cert-secret" + {{- if .Values.console.tlsSecretName }} + secretName: "{{ .values.console.tlsSecretName }}" + {{- else }} + secretName: "{{ .Release.Name }}-cert-{{ .Release.Revision }}" + {{- end }} - name: "{{ .Release.Name }}-db-secret" secret: secretName: "{{ .Release.Name }}-db-secret" diff --git a/deploy/kubernetes/console/templates/secrets.yaml b/deploy/kubernetes/console/templates/secrets.yaml index abf8790c61..a74c11bca0 100644 --- a/deploy/kubernetes/console/templates/secrets.yaml +++ b/deploy/kubernetes/console/templates/secrets.yaml @@ -43,7 +43,7 @@ data: {{- else }} sessionStoreSecret: {{ randAlphaNum 64 | b64enc | quote }} {{- end }} - localAdminPasword: {{ default "" .Values.console.localAdminPassword | b64enc | quote }} + localAdminPassword: {{ default "" .Values.console.localAdminPassword | b64enc | quote }} {{- if and .Values.kube.registry.username .Values.kube.registry.password }} --- @@ -61,20 +61,3 @@ metadata: data: .dockercfg: {{ template "imagePullSecret" . }} {{- end}} -{{- if not .Release.IsUpgrade }} ---- -# TODO - Only do this if not an upgrade AND the user did not specify a secret to use for the certs -apiVersion: v1 -kind: Secret -type: kubernetes.io/tls -metadata: - name: "{{ .Release.Name }}-cert-secret" - labels: - app.kubernetes.io/name: "stratos" - app.kubernetes.io/instance: "{{ .Release.Name }}" - app.kubernetes.io/version: "{{ .Chart.AppVersion }}" - app.kubernetes.io/component: "console-cert-secret" - helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" -data: -{{ ( include "console.generateCertificate" . ) | indent 2 }} -{{- end -}} diff --git a/deploy/kubernetes/console/templates/volumes.yaml b/deploy/kubernetes/console/templates/volumes.yaml index 05077f0019..9e0c9b3500 100644 --- a/deploy/kubernetes/console/templates/volumes.yaml +++ b/deploy/kubernetes/console/templates/volumes.yaml @@ -24,7 +24,8 @@ spec: requests: storage: 20Mi --- -{{- if and not .Values.mariadb.external and (.Values.mariadb.persistence.enabled (not .Values.mariadb.persistence.existingClaim)) }} +{{- if (not .Values.mariadb.external) }} +{{- if and .Values.mariadb.persistence.enabled (not .Values.mariadb.persistence.existingClaim) }} kind: PersistentVolumeClaim apiVersion: v1 metadata: @@ -52,3 +53,4 @@ spec: requests: storage: {{ .Values.mariadb.persistence.size | quote }} {{- end }} +{{- end }} diff --git a/package-lock.json b/package-lock.json index dac9c5a318..030ab2feaf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "stratos", - "version": "2.5.0", + "version": "3.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index cdce3fd811..4bb2ac5eaa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stratos", - "version": "2.5.0", + "version": "3.0.0", "description": "Stratos Console", "main": "index.js", "scripts": { From 03fe12d193dd8af42ff17e78a00d86fba1d2640a Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 1 Nov 2019 16:24:45 +0000 Subject: [PATCH 152/648] More fixes --- package.json | 1 + .../app-autoscaler-metric-chart-card.component.scss | 1 - src/frontend/packages/core/sass/_all-theme.scss | 2 ++ .../core/sass/components/ngx-charts-gauge.theme.scss | 6 ++++++ 4 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 src/frontend/packages/core/sass/components/ngx-charts-gauge.theme.scss diff --git a/package.json b/package.json index cdce3fd811..cd6fa00eeb 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "ng": "ng", "start": "npm run customize && ng serve", "start-high-mem": "npm run customize && node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng serve", + "start-dev-high-mem": "npm run customize && node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng serve --aot=false", "start-prod-high-mem": "npm run customize && node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng serve --prod", "start-dev": "ng serve --aot=false", "test": "run-s test-frontend:* --continue-on-error", diff --git a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-card/app-autoscaler-metric-chart-card.component.scss b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-card/app-autoscaler-metric-chart-card.component.scss index 002bddcdbd..de27bc5c5e 100644 --- a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-card/app-autoscaler-metric-chart-card.component.scss +++ b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-card/app-autoscaler-metric-chart-card.component.scss @@ -1,6 +1,5 @@ mat-card-header { .autoscaler-metric-subtitle { - color: rgba(0, 0, 0, .54); font-size: .9em; margin-top: .3em; } diff --git a/src/frontend/packages/core/sass/_all-theme.scss b/src/frontend/packages/core/sass/_all-theme.scss index 878cd20149..9e0e214311 100644 --- a/src/frontend/packages/core/sass/_all-theme.scss +++ b/src/frontend/packages/core/sass/_all-theme.scss @@ -44,6 +44,7 @@ @import '../src/core/stateful-icon/stateful-icon.component.theme'; @import '../src/shared/components/markdown-preview/markdown-preview.component.theme'; @import './components/mat-tabs.theme'; +@import './components/ngx-charts-gauge.theme'; @import './components/text-status.theme'; @import './components/hyperlinks.theme'; @import './mat-themes'; @@ -107,6 +108,7 @@ $side-nav-light-active: #484848; @include app-base-page-theme($theme, $app-theme); @include app-page-subheader-theme($theme, $app-theme); @include app-mat-tabs-theme($theme, $app-theme); + @include ngx-charts-gauge($theme, $app-theme); @include app-text-status-theme($theme, $app-theme); @include app-card-status-theme($theme, $app-theme); @include app-usage-gauge-theme($theme, $app-theme); diff --git a/src/frontend/packages/core/sass/components/ngx-charts-gauge.theme.scss b/src/frontend/packages/core/sass/components/ngx-charts-gauge.theme.scss new file mode 100644 index 0000000000..406a49f950 --- /dev/null +++ b/src/frontend/packages/core/sass/components/ngx-charts-gauge.theme.scss @@ -0,0 +1,6 @@ +@mixin ngx-charts-gauge($theme, $app-theme) { + $foreground-colors: map-get($theme, foreground); + ngx-charts-chart text { + fill: mat-color($foreground-colors, text); + } +} From 026e06c4637396bb280995d4cab3f20e3eea37f9 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Mon, 4 Nov 2019 09:08:59 +0000 Subject: [PATCH 153/648] Update CI pipelines for postflight and init job changes --- deploy/ci/build-images.yml | 57 ------------------------------ deploy/ci/console-dev-releases.yml | 8 ++--- 2 files changed, 4 insertions(+), 61 deletions(-) diff --git a/deploy/ci/build-images.yml b/deploy/ci/build-images.yml index d865fd61e5..287af0aed5 100644 --- a/deploy/ci/build-images.yml +++ b/deploy/ci/build-images.yml @@ -28,30 +28,6 @@ resources: stop: 2:00 AM location: UTC # Docker Images -- name: jetstream-dc-image - type: docker-image - source: - username: ((docker-username)) - password: ((docker-password)) - repository: ((docker-organization))/stratos-dc-jetstream -- name: dc-db-migrator-image - type: docker-image - source: - username: ((docker-username)) - password: ((docker-password)) - repository: ((docker-organization))/stratos-dc-db-migrator -- name: mariadb-dc-image - type: docker-image - source: - username: ((docker-username)) - password: ((docker-password)) - repository: ((docker-organization))/stratos-dc-mariadb -- name: ui-dc-image - type: docker-image - source: - username: ((docker-username)) - password: ((docker-password)) - repository: ((docker-organization))/stratos-dc-console - name: jetstream-helm-image type: docker-image source: @@ -81,7 +57,6 @@ groups: - name: tests jobs: - build-helm-images - - build-dc-images - build-aio-image jobs: @@ -117,38 +92,6 @@ jobs: tag_as_latest: true timeout: 2h30m -- name: build-dc-images - plan: - - get: stratos - - get: after-midnight - trigger: true - - aggregate: - - do: - - put: jetstream-dc-image - params: - dockerfile: stratos/deploy/Dockerfile.bk - build: stratos/ - target_name: dev-build - tag_as_latest: true - - put: dc-db-migrator-image - params: - dockerfile: stratos/deploy/Dockerfile.bk - build: stratos/ - target_name: db-migrator - tag_as_latest: true - - put: mariadb-dc-image - params: - dockerfile: stratos/deploy/db/Dockerfile.mariadb - build: stratos/deploy/db - tag_as_latest: true - - put: ui-dc-image - params: - dockerfile: stratos/deploy/Dockerfile.ui - build: stratos/ - target_name: prod-build - tag_as_latest: true - timeout: 2h30m - - name: build-aio-image public: true serial: true diff --git a/deploy/ci/console-dev-releases.yml b/deploy/ci/console-dev-releases.yml index ef7e39e757..707ffd6e0b 100644 --- a/deploy/ci/console-dev-releases.yml +++ b/deploy/ci/console-dev-releases.yml @@ -29,12 +29,12 @@ resources: username: ((docker-username)) password: ((docker-password)) repository: ((docker-repository))/stratos-jetstream -- name: postflight-image +- name: config-init-image type: docker-image source: username: ((docker-username)) password: ((docker-password)) - repository: ((docker-repository))/stratos-postflight-job + repository: ((docker-repository))/stratos-config-init - name: mariadb-image type: docker-image source: @@ -122,9 +122,9 @@ jobs: build_args_file: image-tag/build-args patch_base_reg: ((patch-base-reg)) patch_base_tag: ((patch-base-tag)) - - put: postflight-image + - put: config-init-image params: - dockerfile: stratos/deploy/Dockerfile.bk + dockerfile: stratos/deploy/Dockerfile.init build: stratos/ target_name: postflight-job tag: image-tag/v2-alpha-tag From 6e2e5e077461999932d0a235ad0402f1327c1852 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Mon, 4 Nov 2019 09:38:04 +0000 Subject: [PATCH 154/648] Fix helm unit tests --- .../console/tests/deployment_test.yaml | 6 +-- .../console/tests/user_invite_test.yaml | 32 ++++++------ .../kubernetes/console/tests/user_test.yaml | 49 ++++++++++++++----- 3 files changed, 56 insertions(+), 31 deletions(-) diff --git a/deploy/kubernetes/console/tests/deployment_test.yaml b/deploy/kubernetes/console/tests/deployment_test.yaml index 7f64dba2cf..a817f56477 100644 --- a/deploy/kubernetes/console/tests/deployment_test.yaml +++ b/deploy/kubernetes/console/tests/deployment_test.yaml @@ -8,7 +8,7 @@ tests: env.DOMAIN: test.com asserts: - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: AUTO_REG_CF_URL value: https://api.test.com @@ -17,12 +17,12 @@ tests: env.UAA_HOST: scf.test.com asserts: - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: UAA_ENDPOINT value: https://scf.scf.test.com:2793 - notContains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: AUTO_REG_CF_URL value: https://api.test.com diff --git a/deploy/kubernetes/console/tests/user_invite_test.yaml b/deploy/kubernetes/console/tests/user_invite_test.yaml index d510c141b5..6df0d7eb1f 100644 --- a/deploy/kubernetes/console/tests/user_invite_test.yaml +++ b/deploy/kubernetes/console/tests/user_invite_test.yaml @@ -6,15 +6,15 @@ tests: - it: should have default SMTP configuration asserts: - equal: - path: spec.template.spec.containers[2].name + path: spec.template.spec.containers[1].name value: proxy - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: SMTP_AUTH value: "false" - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: SMTP_HOST value: "" @@ -25,15 +25,15 @@ tests: env.SMTP_FROM_ADDRESS: "test@email.com" asserts: - equal: - path: spec.template.spec.containers[2].name + path: spec.template.spec.containers[1].name value: proxy - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: SMTP_AUTH value: "true" - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: SMTP_FROM_ADDRESS value: "test@email.com" @@ -43,15 +43,15 @@ tests: env.SMTP_PORT: "4567" asserts: - equal: - path: spec.template.spec.containers[2].name + path: spec.template.spec.containers[1].name value: proxy - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: SMTP_HOST value: "test_host" - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: SMTP_PORT value: "4567" @@ -61,15 +61,15 @@ tests: env.SMTP_PASSWORD: "test_password" asserts: - equal: - path: spec.template.spec.containers[2].name + path: spec.template.spec.containers[1].name value: proxy - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: SMTP_USER value: "test_user" - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: SMTP_PASSWORD value: "test_password" @@ -78,10 +78,10 @@ tests: console.userInviteSubject: "test subject" asserts: - equal: - path: spec.template.spec.containers[2].name + path: spec.template.spec.containers[1].name value: proxy - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: INVITE_USER_SUBJECT value: "test subject" @@ -90,10 +90,10 @@ tests: console.templatesConfigMapName: "templateCMName" asserts: - equal: - path: spec.template.spec.containers[2].name + path: spec.template.spec.containers[1].name value: proxy - contains: - path: spec.template.spec.containers[2].volumeMounts + path: spec.template.spec.containers[1].volumeMounts content: mountPath: /etc/templates/ name: "RELEASE-NAME-templates" diff --git a/deploy/kubernetes/console/tests/user_test.yaml b/deploy/kubernetes/console/tests/user_test.yaml index 07fd4e0293..ec7b472936 100644 --- a/deploy/kubernetes/console/tests/user_test.yaml +++ b/deploy/kubernetes/console/tests/user_test.yaml @@ -1,6 +1,7 @@ suite: test stratos user configuration templates: - deployment.yaml + - secrets.yaml tests: - it: should use default UAA zone set: @@ -8,7 +9,7 @@ tests: env.DOMAIN: test.com asserts: - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: UAA_ENDPOINT value: https://scf.test.com:2793 @@ -19,7 +20,7 @@ tests: env.UAA_ZONE: testzone asserts: - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: UAA_ENDPOINT value: https://testzone.test.com:2793 @@ -30,46 +31,70 @@ tests: env.DOMAIN: test.com asserts: - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: UAA_ENDPOINT value: https://scf.test.com:2793 - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: CONSOLE_CLIENT value: cf - - it: should use local user when configured + - it: should use local admin password in secret when specified set: console.localAdminPassword: TEST_PASSWORD asserts: - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: CONSOLE_ADMIN_SCOPE value: stratos.admin - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: CONSOLE_CLIENT value: cf - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: AUTH_ENDPOINT_TYPE value: local - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: LOCAL_USER value: admin - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: LOCAL_USER_PASSWORD - value: TEST_PASSWORD + valueFrom: + secretKeyRef: + key: localAdminPassword + name: RELEASE-NAME-db-secret + - contains: + path: spec.template.spec.containers[1].env + content: + name: LOCAL_USER_SCOPE + value: stratos.admin - contains: - path: spec.template.spec.containers[2].env + path: spec.template.spec.containers[1].env content: name: LOCAL_USER_SCOPE value: stratos.admin + - it: should use local user when configured + set: + console.localAdminPassword: TEST_PASSWORD + asserts: + - equal: + path: data.localAdminPassword + value: VEVTVF9QQVNTV09SRA== + template: secrets.yaml + documentIndex: 1 + + + + + + + From eb707e32ad75bce428145a1482dfc3f9e20f2456 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 5 Nov 2019 10:56:18 +0000 Subject: [PATCH 155/648] Move a few more values to secrets. Tidy ups --- .../console/templates/deployment.yaml | 43 ++++++++++++------- .../kubernetes/console/templates/secrets.yaml | 8 ++-- deploy/kubernetes/console/values.yaml | 20 ++++++--- 3 files changed, 48 insertions(+), 23 deletions(-) diff --git a/deploy/kubernetes/console/templates/deployment.yaml b/deploy/kubernetes/console/templates/deployment.yaml index d5988f80af..cf9dd2cc40 100644 --- a/deploy/kubernetes/console/templates/deployment.yaml +++ b/deploy/kubernetes/console/templates/deployment.yaml @@ -59,11 +59,15 @@ spec: name: "{{ .Release.Name }}-db-secret" key: database - name: DB_HOST + {{- if not .Values.mariadb.external }} value: "{{ .Release.Name }}-mariadb" + {{- else }} + value: {{ .Values.mariadb.host | quote }} + {{- end }} - name: DB_PORT - value: "3306" + value: {{ default "3306" .Values.mariadb.port | quote }} - name: DATABASE_PROVIDER - value: "mysql" + value: {{ default "mysql" .Values.mariadb.type | quote }} - name: HTTP_CONNECTION_TIMEOUT_IN_SECS value: "10" - name: HTTP_CLIENT_TIMEOUT_IN_SECS @@ -115,10 +119,18 @@ spec: - name: LOG_LEVEL value: {{.Values.console.backendLogLevel}} {{- end }} + - name: CONSOLE_CLIENT + valueFrom: + secretKeyRef: + name: "{{ .Release.Name }}-secret" + key: client + - name: CONSOLE_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: "{{ .Release.Name }}-secret" + key: clientSecret # Local admin user {{- if .Values.console.localAdminPassword }} - - name: CONSOLE_CLIENT - value: cf - name: CONSOLE_ADMIN_SCOPE value: stratos.admin - name: SKIP_SSL_VALIDATION @@ -130,7 +142,7 @@ spec: - name: LOCAL_USER_PASSWORD valueFrom: secretKeyRef: - name: "{{ .Release.Name }}-db-secret" + name: "{{ .Release.Name }}-secret" key: localAdminPassword - name: LOCAL_USER_SCOPE value: stratos.admin @@ -139,10 +151,6 @@ spec: {{- if or .Values.env.UAA_HOST .Values.env.DOMAIN }} - name: UAA_ENDPOINT value: {{ template "scfUaaEndpoint" . }} - - name: CONSOLE_CLIENT - value: cf - - name: CONSOLE_CLIENT_SECRET - value: {{- if .Values.env.DOMAIN }} - name: AUTO_REG_CF_URL value: https://api.{{.Values.env.DOMAIN}} @@ -154,10 +162,6 @@ spec: {{- else if .Values.uaa.host }} - name: UAA_ENDPOINT value: {{default "https://" .Values.uaa.protocol}}{{.Values.uaa.host}}:{{.Values.uaa.port}} - - name: CONSOLE_CLIENT - value: {{.Values.uaa.consoleClient}} - - name: CONSOLE_CLIENT_SECRET - value: {{.Values.uaa.consoleClientSecret}} - name: CONSOLE_ADMIN_SCOPE value: {{.Values.uaa.consoleAdminIdentifier}} - name: SKIP_SSL_VALIDATION @@ -202,9 +206,15 @@ spec: - name: SMTP_PORT value: {{ default "" .Values.env.SMTP_PORT | quote }} - name: SMTP_USER - value: {{ default "" .Values.env.SMTP_USER | quote }} + valueFrom: + secretKeyRef: + name: "{{ .Release.Name }}-secret" + key: smtpUser - name: SMTP_PASSWORD - value: {{ default "" .Values.env.SMTP_PASSWORD | quote }} + valueFrom: + secretKeyRef: + name: "{{ .Release.Name }}-secret" + key: smtpPassword - name: INVITE_USER_SUBJECT value: {{ default "" .Values.console.userInviteSubject | quote }} - name: ENABLE_TECH_PREVIEW @@ -246,6 +256,9 @@ spec: - name: "{{ .Release.Name }}-db-secret" secret: secretName: "{{ .Release.Name }}-db-secret" + - name: "{{ .Release.Name }}-secret" + secret: + secretName: "{{ .Release.Name }}-secret" {{- if .Values.console.templatesConfigMapName }} - name: {{ .Release.Name }}-templates configMap: diff --git a/deploy/kubernetes/console/templates/secrets.yaml b/deploy/kubernetes/console/templates/secrets.yaml index a74c11bca0..450533d27e 100644 --- a/deploy/kubernetes/console/templates/secrets.yaml +++ b/deploy/kubernetes/console/templates/secrets.yaml @@ -11,8 +11,11 @@ metadata: app.kubernetes.io/component: "console-secret" helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" data: - client: {{ default "" .Values.uaa.consoleClient | b64enc | quote }} + client: {{ default "cf" .Values.uaa.consoleClient | b64enc | quote }} clientSecret: {{ default "" .Values.uaa.consoleClientSecret | b64enc | quote }} + localAdminPassword: {{ default "" .Values.console.localAdminPassword | b64enc | quote }} + smtpUser: {{ default "" .Values.env.SMTP_USER | b64enc | quote }} + smtpPassword: {{ default "" .Values.env.SMTP_PASSWORD | b64enc | quote }} --- apiVersion: v1 kind: Secret @@ -37,13 +40,12 @@ data: password: {{ randAlphaNum 32 | b64enc | quote }} {{- end }} user: {{ .Values.mariadb.user | b64enc | quote }} - database: {{ .Values.mariadb.database | b64enc | quote }} + database: {{ .Values.mariadb.mariadb | b64enc | quote }} {{- if .Values.console.sessionStoreSecret }} sessionStoreSecret: {{ .Values.console.sessionStoreSecret | b64enc | quote }} {{- else }} sessionStoreSecret: {{ randAlphaNum 64 | b64enc | quote }} {{- end }} - localAdminPassword: {{ default "" .Values.console.localAdminPassword | b64enc | quote }} {{- if and .Values.kube.registry.username .Values.kube.registry.password }} --- diff --git a/deploy/kubernetes/console/values.yaml b/deploy/kubernetes/console/values.yaml index 0a3f50111e..81abf159bd 100644 --- a/deploy/kubernetes/console/values.yaml +++ b/deploy/kubernetes/console/values.yaml @@ -62,9 +62,6 @@ console: # Email subject of the user invitation message userInviteSubject: ~ - # Whether to perform the volume migration job on install/upgrade (migrate to secrets) - migrateVolumes: true - # Enable/disable Tech Preview techPreview: false @@ -77,7 +74,6 @@ console: images: console: stratos-console proxy: stratos-jetstream - postflight: stratos-postflight-job mariadb: stratos-mariadb configInit: stratos-config-init @@ -85,7 +81,7 @@ images: #storageClass: default # Database configuration -mariadb: +database: external: false database: console user: console @@ -94,6 +90,15 @@ mariadb: # Leave password blank to auto-generate (not needed for external database) rootPassword: + # DB Host + host: + + # Override port (default 3306) + port: + + # Override DB type - default is mysql + type: + resources: requests: memory: 256Mi @@ -151,6 +156,11 @@ kube: username: password: email: default + +# ================================================================================ +# Values below are for internal use only for SCF compatability - subject to change +# ================================================================================ + services: loadbalanced: false From dfa14ea446cd0d5a5238191e33457aa29fc771bb Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 6 Nov 2019 11:50:10 +0000 Subject: [PATCH 156/648] Fix issues due to broken tidy ups --- .../console/templates/deployment.yaml | 6 +-- .../kubernetes/console/templates/secrets.yaml | 3 +- .../console/tests/user_invite_test.yaml | 53 ++++++++++++------- .../kubernetes/console/tests/user_test.yaml | 14 +---- deploy/kubernetes/console/values.yaml | 4 +- 5 files changed, 43 insertions(+), 37 deletions(-) diff --git a/deploy/kubernetes/console/templates/deployment.yaml b/deploy/kubernetes/console/templates/deployment.yaml index cf9dd2cc40..56573be788 100644 --- a/deploy/kubernetes/console/templates/deployment.yaml +++ b/deploy/kubernetes/console/templates/deployment.yaml @@ -207,9 +207,9 @@ spec: value: {{ default "" .Values.env.SMTP_PORT | quote }} - name: SMTP_USER valueFrom: - secretKeyRef: - name: "{{ .Release.Name }}-secret" - key: smtpUser + secretKeyRef: + name: "{{ .Release.Name }}-secret" + key: smtpUser - name: SMTP_PASSWORD valueFrom: secretKeyRef: diff --git a/deploy/kubernetes/console/templates/secrets.yaml b/deploy/kubernetes/console/templates/secrets.yaml index 450533d27e..544108e376 100644 --- a/deploy/kubernetes/console/templates/secrets.yaml +++ b/deploy/kubernetes/console/templates/secrets.yaml @@ -40,13 +40,12 @@ data: password: {{ randAlphaNum 32 | b64enc | quote }} {{- end }} user: {{ .Values.mariadb.user | b64enc | quote }} - database: {{ .Values.mariadb.mariadb | b64enc | quote }} + database: {{ .Values.mariadb.database | b64enc | quote }} {{- if .Values.console.sessionStoreSecret }} sessionStoreSecret: {{ .Values.console.sessionStoreSecret | b64enc | quote }} {{- else }} sessionStoreSecret: {{ randAlphaNum 64 | b64enc | quote }} {{- end }} - {{- if and .Values.kube.registry.username .Values.kube.registry.password }} --- apiVersion: v1 diff --git a/deploy/kubernetes/console/tests/user_invite_test.yaml b/deploy/kubernetes/console/tests/user_invite_test.yaml index 6df0d7eb1f..b2309e8233 100644 --- a/deploy/kubernetes/console/tests/user_invite_test.yaml +++ b/deploy/kubernetes/console/tests/user_invite_test.yaml @@ -1,6 +1,7 @@ suite: test stratos user invite configuration templates: - deployment.yaml + - secrets.yaml tests: - it: should have default SMTP configuration @@ -55,24 +56,6 @@ tests: content: name: SMTP_PORT value: "4567" - - it: should allow SMTP configuration to be set for username/password - set: - env.SMTP_USER: "test_user" - env.SMTP_PASSWORD: "test_password" - asserts: - - equal: - path: spec.template.spec.containers[1].name - value: proxy - - contains: - path: spec.template.spec.containers[1].env - content: - name: SMTP_USER - value: "test_user" - - contains: - path: spec.template.spec.containers[1].env - content: - name: SMTP_PASSWORD - value: "test_password" - it: should allow SMTP configuration to be set for email sibject set: console.userInviteSubject: "test subject" @@ -108,3 +91,37 @@ tests: name: "RELEASE-NAME-templates" configMap: name: templateCMName + - it: should allow SMTP configuration to be set for username/password + set: + env.SMTP_USER: "test_user" + env.SMTP_PASSWORD: "test_password" + asserts: + - equal: + path: spec.template.spec.containers[1].name + value: proxy + - contains: + path: spec.template.spec.containers[1].env + content: + name: SMTP_USER + valueFrom: + secretKeyRef: + key: smtpUser + name: RELEASE-NAME-secret + - contains: + path: spec.template.spec.containers[1].env + content: + name: SMTP_PASSWORD + valueFrom: + secretKeyRef: + key: smtpPassword + name: RELEASE-NAME-secret + - equal: + path: data.smtpUser + value: dGVzdF91c2Vy + template: secrets.yaml + documentIndex: 0 + - equal: + path: data.smtpPassword + value: dGVzdF9wYXNzd29yZA== + template: secrets.yaml + documentIndex: 0 diff --git a/deploy/kubernetes/console/tests/user_test.yaml b/deploy/kubernetes/console/tests/user_test.yaml index ec7b472936..09adfdb541 100644 --- a/deploy/kubernetes/console/tests/user_test.yaml +++ b/deploy/kubernetes/console/tests/user_test.yaml @@ -35,11 +35,6 @@ tests: content: name: UAA_ENDPOINT value: https://scf.test.com:2793 - - contains: - path: spec.template.spec.containers[1].env - content: - name: CONSOLE_CLIENT - value: cf - it: should use local admin password in secret when specified set: console.localAdminPassword: TEST_PASSWORD @@ -49,11 +44,6 @@ tests: content: name: CONSOLE_ADMIN_SCOPE value: stratos.admin - - contains: - path: spec.template.spec.containers[1].env - content: - name: CONSOLE_CLIENT - value: cf - contains: path: spec.template.spec.containers[1].env content: @@ -71,7 +61,7 @@ tests: valueFrom: secretKeyRef: key: localAdminPassword - name: RELEASE-NAME-db-secret + name: RELEASE-NAME-secret - contains: path: spec.template.spec.containers[1].env content: @@ -90,7 +80,7 @@ tests: path: data.localAdminPassword value: VEVTVF9QQVNTV09SRA== template: secrets.yaml - documentIndex: 1 + documentIndex: 0 diff --git a/deploy/kubernetes/console/values.yaml b/deploy/kubernetes/console/values.yaml index 81abf159bd..135813c4cc 100644 --- a/deploy/kubernetes/console/values.yaml +++ b/deploy/kubernetes/console/values.yaml @@ -81,7 +81,7 @@ images: #storageClass: default # Database configuration -database: +mariadb: external: false database: console user: console @@ -112,7 +112,7 @@ uaa: protocol: https:// port: host: - consoleClient: + consoleClient: consoleClientSecret: consoleAdminIdentifier: skipSSLValidation: false From c70eef40093c132ebb251542f69a8f40f63e8827 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 7 Nov 2019 11:28:02 +0000 Subject: [PATCH 157/648] Add Helm chart icon and readme --- deploy/kubernetes/console/Chart.yaml | 3 + deploy/kubernetes/console/README.md | 323 +++++++++++++++++++++++++++ deploy/kubernetes/console/icon.png | Bin 0 -> 13121 bytes 3 files changed, 326 insertions(+) create mode 100644 deploy/kubernetes/console/README.md create mode 100644 deploy/kubernetes/console/icon.png diff --git a/deploy/kubernetes/console/Chart.yaml b/deploy/kubernetes/console/Chart.yaml index a167c169be..fcf011b8c0 100644 --- a/deploy/kubernetes/console/Chart.yaml +++ b/deploy/kubernetes/console/Chart.yaml @@ -3,3 +3,6 @@ description: A Helm chart for deploying Stratos UI Console name: console version: 0.1.0 appVersion: 0.1.0 +sources: + - https://github.com/cloudfoundry/stratos +icon: https://raw.githubusercontent.com/cloudfoundry/stratos/master/deploy/kubernetes/icon.png \ No newline at end of file diff --git a/deploy/kubernetes/console/README.md b/deploy/kubernetes/console/README.md new file mode 100644 index 0000000000..53e0dad639 --- /dev/null +++ b/deploy/kubernetes/console/README.md @@ -0,0 +1,323 @@ +# Stratos + +Stratos is an Open Source Web-based UI (Console) for managing Cloud Foundry. It allows users and administrators to both manage applications running in the Cloud Foundry cluster and perform cluster management tasks. + +## Installation + +The Helm chart is published to the Stratos Helm repository. + +You will need to have the Stratos Helm repository added to your Helm setup, if you do not, run: + +``` +helm repo add stratos https://cloudfoundry.github.io/stratos +``` +Check the repository was successfully added by searching for the `console` +``` +helm search console +NAME CHART VERSION APP VERSION DESCRIPTION +stratos/console 2.6.0 2.6.0 A Helm chart for deploying Stratos UI Console +``` +To install Stratos: + +``` +helm install stratos/console --namespace=console --name my-console +``` +> **Note**: This assumes that a storage class exists in the Kubernetes cluster that has been marked as `default`. If no such storage class exists, a specific storage class needs to be specified, please see the following section *Specifying a custom Storage Class*. + +> You can change the namespace (--namespace) and the release name (--name) to values of your choice. + +This will create a Console instance named `my-console` in a namespace called `console` in your Kubernetes cluster. + +After the install, you should be able to access the Console in a web browser by following [the instructions](#accessing-the-console) below. + +Advanced installation topics are covered in the the [Advanced Topics](#advanced-topics) section below. + +# Helm Chart Configuration + +The following table lists the configurable parameters of the Stratos Helm chart and their default values. + +|Parameter|Description|Default| +|---|---|---| +|imagePullPolicy|Image pull policy|IfNotPresent| +|console.sessionStoreSecret|Secret to use when encrypting session tokens|auto-generated random value| +|console.ssoLogin|Whether to enable SSO Login and use the UAA Login UI instead of the built-in one|false| +|console.backendLogLevel|Log level for backend (info, debug)|info| +|console.service.annotations|Annotations to add to the console service|[]| +|console.service.externalIPs|External IPs to add to the console service|[]| +|console.service.loadBalancerIP|IP address to assign to the load balancer for the metrics service (if supported)|| +|console.service.loadBalancerSourceRanges|List of IP CIDRs allowed access to load balancer (if supported)|[]| +|console.service.type|Service type for the console (ClusterIP, NodePort, LoadBalancer, ExternalName etc)|ClusterIP| +|console.service.servicePort|Service port for the console service|443| +|console.service.externalName|External name for the console service when service type is ExternalName|| +|console.service.nodePort|Node port to use for the console service when service type is NodePort or LoadBalancer|| +|console.ingress.enabled|Enable ingress for the console service|false| +|console.ingress.annotations|Annotations to add to the ingress resource|{}| +|console.ingress.extraLabels|Extra labels to add to the ingress resource|{}| +|console.ingress.host|Host for the ingress resource||| +|console.ingress.secretName|Name of an existing secret containing the TLS certificate for ingress||| +|console.service.http.enabled|Enabled HTTP access to the console service (as well as HTTPS)|false| +|console.service.http.servicePort|Service port for HTTP access to the console service when enabled|80| +|console.service.http.nodePort|Node port for HTTP access to the console service (as well as HTTPS)|| +|console.templatesConfigMapName|Name of config map that provides the template files for user invitation emails|| +|console.userInviteSubject|Email subject of the user invitation message|| +|console.techPreview|Enable/disable Tech Preview features|false| +|console.localAdminPassword|Use local admin user instead of UAA - set to a password to enable|| +|console.tlsSecretName|Secret containing TLS certificate to use for the Console|| +|console.mariadb.external|Use an external database instead of the built-in MariaDB|false| +|console.mariadb.database|Name of the database to use|console| +|console.mariadb.user|Name of the user for accessing the database|console| +|console.mariadb.userPassword|Password of the user for accessing the database. Leave blank for the built-in database to generate a random password|| +|console.mariadb.rootPassword|Password of the root user for accessing the database. Leave blank for the built-in database to generate a random password|| +|console.mariadb.host|Hostname of the database when using an external db|| +|console.mariadb.port|Port of the database when using an external db|| +|console.uaa.protocol|Protocol to use when authenticating with the UAA|https://| +|console.uaa.host|Host of the UAA to authenticate with || +|console.uaa.port|Port of the UAA to authenticate with || +|console.uaa.consoleClient|Client to use when authenticating with the UAA|cf| +|console.uaa.consoleClientSecret|Client secret to use when authenticating with the UAA|| +|console.uaa.consoleAdminIdentifier|Scope that identifies an admin user of Stratos (e.g. cloud_controller.admin|| +|console.uaa.skipSSLValidation|Skip SSL validation when when authenticating with the UAA|false| +|env.SMTP_AUTH|Authenticate against the SMTP server using AUTH command when Sending User Invite emails|false| +|env.SMTP_FROM_ADDRESS|From email address to use when Sending User Invite emails|| +|env.SMTP_USER|User name to use for authentication when Sending User Invite emails|| +|env.SMTP_PASSWORD|Password to use for authentication when Sending User Invite emails|| +|env.SMTP_HOST|Server host address to use for authentication when Sending User Invite emails|| +|env.SMTP_PORT|Server port to use for authentication when Sending User Invite emails|| +|kube.auth|Set to "rbac" if the Kubernetes cluster supports Role-based access control|"rbac"| +|kube.organization|Registry organization to use when pulling images|| +|kube.registry.hostname|Hostname of registry to use when pulling images|docker.io| +|kube.registry.username|Username to use when pulling images from the registry|| +|kube.registry.password|Password to use when pulling images from the registry|| + +## Accessing the Console + +To check the status of the instance use the following command: +``` +helm status my-console +``` + +> Note: Replace `my-console` with the value you used for the `name` parameter, or if you did not provide one, use the `helm list` command to find the release name that was automatically generated for you. + +The console is exposed via an HTTPS service - `RELEASE-NAME-ui-ext` (where RELEASE-NAME is the name used for the `name` parameter when installing). You can find the details of this service with: + +``` +$ kubectl get services -n NAMESPACE +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +my-console-mariadb ClusterIP 10.105.216.25 3306/TCP 60s +my-console-ui-ext NodePort 10.109.207.70 443:31067/TCP 60s +``` + +> In this example, Stratos has been deployed withe the service configured as `NodePort`. + +The URL you use for accessing Stratos will depend on the service configuration and the environment that you have used. + +> Note: If you did not provide a certificate when installing, Stratos will use a self-signed certificate, so you will see a certificate warning which you access Stratos in a browser. + +# Upgrading your deployment + +To upgrade your instance when using the Helm repository, fetch any updates to the repository: + +``` +$ helm repo update +``` + +To update an instance, the following assumes your instance is called `my-console`: + +``` +$ helm upgrade my-console stratos/console --recreate-pods +``` + +After the upgrade, perform a `helm list` to ensure your console is the latest version. + + +# Advanced Topics + +## Using a Load Balancer + +If your Kubernetes deployment supports automatic configuration of a load balancer (e.g. Google Container Engine), specify the parameters `console.service.type=LoadBalancer` when installing. + +``` +helm install stratos/console --namespace=console --name my-console --set console.service.type=LoadBalancer +``` + +### Using an Ingress Controller + +If your Kubernetes Cluster supports Ingress, you can expose Stratos through Ingress by supplying the appropriate ingress configuration when installing. + +This configuration is described below: + +|Parameter|Description|Default| +|----|---|---| +|console.service.ingress.enabled|Enables ingress|false| +|console.service.ingress.annotations|Annotations to be added to the ingress resource.|{}| +|console.service.ingress.extraLabels|Additional labels to be added to the ingress resource.|{}| +|console.service.ingress.host|The host name that will be used for the Stratos service.|| +|console.service.ingress.secretName|The existing TLS secret that contains the certificate for ingress.|| + +You must provide `console.service.ingress.host` when enabling ingress. + +By default a certificate will be generated for TLS. You can provide your own certificate by creating a secret and specifying this with `console.service.ingress.secretName`. + +> Note: If you do not supply `console.service.ingress.host` but do supply `env.DOMAIN` then the host `console.[env.DOMAIN]` will be used. + +## Deploying from a Private Image Repository + +If the images used by the chart are hosted in a private repository, the following needs to be specified. Save the following to a file called `private_overrides.yaml`. Replace `REGISTRY USER PASSSWORD`, `REGISTRY USERNAME`, `REGISTRY URL` with the appropriate values. `USER EMAIL` can be left blank. + +``` +kube: + registry: + password: + username: + hostname: + email: +``` + +Deploy with: + +``` +helm install stratos/console -f private_overrides.yaml --namespace=metrics +``` + +## Deploying with your own TLS certificates + +By default the console will generate self-signed certificates for demo purposes. To configure Stratos to use your provided TLS certificate, create a TLS secret in the namespace you are installing into and specify this secret name using the `` when installing. + +Assuming you have your certificate and key in the files `tls.crt` and `tls.key`, create the secret with: + +``` +kubectl create secret tls -n NAMESPACE stratos-tls-secret --cert=tls.crt --key=tls.key +``` + +> Where NAMESPACE is the namespace you are installing Stratos into - you will need to manually create the namespace first if it does not already exist. + +You can now install Stratos with: + +``` +helm install stratos/console --namespace console --name my-console --set console.tlsSecretName=stratos-tls-secret +``` + +## Specifying UAA configuration + +When deploying with SCF, the `scf-config-values.yaml` (see [SCF Wiki link](https://github.com/SUSE/scf/wiki/How-to-Install-SCF#configuring-the-deployment)) can be supplied when installing Stratos. +``` +$ helm install stratos/console -f scf-config-values.yaml +``` + +UAA configuration can be specified by providing the following configuration. + +Create a yaml file with the content below and and update according to your environment and save to a file called `uaa-config.yaml`. +``` +uaa: + protocol: https:// + port: 2793 + host: uaa.cf-dev.io + consoleClient: cf + consoleClientSecret: + consoleAdminIdentifier: cloud_controller.admin + skipSSLValidation: false +``` + +To install Stratos with the above specified configuration: +``` +$ helm install stratos/console -f uaa-config.yaml +``` + +## Configuring a local user account + +This allows for deployment without a UAA. To enable the local user account, supply a password for the local user in the deployment command, as follows. All other steps for each deployment method should be followed as in the preceding sections above. + +To deploy using our Helm repository: + +``` +helm install stratos/console --namespace=console --name my-console --set console.localAdminPassword= +``` + +To deploy using an archive file containing a given release of our Helm chart + +``` +helm install console --namespace=console --name my-console --set console.localAdminPassword= +``` + +To deploy using the latest Helm chart directly from out GitHub repository + +``` +$ helm install console --namespace console --name my-console --set console.localAdminPassword= +``` + +## Requirements + +### Storage Class + +Stratos uses persistent volumes. In order to deploy it in your Kubernetes environment, you must +have a storage class available. + +Without configuration, the Stratos Helm Chart will use the default storage class. If a default storage +class is not available, installation will fail. + +To check if a `default` storage class exists, you can list your configured storage classes with `kubectl get storageclass`. If no storage class has `(default)` after it, then you need to either specify a storage class override or declare a default storage class for your Kubernetes cluster. + +For non-production environments, you may want to use the `hostpath` storage class. See the [SCF instructions](https://github.com/SUSE/scf/wiki/How-to-Install-SCF#choosing-a-storage-class) for details on setting this up. Note that you will need to make this storage class the default storage class, e.g. + +``` +kubectl patch storageclass -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}' +``` +Where `` would be `hostpath` if you follow the SCF instructions. + + +### Specifying a custom Storage Class + +If no default storage class has been defined in the Kubernetes cluster. The Stratos helm chart will fail to deploy successfully. To check if a `default` storage class exists, you can list your configured storage classes with `kubectl`. If no storage class has `(default)` after it, then you need to either specify a storage class override or declare a default storage class for your Kubernetes cluster. + +#### Providing Storage Class override +``` +$ kubectl get storageclass +NAME TYPE +ssd kubernetes.io/host-path +persistent kubernetes.io/host-path +``` + +For instance to use the storage class `persistent` to deploy Console persistent volume claims, store the following to a file called `override.yaml`. + +``` +--- +storageClass: persistent +``` + +If you want MariaDB to use a specific storage class (which can be different to that used for the other components), then specify the following: +``` +--- +storageClass: persistent +mariadb: + persistence: + storageClass: persistent +``` + +Run Helm with the override: +``` +helm install -f override.yaml stratos/console +``` + +#### Create a default Storage Class +Alternatively, you can configure a storage class with `storageclass.kubernetes.io/is-default-class` set to `true`. For instance the following storage class will be declared as the default. If you don't have the `hostpath` provisioner available in your local cluster, please follow the instructions on [link] (https://github.com/kubernetes-incubator/external-storage/tree/master/docs/demo/hostpath-provisioner), to deploy one. + +If the hostpath provisioner is available, save the file to `storageclass.yaml` + +``` +--- +kind: StorageClass +apiVersion: storage.k8s.io/v1beta1 +metadata: + name: default + annotations: + storageclass.kubernetes.io/is-default-class: "true" +provisioner: kubernetes.io/host-path # Or whatever the local hostpath provisioner is called +``` + +To create it in your kubernetes cluster, execute the following. +``` +kubectl create -f storageclass.yaml +``` + +See [Storage Class documentation] ( https://kubernetes.io/docs/tasks/administer-cluster/change-default-storage-class/) for more insformation. + diff --git a/deploy/kubernetes/console/icon.png b/deploy/kubernetes/console/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..60a70a0749758330261c93b59af8ed3e675d4bba GIT binary patch literal 13121 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 bOauFW`%;$Ds8^M~00000NkvXXu0mjf2mm#J literal 0 HcmV?d00001 From 969b2818e96a87a1599bc326679a3f9a906bca43 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 7 Nov 2019 12:43:34 +0000 Subject: [PATCH 158/648] Tweaks to readme and the customization script invocation --- deploy/ci/tasks/dev-releases/create-chart.yml | 2 +- deploy/kubernetes/build.sh | 14 +++++++++++++- deploy/kubernetes/console/README.md | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/deploy/ci/tasks/dev-releases/create-chart.yml b/deploy/ci/tasks/dev-releases/create-chart.yml index ecd9006a4d..06d10e4c83 100644 --- a/deploy/ci/tasks/dev-releases/create-chart.yml +++ b/deploy/ci/tasks/dev-releases/create-chart.yml @@ -41,7 +41,7 @@ run: # Run customization script if there is one if [ -f "${STRATOS}/custom-src/deploy/kubernetes/customize-helm.sh" ]; then echo "Applying Helm Chart customizations" - ${STRATOS}/custom-src/deploy/kubernetes/customize-helm.sh "${STRATOS}" + ${STRATOS}/custom-src/deploy/kubernetes/customize-helm.sh "${STRATOS}/deploy/kubernetes/console" fi # Generate imagelist diff --git a/deploy/kubernetes/build.sh b/deploy/kubernetes/build.sh index e2fd3b3c4f..3ccb0c4830 100755 --- a/deploy/kubernetes/build.sh +++ b/deploy/kubernetes/build.sh @@ -216,7 +216,19 @@ pushd ${DEST_HELM_CHART_PATH} > /dev/null # Run customization script if there is one if [ -f "${STRATOS_PATH}/custom-src/deploy/kubernetes/customize-helm.sh" ]; then printf "${YELLOW}${BOLD}Applying Helm Chart customizations${RESET}\n" - ${STRATOS_PATH}/custom-src/deploy/kubernetes/customize-helm.sh "${STRATOS_PATH}" + ${STRATOS_PATH}/custom-src/deploy/kubernetes/customize-helm.sh "${DEST_HELM_CHART_PATH}" +fi + +# Look for custom config metadata with product version metadata +if [ -f "${STRATOS_PATH}/custom-src/stratos.yaml" ]; then + PROD_VERSION=$(cat "${STRATOS_PATH}/custom-src/stratos.yaml" | grep "productVersion") + if [ ! -z "${PROD_VERSION}" ]; then + PROD_VERSION=$(echo $PROD_VERSION | grep --extended --only-matching '[0-9\.]+') + if [ ! -z "${PROD_VERSION}" ]; then + echo "Setting appVersion to: ${PROD_VERSION}" + sed -i.bak -e 's/appVersion: [0-9\.]*/appVersion: '"${PROD_VERSION}"'/g' ${DEST_HELM_CHART_PATH}/Chart.yaml + fi + fi fi # Fetch subcharts diff --git a/deploy/kubernetes/console/README.md b/deploy/kubernetes/console/README.md index 53e0dad639..79bc27455d 100644 --- a/deploy/kubernetes/console/README.md +++ b/deploy/kubernetes/console/README.md @@ -1,6 +1,6 @@ # Stratos -Stratos is an Open Source Web-based UI (Console) for managing Cloud Foundry. It allows users and administrators to both manage applications running in the Cloud Foundry cluster and perform cluster management tasks. +Stratos is an Open Source Web-based Management console for Cloud Foundry. It allows users and administrators to both manage applications running in the Cloud Foundry cluster and perform cluster management tasks. ## Installation From dd0080d79a8234c1f869c38fe9d16d13ed700891 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 7 Nov 2019 13:50:02 +0000 Subject: [PATCH 159/648] Helm: Add customization of helm chart for SUSE --- .../deploy/kubernetes/customize-helm.sh | 66 ++++++++++++++++++ custom-src/deploy/kubernetes/icon.png | Bin 0 -> 1542 bytes 2 files changed, 66 insertions(+) create mode 100755 custom-src/deploy/kubernetes/customize-helm.sh create mode 100644 custom-src/deploy/kubernetes/icon.png diff --git a/custom-src/deploy/kubernetes/customize-helm.sh b/custom-src/deploy/kubernetes/customize-helm.sh new file mode 100755 index 0000000000..d7ef8c7cd3 --- /dev/null +++ b/custom-src/deploy/kubernetes/customize-helm.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +CHART_PATH=$1 + +# Script folder +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" + +CYAN="\033[96m" +YELLOW="\033[93m" +RESET="\033[0m" +BOLD="\033[1m" + +echo -e "${CYAN}${BOLD}Applying SUSE customizations to Helm Chart${RESET}" +echo "Customizations folder: ${DIR}" +echo "Chart folder : ${CHART_PATH}" +echo "" + +# =========================================================================================== +# Chart.yaml changes +# =========================================================================================== + +echo -e "${CYAN}Patching Chart.yaml${RESET}" + +# Change the sources git repo reference in Chart.yaml +sed -i.bak -e 's@https://github.com/cloudfoundry/stratos@https://github.com/SUSE/stratos@g' ${CHART_PATH}/Chart.yaml + +# Change the URL of the icon to be the SUSE one +ICON_URL="https://raw.githubusercontent.com/cloudfoundry/stratos/master/deploy/kubernetes/icon.png" +SUSE_ICON_URL="https://raw.githubusercontent.com/SUSE/stratos/custom-src/deploy/kubernetes/icon.png" +sed -i.bak -e 's@'"${ICON_URL}"'@'"${SUSE_ICON_URL}"'@g' ${CHART_PATH}/Chart.yaml + +SRC="A Helm chart for deploying Stratos UI Console" +DEST="A Helm chart for deploying SUSE Stratos Console" +sed -i.bak -e 's@'"${SRC}"'@'"${DEST}"'@g' ${CHART_PATH}/Chart.yaml + +# =========================================================================================== +# README.md changes +# =========================================================================================== + +# Need to apply these carefully + +echo -e "${CYAN}Patching README.md${RESET}" + +# Change all references to 'Stratos' (case sensitive) +sed -i.bak -e 's@Stratos@SUSE Stratos Console@g' ${CHART_PATH}/README.md + +# Change command for helm repo addition +HELM_ADD="helm repo add stratos https://cloudfoundry.github.io/stratos" +SUSE_HELM_ADD="helm repo add suse https://registry.suse.com" +sed -i.bak -e 's@'"${HELM_ADD}"'@'"${SUSE_HELM_ADD}"'@g' ${CHART_PATH}/README.md + +# Change command for helm install +HELM_INSTALL="stratos/console" +SUSE_HELM_INSTALL="suse/console " +sed -i.bak -e 's@'"${HELM_INSTALL}"'@'"${SUSE_HELM_INSTALL}"'@g' ${CHART_PATH}/README.md + +SRC="SUSE Stratos Console Helm repository" +DEST="SUSE Helm repository" +sed -i.bak -e 's@'"${SRC}"'@'"${DEST}"'@g' ${CHART_PATH}/README.md + +SRC="SUSE Stratos Console UI Console" +DEST="SUSE Stratos Console" +sed -i.bak -e 's@'"${SRC}"'@'"${DEST}"'@g' ${CHART_PATH}/README.md + +echo -e "${CYAN}${BOLD}All done applying SUSE customizations to Helm Chart${RESET}" +echo "" \ No newline at end of file diff --git a/custom-src/deploy/kubernetes/icon.png b/custom-src/deploy/kubernetes/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..215597b551b681524c64fb322862527ff9db4f0f GIT binary patch literal 1542 zcmY*Zc{mh!7#>HZ!MaLJIdX*=LYc`m?i|awW!+38H^sQGhM9;QxtWZJkX&ixnq)%Q zGN!TQYDLar9mQg?gSO|{=lOl_`yKE1KEJ=dBy)rTClCY#005juhI*C^4mk`q76!_O z=ZZ5Z)7->bpTSHYU6?6O3}hNSLh2D7msID&KI0uH4E#C$$jFb%LuAPQuRNSGe2)A7 z2@YjPZpVf{wtnvZNQiWitBh|c1Y07k06WadnxU}|U9lxO2wW!~1^}?G8|i6VdrmHo4Cj<+aRVp_iQb1n3ikw; z4qp4808Lf2_T#&vzrg1$dgQBG*jrD@@h5k=*V{o3X;9SNz1gE~LO88w8Omk?Yf{G+ zbMczK4k0NcFI#DM2(UAk5kVqUO66V$s7FdQb}xXfA9h2II2AfGZ;pqS>$JG`!QoL( zmkrV~Vx_Acydnk3F0SFX@IJ#xwxq^z(;?63_U0l?Olp21jo(!a@{ZaIliLZ4@1vs6 zhG}vvx*$0;6Q-HT0+VH_H72zy##U~^VND+(I4MKhQ-U6tN$vYIKHw0)?l)4#h$Kd3kac3ktYK-v@ZNIMHop*;8xTTFf>CbxQBLwpnS>>Mopsr0gOwq>RLdj%590qZ6Oc+tTD(#vQ zP7xFJ48(ACbobs&cvWcLu=XfQ8hxD?m1hSOxf_ZlC_I_DOR`sPwezsSZA;fP5ipe& ziz>RDIZG&sku={c0-4kC<?7kzZV?vX?H8B+My; zaBHM5&&Og|9yGYa)c`fW1OBq-FH$S?!Xap7AVdY@S3OL1eu*;1ZaPYjwSMn~Xnc>D-*k6!E5|)$V{fztNqr(LcUKCHZg1U6_d}wlPGc|3-4PBn4+i7 z#WG>!N3&@q66fTRNoYT)?8b0^j`Imb;f0WjRI#Q^ld!cGY(jw3fw!)mh=!oNtdu7R zj;FDfOgN`mwSet2<;7J;7c`Lzb=r?+{}~~Pdo;Oa#e53DLV+%LnLybH)aJSD$6iXL zhZ~XAPz^`lDzWwn7QWLU)que9P_77l(ylDn6EhWJp;69e1r&w7>GX#U7(j~}q6_6~ z1|{zI<=(OSY7to^!ueHw`Q-dq!cK7Mj6r5H%rp{20f!UG!OdqvF-;47ulP!gh|C0~ zXM!#1x0`tCg@alb<_28QlXImTW_w;5dX1!^!l^R{4oXkHQ6Zm;+eLjzQ7rG>cK=@J zAJfi$vP&o{L1%VRZe>OL7?PB3e!=FMtyfP9>CeyxG_G^=1Q7^yKlW&2IK=g$JOW`G zb6)55kTwBnyn4PM5iV_p-Rgm|M`z8`r?AD*Q17kQ{ix!!3p~-nYO1_n3(Zq1r&w9{ z&OsEHd?L~%mL2{0aQh-UcFDy*4;JHY#n8{;;ylAu{%SkVxSRkZeS}_(jw|M05=+)x literal 0 HcmV?d00001 From 2406758e2ab1d4274667b1ca7c10dba877bfc9f1 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 8 Nov 2019 15:13:52 +0000 Subject: [PATCH 160/648] Final tweaks --- .../actions/deploy-applications.actions.ts | 2 +- .../deploy-application.component.theme.scss | 12 +++++++++- .../packages/core/sass/_all-theme.scss | 4 ++-- .../sass/components/mat-snack-bar.theme.scss | 23 +++++++++++++++++++ .../core/sass/components/mat-tabs.theme.scss | 20 ---------------- .../no-content-message.component.theme.scss | 2 +- 6 files changed, 38 insertions(+), 25 deletions(-) create mode 100644 src/frontend/packages/core/sass/components/mat-snack-bar.theme.scss delete mode 100644 src/frontend/packages/core/sass/components/mat-tabs.theme.scss diff --git a/src/frontend/packages/cloud-foundry/src/actions/deploy-applications.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/deploy-applications.actions.ts index 7264dee6a7..36baef9f80 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/deploy-applications.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/deploy-applications.actions.ts @@ -9,7 +9,7 @@ import { GitAppDetails, OverrideAppDetails, SourceType } from '../store/types/de import { GitBranch, GitCommit } from '../store/types/git.types'; export const SET_APP_SOURCE_DETAILS = '[Deploy App] Application Source'; -export const CHECK_PROJECT_EXISTS = '[Deploy App] Check Projet exists'; +export const CHECK_PROJECT_EXISTS = '[Deploy App] Check Project exists'; export const PROJECT_DOESNT_EXIST = '[Deploy App] Project Doesn\'t exist'; export const PROJECT_FETCH_FAILED = '[Deploy App] Project Fetch Failed'; export const PROJECT_EXISTS = '[Deploy App] Project exists'; diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application.component.theme.scss b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application.component.theme.scss index 0ce40249ca..d5f3293582 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application.component.theme.scss +++ b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application.component.theme.scss @@ -1,7 +1,17 @@ @import '~@angular/material/theming'; @mixin app-deploy-app-theme($theme, $app-theme) { + $is-dark: map-get($theme, is-dark); + $ansi-colors: map-get($app-theme, ansi-colors); + $background-color: map-get(map-get($ansi-colors, 'white'), intense); + + @if $is-dark == true { + $background-colors: map-get($theme, background); + $foreground-colors: map-get($theme, foreground); + $background-color: lighten(mat-color($background-colors, background), 5%); + } + .deploy-app { - background-color: map-get(map-get($ansi-colors, 'white'), intense); + background-color: $background-color; } } diff --git a/src/frontend/packages/core/sass/_all-theme.scss b/src/frontend/packages/core/sass/_all-theme.scss index 9e0e214311..eebacc092f 100644 --- a/src/frontend/packages/core/sass/_all-theme.scss +++ b/src/frontend/packages/core/sass/_all-theme.scss @@ -43,7 +43,7 @@ @import '../src/features/user-profile/profile-info/profile-info.component.theme'; @import '../src/core/stateful-icon/stateful-icon.component.theme'; @import '../src/shared/components/markdown-preview/markdown-preview.component.theme'; -@import './components/mat-tabs.theme'; +@import './components/mat-snack-bar.theme'; @import './components/ngx-charts-gauge.theme'; @import './components/text-status.theme'; @import './components/hyperlinks.theme'; @@ -107,7 +107,7 @@ $side-nav-light-active: #484848; @include list-theme($theme, $app-theme); @include app-base-page-theme($theme, $app-theme); @include app-page-subheader-theme($theme, $app-theme); - @include app-mat-tabs-theme($theme, $app-theme); + @include app-mat-snack-bar-theme($theme, $app-theme); @include ngx-charts-gauge($theme, $app-theme); @include app-text-status-theme($theme, $app-theme); @include app-card-status-theme($theme, $app-theme); diff --git a/src/frontend/packages/core/sass/components/mat-snack-bar.theme.scss b/src/frontend/packages/core/sass/components/mat-snack-bar.theme.scss new file mode 100644 index 0000000000..a54bff5792 --- /dev/null +++ b/src/frontend/packages/core/sass/components/mat-snack-bar.theme.scss @@ -0,0 +1,23 @@ +// Fix the color of the underline for the active tab +// when on a primary backgrdound +// Need to revist this and check if this is an issue with the Angular Material library +@mixin app-mat-snack-bar-theme($theme, $app-theme) { + $is-dark: map-get($theme, is-dark); + $background-colors: map-get($theme, background); + $foreground-colors: map-get($theme, foreground); + + $background-color: mat-color($foreground-colors, text); + $color: mat-color($background-colors, text); + + @if $is-dark == true { + $background-color: lighten(mat-color($background-colors, background), 5%); + $color: mat-color($foreground-colors, text); + } + + .mat-snack-bar-container { + background-color: $background-color; + .mat-simple-snackbar { + color: $color; + } + } +} diff --git a/src/frontend/packages/core/sass/components/mat-tabs.theme.scss b/src/frontend/packages/core/sass/components/mat-tabs.theme.scss deleted file mode 100644 index 90bc38cd86..0000000000 --- a/src/frontend/packages/core/sass/components/mat-tabs.theme.scss +++ /dev/null @@ -1,20 +0,0 @@ -// Fix the color of the underline for the active tab -// when on a primary backgrdound -// Need to revist this and check if this is an issue with the Angular Material library -@mixin app-mat-tabs-theme($theme, $app-theme) { - $is-dark: map-get($theme, is-dark); - $primary: map-get($theme, primary); - $tabs-ink-color: mat-color($primary); - @if $is-dark == true { - $tabs-ink-color: lighten($tabs-ink-color, 20%); - } @else { - $tabs-ink-color: darken($tabs-ink-color, 20%); - } - - .mat-tab-nav-bar.mat-primary.mat-background-primary { - .mat-ink-bar { - //background-color: $tabs-ink-color; // TODO: RC - } - - } -} diff --git a/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.theme.scss b/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.theme.scss index 9e52da867e..502ecae635 100644 --- a/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.theme.scss +++ b/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.theme.scss @@ -3,6 +3,6 @@ $subdued: map-get($app-theme, subdued-color); .app-no-content-container { - color: $subdued; // TODO: RC $subdued contrast(?) + color: $subdued; } } From e1243f2a1e19dafefa3d02b1d1acf8012e74b77f Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 8 Nov 2019 15:18:32 +0000 Subject: [PATCH 161/648] Tidying up --- .../sass/components/mat-snack-bar.theme.scss | 3 -- .../packages/core/sass/dark-theme.scss | 29 +------------------ .../packages/core/src/app.component.ts | 2 +- .../dashboard-base.component.scss | 1 - .../page-side-nav.component.theme.scss | 2 +- .../profile-info/profile-info.component.html | 8 ----- .../meta-card-item.component.scss | 3 -- 7 files changed, 3 insertions(+), 45 deletions(-) diff --git a/src/frontend/packages/core/sass/components/mat-snack-bar.theme.scss b/src/frontend/packages/core/sass/components/mat-snack-bar.theme.scss index a54bff5792..536d6434d7 100644 --- a/src/frontend/packages/core/sass/components/mat-snack-bar.theme.scss +++ b/src/frontend/packages/core/sass/components/mat-snack-bar.theme.scss @@ -1,6 +1,3 @@ -// Fix the color of the underline for the active tab -// when on a primary backgrdound -// Need to revist this and check if this is an issue with the Angular Material library @mixin app-mat-snack-bar-theme($theme, $app-theme) { $is-dark: map-get($theme, is-dark); $background-colors: map-get($theme, background); diff --git a/src/frontend/packages/core/sass/dark-theme.scss b/src/frontend/packages/core/sass/dark-theme.scss index 22c5e81f64..ee4ec386ba 100644 --- a/src/frontend/packages/core/sass/dark-theme.scss +++ b/src/frontend/packages/core/sass/dark-theme.scss @@ -1,34 +1,7 @@ .dark-theme { - // @import '@angular/material/prebuilt-themes/deeppurple-amber.css'; - // color: $light-primary-text; - - // .mat-drawer-container { - // background-color: black; - // color: unset; - // } - - // .mat-app-background { - // background-color: yellow; - // color: yellow; - // } - - .dashboard { - // background-color: yellow; - } - - // $dark-primary: mat-palette($mat-yellow); - // Base $dark-primary: mat-palette($mat-blue); $dark-accent: mat-palette($mat-amber, A400, A100, A700); - - // darker blue, links too dark - // $dark-primary: mat-palette($mat-indigo); - // $dark-accent: mat-palette($mat-blue, A400, A100, A700); - - - $dark-warn: mat-palette($mat-red); - // $dark-theme: mat-dark-theme($oss-theme-primary, $oss-theme-accent, $oss-theme-warn); $dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn); $stratos-theme: $dark-theme !default; @@ -38,4 +11,4 @@ @include angular-material-theme($dark-theme); @include app-theme($stratos-theme, $stratos-nav-theme, $stratos-status-theme); -} \ No newline at end of file +} diff --git a/src/frontend/packages/core/src/app.component.ts b/src/frontend/packages/core/src/app.component.ts index f011cfd52b..5dce6095bc 100644 --- a/src/frontend/packages/core/src/app.component.ts +++ b/src/frontend/packages/core/src/app.component.ts @@ -11,7 +11,7 @@ import { LoggedInService } from './logged-in.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], + styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit, OnDestroy, AfterContentInit { diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss index 2a7eadbf3a..8c10e82191 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss @@ -3,7 +3,6 @@ $app-sub-header-height: 48px; .dashboard { - // background-color: transparent; display: flex; flex-direction: column; height: 100%; diff --git a/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.theme.scss b/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.theme.scss index 293e52286c..d7c1ebb669 100644 --- a/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.theme.scss +++ b/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.theme.scss @@ -23,7 +23,7 @@ } } &__item { - color: map-get($app-theme, subdued-color);// mat-contrast($background-color, 500); + color: map-get($app-theme, subdued-color); &--active { background-color: transparentize($primary-color, .9); color: $primary-color; diff --git a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html index 0a4c2df935..98117c306c 100644 --- a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html +++ b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html @@ -102,14 +102,6 @@

    User Profile

    {{ theme.label }} - diff --git a/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.scss b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.scss index 397814ab33..7ad3af708e 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.scss +++ b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.scss @@ -1,6 +1,5 @@ .meta-card-item-row, .meta-card-item-row-top { - // $text-color: rgba(0, 0, 0, .6); align-items: center; display: flex; justify-content: space-between; @@ -20,8 +19,6 @@ } .meta-card-item__key { - // $text-color: rgba(0, 0, 0, .6); - // color: $text-color; flex: none; font-weight: 300; padding-right: 10px; From 91b2d881cb1f280b23809df52fd76f3d8f5e9566 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 8 Nov 2019 17:04:01 +0000 Subject: [PATCH 162/648] Fix tests --- src/frontend/packages/core/test-framework/store-test-helper.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontend/packages/core/test-framework/store-test-helper.ts b/src/frontend/packages/core/test-framework/store-test-helper.ts index 0fbfec1027..054c18fd14 100644 --- a/src/frontend/packages/core/test-framework/store-test-helper.ts +++ b/src/frontend/packages/core/test-framework/store-test-helper.ts @@ -165,7 +165,8 @@ function getDefaultInitialTestStratosStoreState() { isMobile: false, isMobileNavOpen: false, sideNavPinned: false, - pollingEnabled: true + pollingEnabled: true, + themeKey: null }, actionHistory: [], lists: {}, From af6d689b6b83bc3b04406061082ddc4d98e96820 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Mon, 18 Nov 2019 10:08:52 +0000 Subject: [PATCH 163/648] Rename to SUSE Stratos Console --- .../suse-about-info.component.html | 4 ++-- .../suse-login/suse-login.component.html | 4 ++-- .../suse-welcome/suse-welcome.component.html | 2 +- custom-src/frontend/assets/nav-logo.png | Bin 1421 -> 1475 bytes custom-src/stratos.yaml | 2 +- .../packages/core/src/core/core.module.ts | 9 ++++++++- .../packages/core/src/core/core.types.ts | 9 +++++++++ .../console-uaa-wizard.component.html | 2 +- .../console-uaa-wizard.component.ts | 5 +++-- 9 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 src/frontend/packages/core/src/core/core.types.ts diff --git a/custom-src/frontend/app/custom/suse-about-info/suse-about-info.component.html b/custom-src/frontend/app/custom/suse-about-info/suse-about-info.component.html index 65c2e980f1..a0d3105d47 100644 --- a/custom-src/frontend/app/custom/suse-about-info/suse-about-info.component.html +++ b/custom-src/frontend/app/custom/suse-about-info/suse-about-info.component.html @@ -1,8 +1,8 @@ - +
    {{ (versionNumber$ | async) }}
    -
    SUSE Cloud Application Platform Console provides an easy-to-use web-based UI that allows developers and administrators to +
    SUSE Stratos Console provides an easy-to-use web-based UI that allows developers and administrators to manage and monitor Cloud Foundry and Kubernetes systems and workloads.
    diff --git a/custom-src/frontend/app/custom/suse-login/suse-login.component.html b/custom-src/frontend/app/custom/suse-login/suse-login.component.html index fd794c1b33..d8e7ee82f0 100644 --- a/custom-src/frontend/app/custom/suse-login/suse-login.component.html +++ b/custom-src/frontend/app/custom/suse-login/suse-login.component.html @@ -1,10 +1,10 @@ diff --git a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts index a252d8b69e..29c8e373ef 100644 --- a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts +++ b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts @@ -7,10 +7,15 @@ import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; import { RemoveUserFavoriteAction, } from '../../../../../store/src/actions/user-favourites-actions/remove-user-favorite-action'; -import { endpointEntitiesSelector } from '../../../../../store/src/selectors/endpoint.selectors'; +import { + endpointEntitiesSelector, + endpointsEntityRequestDataSelector, +} from '../../../../../store/src/selectors/endpoint.selectors'; import { IFavoriteMetadata, UserFavorite } from '../../../../../store/src/types/user-favorites.types'; import { userFavoritesEntitySchema } from '../../../base-entity-schemas'; +import { entityCatalogue } from '../../../core/entity-catalogue/entity-catalogue.service'; import { IFavoriteEntity } from '../../../core/user-favorite-manager'; +import { PanelPreviewService } from '../../services/panel-preview.service'; import { ComponentEntityMonitorConfig, StratosStatus } from '../../shared.types'; import { ConfirmationDialogConfig } from '../confirmation-dialog.config'; import { ConfirmationDialogService } from '../confirmation-dialog.service'; @@ -97,7 +102,33 @@ export class FavoritesMetaCardComponent { } } - constructor(private store: Store, private confirmDialog: ConfirmationDialogService) { } + constructor( + private store: Store, + private confirmDialog: ConfirmationDialogService, + private panelPreviewService: PanelPreviewService + ) { } + + previewPanel() { + const catalogueEntity = entityCatalogue.getEntity(this.favorite.endpointType, this.favorite.entityType); + const previewComponent = catalogueEntity.builders.entityBuilder.getPreviewableComponent(); + + // TODO: use 'endpoint' as constant + if (this.favorite.entityType === 'endpoint') { + const entity$ = this.store.select(endpointsEntityRequestDataSelector(this.favorite.endpointId)); + this.panelPreviewService.show(previewComponent, { + title: this.favorite.metadata.name, + entity$, + cfGuid: this.favorite.endpointId + }); + } else { + this.panelPreviewService.show(previewComponent, { + title: this.favorite.metadata.name, + cfGuid: this.favorite.metadata.cfGuid, + orgGuid: this.favorite.metadata.orgGuid, + guid: this.favorite.metadata.guid + }); + } + } public setConfirmation(prettyName: string, favorite: UserFavorite) { this.confirmation = new ConfirmationDialogConfig( diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.html b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.html index 4ad4077847..972ac85319 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.html +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.html @@ -1,6 +1,3 @@ -
    -
    -

    {{ title }}

    -
    +
    -
    \ No newline at end of file + \ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.scss b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.scss index 2a7eaa882f..e69de29bb2 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.scss +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.scss @@ -1,21 +0,0 @@ -.markdown-preview { - display: flex; - flex-direction: column; - height: 100vh; - &__header { - display: flex; - flex: 0 0 56px; - height: 56px; - - h1 { - align-self: center; - font-size: 20px; - margin-left: 24px; - } - } - &__content { - flex: 1; - overflow: auto; - padding: 10px 24px; - } -} diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts index 97384669d0..dab1792c71 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts @@ -4,13 +4,14 @@ import { DomSanitizer } from '@angular/platform-browser'; import * as markdown from 'marked'; import { LoggerService } from '../../../core/logger.service'; +import { PreviewableComponent } from '../../previewable-component'; @Component({ selector: 'app-markdown-preview', templateUrl: './markdown-preview.component.html', styleUrls: ['./markdown-preview.component.scss'] }) -export class MarkdownPreviewComponent { +export class MarkdownPreviewComponent implements PreviewableComponent { markdownHtml: string; documentUrl: string; @@ -27,7 +28,16 @@ export class MarkdownPreviewComponent { @ViewChild('markdown', { static: true }) public markdown: ElementRef; - constructor(private httpClient: HttpClient, private logger: LoggerService, private domSanitizer: DomSanitizer) { } + constructor( + private httpClient: HttpClient, + private logger: LoggerService, + private domSanitizer: DomSanitizer + ) { } + + setProps(props: { [key: string]: any }) { + console.log('props', props); + this.setDocumentUrl = props.documentUrl; + } private loadDocument() { this.httpClient.get(this.documentUrl, { responseType: 'text' }).subscribe( diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.html b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.html new file mode 100644 index 0000000000..9e95cac0d8 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.html @@ -0,0 +1,8 @@ +
    +
    +

    {{ title }}

    +
    +
    + +
    +
    \ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.scss b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.scss new file mode 100644 index 0000000000..c3afff37d8 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.scss @@ -0,0 +1,23 @@ +.sidepanel-preview { + display: flex; + flex-direction: column; + height: 100vh; + &__header { + display: flex; + flex: 0 0 56px; + height: 56px; + + h1 { + align-self: center; + font-size: 20px; + margin-left: 24px; + } + } + &__content { + flex: 1; + overflow-x: hidden; + overflow-y: auto; + padding: 12px 15px; + position: relative; + } +} diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts new file mode 100644 index 0000000000..2eb1505ae2 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts @@ -0,0 +1,37 @@ +import { HttpClient, HttpClientModule, HttpHandler } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; +import { createBasicStoreModule } from '../../../../test-framework/store-test-helper'; +import { LoggerService } from '../../../core/logger.service'; +import { SidepanelPreviewComponent } from './sidepanel-preview.component'; + +describe('SidepanelPreviewComponent', () => { + let component: SidepanelPreviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [SidepanelPreviewComponent], + providers: [LoggerService, HttpClient, HttpHandler], + imports: [ + HttpClientModule, + HttpClientTestingModule, + CoreTestingModule, + createBasicStoreModule() + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SidepanelPreviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.theme.scss b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.theme.scss similarity index 67% rename from src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.theme.scss rename to src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.theme.scss index 926eb8127e..c84bed25dc 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.theme.scss +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.theme.scss @@ -1,12 +1,14 @@ -@mixin app-markdown-preview-theme($theme, $app-theme) { +@mixin app-sidepanel-preview-theme($theme, $app-theme) { $primary: map-get($theme, primary); - .markdown-preview__header { + .sidepanel-preview__header { background-color: mat-color($primary); color: mat-contrast($primary, 500); } - .markdown-preview__content { + .sidepanel-preview__content { + background-color: map-get($app-theme, app-background-color); + > h1:first-child { height: 0; margin: 0; diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.ts b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.ts new file mode 100644 index 0000000000..315d11c160 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.ts @@ -0,0 +1,14 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'app-sidepanel-preview', + templateUrl: './sidepanel-preview.component.html', + styleUrls: ['./sidepanel-preview.component.scss'] +}) +export class SidepanelPreviewComponent { + + @Input() + title: string; + + constructor() { } +} diff --git a/src/frontend/packages/core/src/shared/previewable-component.ts b/src/frontend/packages/core/src/shared/previewable-component.ts new file mode 100644 index 0000000000..40410d6f07 --- /dev/null +++ b/src/frontend/packages/core/src/shared/previewable-component.ts @@ -0,0 +1,3 @@ +export interface PreviewableComponent { + setProps(props: { [key: string]: any }): void; +} diff --git a/src/frontend/packages/core/src/shared/services/panel-preview.service.ts b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts new file mode 100644 index 0000000000..dda1d3e374 --- /dev/null +++ b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts @@ -0,0 +1,67 @@ +import { ComponentFactory, ComponentFactoryResolver, ComponentRef, Injectable, ViewContainerRef } from '@angular/core'; +import { asapScheduler, BehaviorSubject, Observable, Subject } from 'rxjs'; +import { observeOn, publishReplay, refCount } from 'rxjs/operators'; + +@Injectable() +export class PanelPreviewService { + private openedSubject: BehaviorSubject; + public opened$: Observable; + + private container: ViewContainerRef; + + constructor(private componentFactoryResolver: ComponentFactoryResolver) { + this.openedSubject = new BehaviorSubject(false); + this.opened$ = this.observeSubject(this.openedSubject); + } + + public setContainer(container: ViewContainerRef) { + if (this.container) { + throw new Error('PanelPreviewService: container already set'); + } + + this.container = container; + } + + public show(component: object, props?: { [key: string]: any }) { + if (!this.container) { + throw new Error('PanelPreviewService: container must be set'); + } + + this.render(component, props); + this.openedSubject.next(true); + } + + public hide() { + if (!this.container) { + throw new Error('PanelPreviewService: container must be set'); + } + + this.openedSubject.next(false); + } + + render(component: object, props: { [key: string]: any }) { + if (this.container.length) { + this.container.remove(0); + } + + const factory: ComponentFactory = this.componentFactoryResolver.resolveComponentFactory(component as any); + const componentRef: ComponentRef = this.container.createComponent(factory); + + if (props) { + componentRef.instance.setProps(props); + } + } + + public clear() { + this.container.clear(); + this.openedSubject.next(false); + } + + private observeSubject(subject: Subject) { + return subject.asObservable().pipe( + publishReplay(1), + refCount(), + observeOn(asapScheduler) + ); + } +} diff --git a/src/frontend/packages/core/src/shared/shared.module.ts b/src/frontend/packages/core/src/shared/shared.module.ts index 12aae65f83..a1d52f874c 100644 --- a/src/frontend/packages/core/src/shared/shared.module.ts +++ b/src/frontend/packages/core/src/shared/shared.module.ts @@ -58,8 +58,12 @@ import { TableCellStatusDirective } from './components/list/list-table/table-cel import { TableComponent } from './components/list/list-table/table.component'; import { listTableComponents } from './components/list/list-table/table.types'; import { EndpointCardComponent } from './components/list/list-types/endpoint/endpoint-card/endpoint-card.component'; +import { EndpointListHelper } from './components/list/list-types/endpoint/endpoint-list.helpers'; +import { EndpointsListConfigService } from './components/list/list-types/endpoint/endpoints-list-config.service'; import { ListComponent } from './components/list/list.component'; import { ListConfig } from './components/list/list.component.types'; +import { ListHostDirective } from './components/list/simple-list/list-host.directive'; +import { SimpleListComponent } from './components/list/simple-list/simple-list.component'; import { LoadingPageComponent } from './components/loading-page/loading-page.component'; import { LogViewerComponent } from './components/log-viewer/log-viewer.component'; import { MarkdownContentObserverDirective } from './components/markdown-preview/markdown-content-observer.directive'; @@ -78,6 +82,7 @@ import { PageSubNavComponent } from './components/page-sub-nav/page-sub-nav.comp import { PollingIndicatorComponent } from './components/polling-indicator/polling-indicator.component'; import { RingChartComponent } from './components/ring-chart/ring-chart.component'; import { RoutingIndicatorComponent } from './components/routing-indicator/routing-indicator.component'; +import { SidepanelPreviewComponent } from './components/sidepanel-preview/sidepanel-preview.component'; import { SimpleUsageChartComponent } from './components/simple-usage-chart/simple-usage-chart.component'; import { SnackBarReturnComponent } from './components/snackbar-return/snackbar-return.component'; import { SshViewerComponent } from './components/ssh-viewer/ssh-viewer.component'; @@ -111,11 +116,8 @@ import { UsageBytesPipe } from './pipes/usage-bytes.pipe'; import { ValuesPipe } from './pipes/values.pipe'; import { CloudFoundryUserProvidedServicesService } from './services/cloud-foundry-user-provided-services.service'; import { MetricsRangeSelectorService } from './services/metrics-range-selector.service'; +import { PanelPreviewService } from './services/panel-preview.service'; import { UserPermissionDirective } from './user-permission.directive'; -import { SimpleListComponent } from './components/list/simple-list/simple-list.component'; -import { ListHostDirective } from './components/list/simple-list/list-host.directive'; -import { EndpointListHelper } from './components/list/list-types/endpoint/endpoint-list.helpers'; -import { EndpointsListConfigService } from './components/list/list-types/endpoint/endpoints-list-config.service'; /* tslint:disable:max-line-length */ @@ -211,13 +213,13 @@ import { EndpointsListConfigService } from './components/list/list-types/endpoin BreadcrumbsComponent, PageSubNavSectionComponent, EntitySummaryTitleComponent, - MarkdownPreviewComponent, MarkdownContentObserverDirective, SnackBarReturnComponent, PollingIndicatorComponent, UnlimitedInputComponent, SimpleListComponent, ListHostDirective, + SidepanelPreviewComponent ], exports: [ ApplicationStateIconPipe, @@ -300,24 +302,26 @@ import { EndpointsListConfigService } from './components/list/list-types/endpoin AppNameUniqueDirective, SimpleUsageChartComponent, EntitySummaryTitleComponent, - MarkdownPreviewComponent, MarkdownContentObserverDirective, AppNameUniqueDirective, PollingIndicatorComponent, UnlimitedInputComponent, SimpleListComponent, - ListHostDirective + ListHostDirective, + SidepanelPreviewComponent, ], entryComponents: [ DialogConfirmComponent, EnvVarViewComponent, - SnackBarReturnComponent + SnackBarReturnComponent, + MarkdownPreviewComponent, ], providers: [ ListConfig, ApplicationStateService, EndpointListHelper, EndpointsListConfigService, + PanelPreviewService, // CfUserService, ConfirmationDialogService, EntityMonitorFactory, diff --git a/src/frontend/packages/store/src/actions/dashboard-actions.ts b/src/frontend/packages/store/src/actions/dashboard-actions.ts index 88c8f8019c..08e6b3fb42 100644 --- a/src/frontend/packages/store/src/actions/dashboard-actions.ts +++ b/src/frontend/packages/store/src/actions/dashboard-actions.ts @@ -10,10 +10,6 @@ export const SET_HEADER_EVENT = '[Dashboard] Set header event'; export const ENABLE_SIDE_NAV_MOBILE_MODE = '[Dashboard] Enable mobile nav'; export const DISABLE_SIDE_NAV_MOBILE_MODE = '[Dashboard] Disable mobile nav'; -export const SHOW_SIDE_HELP = '[Dashboard] Show side help'; -export const CLOSE_SIDE_HELP = '[Dashboard] Close side help'; - - export const TIMEOUT_SESSION = '[Dashboard] Timeout Session'; export const ENABLE_POLLING = '[Dashboard] Enable Polling'; @@ -36,15 +32,6 @@ export class ToggleSideNav implements Action { type = TOGGLE_SIDE_NAV; } -export class ShowSideHelp implements Action { - constructor(public document: string) { } - type = SHOW_SIDE_HELP; -} - -export class CloseSideHelp implements Action { - type = CLOSE_SIDE_HELP; -} - export class SetHeaderEvent implements Action { constructor(public minimised = false) { } type = SET_HEADER_EVENT; diff --git a/src/frontend/packages/store/src/reducers/dashboard-reducer.ts b/src/frontend/packages/store/src/reducers/dashboard-reducer.ts index 5963b912b9..788c8307a2 100644 --- a/src/frontend/packages/store/src/reducers/dashboard-reducer.ts +++ b/src/frontend/packages/store/src/reducers/dashboard-reducer.ts @@ -1,5 +1,4 @@ import { - CLOSE_SIDE_HELP, CLOSE_SIDE_NAV, DISABLE_SIDE_NAV_MOBILE_MODE, ENABLE_POLLING, @@ -7,9 +6,10 @@ import { HYDRATE_DASHBOARD_STATE, HydrateDashboardStateAction, OPEN_SIDE_NAV, + SET_HEADER_EVENT, + SetHeaderEvent, SetPollingEnabledAction, SetSessionTimeoutAction, - SHOW_SIDE_HELP, TIMEOUT_SESSION, TOGGLE_SIDE_NAV, } from '../actions/dashboard-actions'; @@ -21,8 +21,7 @@ export interface DashboardState { isMobile: boolean; isMobileNavOpen: boolean; sideNavPinned: boolean; - sideHelpOpen: boolean; - sideHelpDocument: string; + headerEventMinimized: boolean; } export const defaultDashboardState: DashboardState = { @@ -32,8 +31,7 @@ export const defaultDashboardState: DashboardState = { isMobile: false, isMobileNavOpen: false, sideNavPinned: true, - sideHelpOpen: false, - sideHelpDocument: null, + headerEventMinimized: false, }; export function dashboardReducer(state: DashboardState = defaultDashboardState, action): DashboardState { @@ -57,10 +55,11 @@ export function dashboardReducer(state: DashboardState = defaultDashboardState, return { ...state, isMobile: true, isMobileNavOpen: false }; case DISABLE_SIDE_NAV_MOBILE_MODE: return { ...state, isMobile: false, isMobileNavOpen: false }; - case SHOW_SIDE_HELP: - return { ...state, sideHelpOpen: true, sideHelpDocument: action.document }; - case CLOSE_SIDE_HELP: - return { ...state, sideHelpOpen: false, sideHelpDocument: '' }; + case SET_HEADER_EVENT: + const setHeaderEvent = action as SetHeaderEvent; + return { + ...state, headerEventMinimized: setHeaderEvent.minimised + }; case TIMEOUT_SESSION: const timeoutSessionAction = action as SetSessionTimeoutAction; return { From 8ea2b10370e8c18fd3138dc2579d500373a07e25 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 29 Nov 2019 17:16:07 +0000 Subject: [PATCH 176/648] Fix issue where clicking on org and then app fav failed - Issue 1 (slightly unrelated) - app service getspace action used space entity without an org - Issue 2 - app entity validation found it was missing the space - validation process fetched space with custom action - custom action did not contain schema key to use space with org schema - org was not stored correctly in store (contained in space rather than seperatly) - Still to do - Fix for issue 2 would need to be expanded to ALL schema's with inline entities - These new schemas would need to be added to their entities - All usages would have to provide the overriding schemaKey --- .../src/actions/relation.actions.ts | 2 + .../actions/user-provided-service.actions.ts | 6 +-- .../cloud-foundry/src/cf-entity-factory.ts | 38 +++++++++---------- .../src/cf-entity-schema-types.ts | 10 +++-- .../applications/application.service.ts | 29 +++++++------- ...aces-user-service-instances-data-source.ts | 12 +++--- 6 files changed, 50 insertions(+), 47 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts index 985214b8dd..d686404b22 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts @@ -21,6 +21,7 @@ export abstract class FetchRelationAction extends CFStartAction implements Entit ) { super(); this.entityType = child.entityType; + this.schemaKey = child.entity.schemaKey; this.options = new HttpRequest( 'GET', url.startsWith('/v2/') ? url.substring(4, url.length) : url, @@ -32,6 +33,7 @@ export abstract class FetchRelationAction extends CFStartAction implements Entit } entity: RequestActionEntity; entityType: string; + schemaKey: string; isId = relationActionId; actions = [ '[Fetch Relations] Start', diff --git a/src/frontend/packages/cloud-foundry/src/actions/user-provided-service.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/user-provided-service.actions.ts index 09713ee768..bdedde7d05 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/user-provided-service.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/user-provided-service.actions.ts @@ -1,3 +1,5 @@ +import { HttpRequest } from '@angular/common/http'; + import { EntityCatalogueEntityConfig } from '../../../core/src/core/entity-catalogue/entity-catalogue.types'; import { getActions } from '../../../store/src/actions/action.helper'; import { endpointSchemaKey } from '../../../store/src/helpers/entity-factory'; @@ -10,7 +12,6 @@ import { organizationEntityType, serviceBindingEntityType, spaceEntityType, - spaceWithOrgEntityType, userProvidedServiceInstanceEntityType, } from '../cf-entity-types'; import { @@ -19,10 +20,9 @@ import { EntityInlineParentAction, } from '../entity-relations/entity-relations.types'; import { CFStartAction } from './cf-action.types'; -import { HttpRequest } from '@angular/common/http'; export const getUserProvidedServiceInstanceRelations = [ - createEntityRelationKey(userProvidedServiceInstanceEntityType, spaceWithOrgEntityType), + createEntityRelationKey(userProvidedServiceInstanceEntityType, spaceEntityType), createEntityRelationKey(spaceEntityType, organizationEntityType), createEntityRelationKey(userProvidedServiceInstanceEntityType, serviceBindingEntityType), createEntityRelationKey(serviceBindingEntityType, applicationEntityType) diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts index 5ed43bd63c..1da855c14e 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts @@ -244,7 +244,7 @@ const SpaceWithOrgsEntitySchema = new CFSpaceEntitySchema({ apps: [ApplicationWithoutSpaceEntitySchema], organization: OrganizationsWithoutSpaces, } -}, spaceWithOrgEntityType); +}, null, spaceWithOrgEntityType); entityCache[spaceWithOrgEntityType] = SpaceWithOrgsEntitySchema; @@ -286,7 +286,7 @@ const ApplicationEntitySchema = new CFApplicationEntitySchema( service_instances: [ServiceInstancesWithNoBindingsSchema], organization: OrganizationsWithoutSpaces, } - }), + }, null, spaceWithOrgEntityType), routes: [RouteNoAppsSchema], service_bindings: [ServiceBindingsSchema] } @@ -328,24 +328,24 @@ const CFUserSchema = new CFUserEntitySchema({ audited_spaces: [createUserOrgSpaceSchema(spaceEntityType, {}, CfUserRoleParams.AUDITED_SPACES)], } }, { - idAttribute: getAPIResourceGuid, - processStrategy: (user: APIResource) => { - if (user.entity.username) { - return user; - } - const entity = { - ...user.entity, - username: user.metadata.guid - }; - - return user.metadata ? { - entity, - metadata: user.metadata - } : { - entity - }; + idAttribute: getAPIResourceGuid, + processStrategy: (user: APIResource) => { + if (user.entity.username) { + return user; } - }); + const entity = { + ...user.entity, + username: user.metadata.guid + }; + + return user.metadata ? { + entity, + metadata: user.metadata + } : { + entity + }; + } +}); entityCache[cfUserEntityType] = CFUserSchema; diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts index 5e5ccbe303..ec7741c151 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts @@ -33,9 +33,10 @@ export class CFEntitySchema extends EntitySchema { definition?: Schema, options?: schema.EntityOptions, relationKey?: string, - excludeFromRecursiveDelete?: string[] + excludeFromRecursiveDelete?: string[], + schemaKey?: string ) { - super(entityKey, CF_ENDPOINT_TYPE, definition, options, relationKey, null, excludeFromRecursiveDelete); + super(entityKey, CF_ENDPOINT_TYPE, definition, options, relationKey, schemaKey, excludeFromRecursiveDelete); } } @@ -124,7 +125,8 @@ export class CFApplicationEntitySchema extends CFEntitySchema { export class CFSpaceEntitySchema extends CFEntitySchema { constructor( definition?: Schema, - relationKey?: string + relationKey?: string, + schemaKey?: string ) { super(spaceEntityType, definition, { idAttribute: getAPIResourceGuid }, relationKey, [ domainEntityType, @@ -133,6 +135,6 @@ export class CFSpaceEntitySchema extends CFEntitySchema { servicePlanEntityType, // App Related stackEntityType - ]); + ], schemaKey); } } diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts index ab5e375edd..c854e5f873 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts @@ -22,9 +22,9 @@ import { routeEntityType, serviceBindingEntityType, spaceEntityType, + spaceWithOrgEntityType, stackEntityType, } from '../../../../cloud-foundry/src/cf-entity-types'; -import { selectCfEntity } from '../../../../cloud-foundry/src/store/selectors/api.selectors'; import { IApp, IAppSummary, IDomain, IOrganization, ISpace } from '../../../../core/src/core/cf-api.types'; import { entityCatalogue } from '../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { EntityService } from '../../../../core/src/core/entity-service'; @@ -47,6 +47,7 @@ import { selectUpdateInfo } from '../../../../store/src/selectors/api.selectors' import { endpointEntitiesSelector } from '../../../../store/src/selectors/endpoint.selectors'; import { APIResource, EntityInfo } from '../../../../store/src/types/api.types'; import { PaginatedAction, PaginationEntityState } from '../../../../store/src/types/pagination.types'; +import { cfEntityFactory } from '../../cf-entity-factory'; import { createEntityRelationKey } from '../../entity-relations/entity-relations.types'; import { AppStat } from '../../store/types/app-metadata.types'; import { @@ -59,13 +60,13 @@ export function createGetApplicationAction(guid: string, endpointGuid: string) { return new GetApplication( guid, endpointGuid, [ - createEntityRelationKey(applicationEntityType, routeEntityType), - createEntityRelationKey(applicationEntityType, spaceEntityType), - createEntityRelationKey(applicationEntityType, stackEntityType), - createEntityRelationKey(applicationEntityType, serviceBindingEntityType), - createEntityRelationKey(routeEntityType, domainEntityType), - createEntityRelationKey(spaceEntityType, organizationEntityType), - ] + createEntityRelationKey(applicationEntityType, routeEntityType), + createEntityRelationKey(applicationEntityType, spaceEntityType), + createEntityRelationKey(applicationEntityType, stackEntityType), + createEntityRelationKey(applicationEntityType, serviceBindingEntityType), + createEntityRelationKey(routeEntityType, domainEntityType), + createEntityRelationKey(spaceEntityType, organizationEntityType), + ] ); } @@ -186,6 +187,8 @@ export class ApplicationService { app.cfGuid, { includeRelations: [createEntityRelationKey(spaceEntityType, organizationEntityType)], populateMissing: true } ); + getSpaceAction.entity = cfEntityFactory(spaceWithOrgEntityType); + getSpaceAction.schemaKey = spaceWithOrgEntityType; return this.entityServiceFactory.create>( app.space_guid, getSpaceAction @@ -198,13 +201,9 @@ export class ApplicationService { ); this.appOrg$ = moreWaiting$.pipe( first(), - switchMap(app => this.appSpace$.pipe( - map(space => space.entity.organization_guid), - switchMap(orgGuid => { - return this.store.select(selectCfEntity(organizationEntityType, orgGuid)); - }), - filter(org => !!org) - )) + switchMap(() => this.appSpace$), + map(space => space.entity.organization), + filter(org => !!org) ); this.isDeletingApp$ = this.appEntityService.isDeletingEntity$.pipe(publishReplay(1), refCount()); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts index 4c88d174d8..6769675f44 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts @@ -1,19 +1,18 @@ import { Store } from '@ngrx/store'; -import { GetAllUserProvidedServices } from '../../../../../../../cloud-foundry/src/actions/user-provided-service.actions'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { applicationEntityType, organizationEntityType, serviceBindingEntityType, spaceEntityType, - spaceWithOrgEntityType, userProvidedServiceInstanceEntityType, } from '../../../../../../../cloud-foundry/src/cf-entity-types'; import { createEntityRelationKey, createEntityRelationPaginationKey, } from '../../../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; +import { entityCatalogue } from '../../../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { ListDataSource, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; @@ -22,11 +21,12 @@ import { IListConfig, } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { APIResource } from '../../../../../../../store/src/types/api.types'; +import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; +import { + UserProvidedServiceActionBuilder, +} from '../../../../../entity-action-builders/user-provided-service.action-builders'; import { getRowMetadata } from '../../../../../features/cloud-foundry/cf.helpers'; -import { entityCatalogue } from '../../../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; -import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; -import { UserProvidedServiceActionBuilder } from '../../../../../entity-action-builders/user-provided-service.action-builders'; export class CfSpacesUserServiceInstancesDataSource extends ListDataSource { constructor(cfGuid: string, spaceGuid: string, store: Store, listConfig?: IListConfig) { @@ -38,7 +38,7 @@ export class CfSpacesUserServiceInstancesDataSource extends ListDataSource Date: Fri, 29 Nov 2019 18:04:12 +0000 Subject: [PATCH 177/648] Revert "Fix issue where clicking on org and then app fav failed" This reverts commit 0a16203284cc0b42d172972047f5d08cddfecc99. --- .../src/actions/relation.actions.ts | 2 - .../actions/user-provided-service.actions.ts | 6 +-- .../cloud-foundry/src/cf-entity-factory.ts | 38 +++++++++---------- .../src/cf-entity-schema-types.ts | 10 ++--- .../applications/application.service.ts | 29 +++++++------- ...aces-user-service-instances-data-source.ts | 12 +++--- 6 files changed, 47 insertions(+), 50 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts index d686404b22..985214b8dd 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts @@ -21,7 +21,6 @@ export abstract class FetchRelationAction extends CFStartAction implements Entit ) { super(); this.entityType = child.entityType; - this.schemaKey = child.entity.schemaKey; this.options = new HttpRequest( 'GET', url.startsWith('/v2/') ? url.substring(4, url.length) : url, @@ -33,7 +32,6 @@ export abstract class FetchRelationAction extends CFStartAction implements Entit } entity: RequestActionEntity; entityType: string; - schemaKey: string; isId = relationActionId; actions = [ '[Fetch Relations] Start', diff --git a/src/frontend/packages/cloud-foundry/src/actions/user-provided-service.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/user-provided-service.actions.ts index bdedde7d05..09713ee768 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/user-provided-service.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/user-provided-service.actions.ts @@ -1,5 +1,3 @@ -import { HttpRequest } from '@angular/common/http'; - import { EntityCatalogueEntityConfig } from '../../../core/src/core/entity-catalogue/entity-catalogue.types'; import { getActions } from '../../../store/src/actions/action.helper'; import { endpointSchemaKey } from '../../../store/src/helpers/entity-factory'; @@ -12,6 +10,7 @@ import { organizationEntityType, serviceBindingEntityType, spaceEntityType, + spaceWithOrgEntityType, userProvidedServiceInstanceEntityType, } from '../cf-entity-types'; import { @@ -20,9 +19,10 @@ import { EntityInlineParentAction, } from '../entity-relations/entity-relations.types'; import { CFStartAction } from './cf-action.types'; +import { HttpRequest } from '@angular/common/http'; export const getUserProvidedServiceInstanceRelations = [ - createEntityRelationKey(userProvidedServiceInstanceEntityType, spaceEntityType), + createEntityRelationKey(userProvidedServiceInstanceEntityType, spaceWithOrgEntityType), createEntityRelationKey(spaceEntityType, organizationEntityType), createEntityRelationKey(userProvidedServiceInstanceEntityType, serviceBindingEntityType), createEntityRelationKey(serviceBindingEntityType, applicationEntityType) diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts index 1da855c14e..5ed43bd63c 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts @@ -244,7 +244,7 @@ const SpaceWithOrgsEntitySchema = new CFSpaceEntitySchema({ apps: [ApplicationWithoutSpaceEntitySchema], organization: OrganizationsWithoutSpaces, } -}, null, spaceWithOrgEntityType); +}, spaceWithOrgEntityType); entityCache[spaceWithOrgEntityType] = SpaceWithOrgsEntitySchema; @@ -286,7 +286,7 @@ const ApplicationEntitySchema = new CFApplicationEntitySchema( service_instances: [ServiceInstancesWithNoBindingsSchema], organization: OrganizationsWithoutSpaces, } - }, null, spaceWithOrgEntityType), + }), routes: [RouteNoAppsSchema], service_bindings: [ServiceBindingsSchema] } @@ -328,24 +328,24 @@ const CFUserSchema = new CFUserEntitySchema({ audited_spaces: [createUserOrgSpaceSchema(spaceEntityType, {}, CfUserRoleParams.AUDITED_SPACES)], } }, { - idAttribute: getAPIResourceGuid, - processStrategy: (user: APIResource) => { - if (user.entity.username) { - return user; - } - const entity = { - ...user.entity, - username: user.metadata.guid - }; - - return user.metadata ? { - entity, - metadata: user.metadata - } : { - entity + idAttribute: getAPIResourceGuid, + processStrategy: (user: APIResource) => { + if (user.entity.username) { + return user; + } + const entity = { + ...user.entity, + username: user.metadata.guid }; - } -}); + + return user.metadata ? { + entity, + metadata: user.metadata + } : { + entity + }; + } + }); entityCache[cfUserEntityType] = CFUserSchema; diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts index ec7741c151..5e5ccbe303 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts @@ -33,10 +33,9 @@ export class CFEntitySchema extends EntitySchema { definition?: Schema, options?: schema.EntityOptions, relationKey?: string, - excludeFromRecursiveDelete?: string[], - schemaKey?: string + excludeFromRecursiveDelete?: string[] ) { - super(entityKey, CF_ENDPOINT_TYPE, definition, options, relationKey, schemaKey, excludeFromRecursiveDelete); + super(entityKey, CF_ENDPOINT_TYPE, definition, options, relationKey, null, excludeFromRecursiveDelete); } } @@ -125,8 +124,7 @@ export class CFApplicationEntitySchema extends CFEntitySchema { export class CFSpaceEntitySchema extends CFEntitySchema { constructor( definition?: Schema, - relationKey?: string, - schemaKey?: string + relationKey?: string ) { super(spaceEntityType, definition, { idAttribute: getAPIResourceGuid }, relationKey, [ domainEntityType, @@ -135,6 +133,6 @@ export class CFSpaceEntitySchema extends CFEntitySchema { servicePlanEntityType, // App Related stackEntityType - ], schemaKey); + ]); } } diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts index c854e5f873..ab5e375edd 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts @@ -22,9 +22,9 @@ import { routeEntityType, serviceBindingEntityType, spaceEntityType, - spaceWithOrgEntityType, stackEntityType, } from '../../../../cloud-foundry/src/cf-entity-types'; +import { selectCfEntity } from '../../../../cloud-foundry/src/store/selectors/api.selectors'; import { IApp, IAppSummary, IDomain, IOrganization, ISpace } from '../../../../core/src/core/cf-api.types'; import { entityCatalogue } from '../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { EntityService } from '../../../../core/src/core/entity-service'; @@ -47,7 +47,6 @@ import { selectUpdateInfo } from '../../../../store/src/selectors/api.selectors' import { endpointEntitiesSelector } from '../../../../store/src/selectors/endpoint.selectors'; import { APIResource, EntityInfo } from '../../../../store/src/types/api.types'; import { PaginatedAction, PaginationEntityState } from '../../../../store/src/types/pagination.types'; -import { cfEntityFactory } from '../../cf-entity-factory'; import { createEntityRelationKey } from '../../entity-relations/entity-relations.types'; import { AppStat } from '../../store/types/app-metadata.types'; import { @@ -60,13 +59,13 @@ export function createGetApplicationAction(guid: string, endpointGuid: string) { return new GetApplication( guid, endpointGuid, [ - createEntityRelationKey(applicationEntityType, routeEntityType), - createEntityRelationKey(applicationEntityType, spaceEntityType), - createEntityRelationKey(applicationEntityType, stackEntityType), - createEntityRelationKey(applicationEntityType, serviceBindingEntityType), - createEntityRelationKey(routeEntityType, domainEntityType), - createEntityRelationKey(spaceEntityType, organizationEntityType), - ] + createEntityRelationKey(applicationEntityType, routeEntityType), + createEntityRelationKey(applicationEntityType, spaceEntityType), + createEntityRelationKey(applicationEntityType, stackEntityType), + createEntityRelationKey(applicationEntityType, serviceBindingEntityType), + createEntityRelationKey(routeEntityType, domainEntityType), + createEntityRelationKey(spaceEntityType, organizationEntityType), + ] ); } @@ -187,8 +186,6 @@ export class ApplicationService { app.cfGuid, { includeRelations: [createEntityRelationKey(spaceEntityType, organizationEntityType)], populateMissing: true } ); - getSpaceAction.entity = cfEntityFactory(spaceWithOrgEntityType); - getSpaceAction.schemaKey = spaceWithOrgEntityType; return this.entityServiceFactory.create>( app.space_guid, getSpaceAction @@ -201,9 +198,13 @@ export class ApplicationService { ); this.appOrg$ = moreWaiting$.pipe( first(), - switchMap(() => this.appSpace$), - map(space => space.entity.organization), - filter(org => !!org) + switchMap(app => this.appSpace$.pipe( + map(space => space.entity.organization_guid), + switchMap(orgGuid => { + return this.store.select(selectCfEntity(organizationEntityType, orgGuid)); + }), + filter(org => !!org) + )) ); this.isDeletingApp$ = this.appEntityService.isDeletingEntity$.pipe(publishReplay(1), refCount()); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts index 6769675f44..4c88d174d8 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts @@ -1,18 +1,19 @@ import { Store } from '@ngrx/store'; +import { GetAllUserProvidedServices } from '../../../../../../../cloud-foundry/src/actions/user-provided-service.actions'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { applicationEntityType, organizationEntityType, serviceBindingEntityType, spaceEntityType, + spaceWithOrgEntityType, userProvidedServiceInstanceEntityType, } from '../../../../../../../cloud-foundry/src/cf-entity-types'; import { createEntityRelationKey, createEntityRelationPaginationKey, } from '../../../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; -import { entityCatalogue } from '../../../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { ListDataSource, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; @@ -21,12 +22,11 @@ import { IListConfig, } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { APIResource } from '../../../../../../../store/src/types/api.types'; -import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; -import { - UserProvidedServiceActionBuilder, -} from '../../../../../entity-action-builders/user-provided-service.action-builders'; import { getRowMetadata } from '../../../../../features/cloud-foundry/cf.helpers'; +import { entityCatalogue } from '../../../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; +import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; +import { UserProvidedServiceActionBuilder } from '../../../../../entity-action-builders/user-provided-service.action-builders'; export class CfSpacesUserServiceInstancesDataSource extends ListDataSource { constructor(cfGuid: string, spaceGuid: string, store: Store, listConfig?: IListConfig) { @@ -38,7 +38,7 @@ export class CfSpacesUserServiceInstancesDataSource extends ListDataSource Date: Fri, 29 Nov 2019 18:16:15 +0000 Subject: [PATCH 178/648] Fix issue where clicking on org and then app fav failed - Issue 1 (slightly unrelated) - app service getspace action used space schema without an org - Issue 2 - app entity validation found it was missing the space - validation process fetched space with custom action - custom action did not contain schema key linked to space schema with org - this lead to org being not stored correctly in store (contained in space rather than seperatly) - Simple fix (see 0a16203284c for harder) - When normalizing prioritise the action's schema over attempting to fetch via entity catalogue + schemaKey (avoids A LOT of plumbing) --- .../entity-relations/entity-relation-tree.ts | 4 +- .../applications/application.service.ts | 29 +++++---- .../entity-catalogue-entity.ts | 13 +--- .../map-multi-endpoint.pipes.ts | 62 +++++++++++-------- .../normalize-entity-request-response.pipe.ts | 14 ----- 5 files changed, 55 insertions(+), 67 deletions(-) delete mode 100644 src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/normalize-entity-request-response.pipe.ts diff --git a/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relation-tree.ts b/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relation-tree.ts index a60a65894e..d758718c6c 100644 --- a/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relation-tree.ts +++ b/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relation-tree.ts @@ -1,5 +1,5 @@ -import { EntitySchema } from '../../../store/src/helpers/entity-schema'; import { entityCatalogue } from '../../../core/src/core/entity-catalogue/entity-catalogue.service'; +import { EntitySchema } from '../../../store/src/helpers/entity-schema'; /** * A structure which represents the tree like layout of entity dependencies. For example organization --> space --> routes @@ -18,7 +18,7 @@ export class EntityTreeRelation { /** * Creates an instance of EntityTreeRelation. - * @param [isArray=false] is this a collection of entities (should be paginationed) or not + * @param [isArray=false] is this a collection of entities (should be paginated) or not * @param paramName parameter name of the entity within the schema. For example `space` may be `spaces` (entity.spaces) * @param [path=''] location of the entity within the parent. For example `space` entity maybe be `entity.spaces` */ diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts index ab5e375edd..c854e5f873 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts @@ -22,9 +22,9 @@ import { routeEntityType, serviceBindingEntityType, spaceEntityType, + spaceWithOrgEntityType, stackEntityType, } from '../../../../cloud-foundry/src/cf-entity-types'; -import { selectCfEntity } from '../../../../cloud-foundry/src/store/selectors/api.selectors'; import { IApp, IAppSummary, IDomain, IOrganization, ISpace } from '../../../../core/src/core/cf-api.types'; import { entityCatalogue } from '../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { EntityService } from '../../../../core/src/core/entity-service'; @@ -47,6 +47,7 @@ import { selectUpdateInfo } from '../../../../store/src/selectors/api.selectors' import { endpointEntitiesSelector } from '../../../../store/src/selectors/endpoint.selectors'; import { APIResource, EntityInfo } from '../../../../store/src/types/api.types'; import { PaginatedAction, PaginationEntityState } from '../../../../store/src/types/pagination.types'; +import { cfEntityFactory } from '../../cf-entity-factory'; import { createEntityRelationKey } from '../../entity-relations/entity-relations.types'; import { AppStat } from '../../store/types/app-metadata.types'; import { @@ -59,13 +60,13 @@ export function createGetApplicationAction(guid: string, endpointGuid: string) { return new GetApplication( guid, endpointGuid, [ - createEntityRelationKey(applicationEntityType, routeEntityType), - createEntityRelationKey(applicationEntityType, spaceEntityType), - createEntityRelationKey(applicationEntityType, stackEntityType), - createEntityRelationKey(applicationEntityType, serviceBindingEntityType), - createEntityRelationKey(routeEntityType, domainEntityType), - createEntityRelationKey(spaceEntityType, organizationEntityType), - ] + createEntityRelationKey(applicationEntityType, routeEntityType), + createEntityRelationKey(applicationEntityType, spaceEntityType), + createEntityRelationKey(applicationEntityType, stackEntityType), + createEntityRelationKey(applicationEntityType, serviceBindingEntityType), + createEntityRelationKey(routeEntityType, domainEntityType), + createEntityRelationKey(spaceEntityType, organizationEntityType), + ] ); } @@ -186,6 +187,8 @@ export class ApplicationService { app.cfGuid, { includeRelations: [createEntityRelationKey(spaceEntityType, organizationEntityType)], populateMissing: true } ); + getSpaceAction.entity = cfEntityFactory(spaceWithOrgEntityType); + getSpaceAction.schemaKey = spaceWithOrgEntityType; return this.entityServiceFactory.create>( app.space_guid, getSpaceAction @@ -198,13 +201,9 @@ export class ApplicationService { ); this.appOrg$ = moreWaiting$.pipe( first(), - switchMap(app => this.appSpace$.pipe( - map(space => space.entity.organization_guid), - switchMap(orgGuid => { - return this.store.select(selectCfEntity(organizationEntityType, orgGuid)); - }), - filter(org => !!org) - )) + switchMap(() => this.appSpace$), + map(space => space.entity.organization), + filter(org => !!org) ); this.isDeletingApp$ = this.appEntityService.isDeletingEntity$.pipe(publishReplay(1), refCount()); diff --git a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts index 11de55d636..64fa92b6c6 100644 --- a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts +++ b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts @@ -1,10 +1,8 @@ import { ActionReducer, Store } from '@ngrx/store'; -import { normalize } from 'normalizr'; import { AppState, IRequestEntityTypeState } from '../../../../store/src/app-state'; import { EntityPipelineEntity, stratosEndpointGuidKey } from '../../../../store/src/entity-request-pipeline/pipeline.types'; import { EntitySchema } from '../../../../store/src/helpers/entity-schema'; -import { NormalizedResponse } from '../../../../store/src/types/api.types'; import { EndpointModel } from '../../../../store/src/types/endpoint.types'; import { APISuccessOrFailedAction, EntityRequestAction } from '../../../../store/src/types/request.types'; import { IEndpointFavMetadata } from '../../../../store/src/types/user-favorites.types'; @@ -89,8 +87,8 @@ export class StratosBaseCatalogueEntity< } return newSchema; }, { - default: entitySchemas.default - }); + default: entitySchemas.default + }); } private getEndpointType(definition: IStratosBaseEntityDefinition) { @@ -217,13 +215,6 @@ export class StratosBaseCatalogueEntity< } - public getNormalizedEntityData(entities: Y | Y[], schemaKey?: string): NormalizedResponse { - const schema = this.getSchema(schemaKey); - if (Array.isArray(entities)) { - return normalize(entities, [schema]); - } - return normalize(entities, schema); - } } export class StratosCatalogueEntity< diff --git a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/map-multi-endpoint.pipes.ts b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/map-multi-endpoint.pipes.ts index 00a2a1f541..d08d04483d 100644 --- a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/map-multi-endpoint.pipes.ts +++ b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/map-multi-endpoint.pipes.ts @@ -1,4 +1,5 @@ import { Action } from '@ngrx/store'; +import { normalize } from 'normalizr'; import { StratosBaseCatalogueEntity } from '../../../../core/src/core/entity-catalogue/entity-catalogue-entity'; import { entityCatalogue } from '../../../../core/src/core/entity-catalogue/entity-catalogue.service'; @@ -10,9 +11,8 @@ import { PipelineResult } from '../entity-request-pipeline.types'; import { getSuccessMapper } from '../pipeline-helpers'; import { endpointErrorsHandlerFactory } from './endpoint-errors.handler'; import { patchActionWithForcedConfig } from './forced-action-type.helpers'; -import { HandledMultiEndpointResponse, JetstreamError } from './handle-multi-endpoints.pipe'; +import { HandledMultiEndpointResponse, JetstreamError, MultiEndpointResponse } from './handle-multi-endpoints.pipe'; import { multiEndpointResponseMergePipe } from './merge-multi-endpoint-data.pipe'; -import { normalizeEntityPipeFactory } from './normalize-entity-request-response.pipe'; const baseErrorHandler = () => 'Api Request Failed'; @@ -56,6 +56,19 @@ function getEntities( }, {}); } +// TODO: Type the output of this pipe. #3976 +function getNormalizedEntityData( + entities: any[], + action: EntityRequestAction, + catalogueEntity: StratosBaseCatalogueEntity) { + // Can patchActionWithForcedConfig be done outside of the pipe? + // This pipe shouldn't have to worry about the multi entity lists. + const patchedAction = patchActionWithForcedConfig(action); + const schema = patchedAction.entity || catalogueEntity.getSchema(patchedAction.schemaKey); + const arraySafeSchema = Array.isArray(schema) ? schema[0] : schema; + return normalize(entities, Array.isArray(entities) ? [arraySafeSchema] : arraySafeSchema); +} + export function mapMultiEndpointResponses( action: EntityRequestAction, catalogueEntity: StratosBaseCatalogueEntity, @@ -63,12 +76,6 @@ export function mapMultiEndpointResponses( multiEndpointResponses: HandledMultiEndpointResponse, actionDispatcher: (actionToDispatch: Action) => void ): PipelineResult { - const normalizeEntityPipe = normalizeEntityPipeFactory( - catalogueEntity, - // Can this be done outside of the pipe? - // This pipe shouldn't have to worry about the multi entity lists. - patchActionWithForcedConfig(action).schemaKey - ); const endpointErrorHandler = endpointErrorsHandlerFactory(actionDispatcher); endpointErrorHandler( action, @@ -84,23 +91,28 @@ export function mapMultiEndpointResponses( errorMessage }; } else { - const responses = multiEndpointResponses.successes.map(normalizeEntityPipe); - const mapped = responses.map(endpointResponse => { - const entities = getEntities(endpointResponse, action); - const parentEntities = entities[catalogueEntity.entityKey]; - return { - response: { - entities, - // If we changed the guid of the entities then make sure this is reflected in the result array. - result: parentEntities ? Object.keys(parentEntities) : endpointResponse.normalizedEntities.result, - }, - totalPages: endpointResponse.totalPages, - totalResults: endpointResponse.totalResults, - success: null - }; - }); - // NormalizedResponse - const response = multiEndpointResponseMergePipe(mapped); + const responses = multiEndpointResponses.successes + .map((responseData: MultiEndpointResponse) => ({ + normalizedEntities: getNormalizedEntityData(responseData.entities, action, catalogueEntity), + endpointGuid: responseData.endpointGuid, + totalResults: responseData.totalResults, + totalPages: responseData.totalPages + })) + .map(endpointResponse => { + const entities = getEntities(endpointResponse, action); + const parentEntities = entities[catalogueEntity.entityKey]; + return { + response: { + entities, + // If we changed the guid of the entities then make sure this is reflected in the result array. + result: parentEntities ? Object.keys(parentEntities) : endpointResponse.normalizedEntities.result, + }, + totalPages: endpointResponse.totalPages, + totalResults: endpointResponse.totalResults, + success: null + }; + }); + const response = multiEndpointResponseMergePipe(responses); return { ...response, success: true, diff --git a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/normalize-entity-request-response.pipe.ts b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/normalize-entity-request-response.pipe.ts deleted file mode 100644 index ee538455b8..0000000000 --- a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/normalize-entity-request-response.pipe.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { StratosBaseCatalogueEntity } from '../../../../core/src/core/entity-catalogue/entity-catalogue-entity'; -import { MultiEndpointResponse } from './handle-multi-endpoints.pipe'; - -// TODO: Type the output of this pipe. #3976 -export const normalizeEntityPipeFactory = (catalogueEntity: StratosBaseCatalogueEntity, schemaKey?: string) => { - return (responseData: MultiEndpointResponse) => { - return { - normalizedEntities: catalogueEntity.getNormalizedEntityData(responseData.entities, schemaKey), - endpointGuid: responseData.endpointGuid, - totalResults: responseData.totalResults, - totalPages: responseData.totalPages - }; - }; -}; From 4b4c6b5c3023b29a34f1258c19b53b810a9fa6f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Mon, 2 Dec 2019 14:18:03 -0300 Subject: [PATCH 179/648] sidepanel preview: cleanup and minor improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vítor Avelino --- .../services/cloud-foundry-space.service.ts | 4 + ...cloud-foundry-space-summary.component.html | 2 +- .../application-preview.component.html | 126 +++++++++--------- .../application-preview.component.ts | 27 ++-- .../organization-preview.component.html | 2 +- .../packages/core/src/base-entity-schemas.ts | 3 +- .../entity-catalogue-entity.ts | 2 +- .../entity-catalogue.types.ts | 1 + .../dashboard-base.component.scss | 5 + .../connect-endpoint-dialog.component.ts | 6 +- .../create-endpoint-connect.component.ts | 8 +- .../favorites-meta-card.component.ts | 4 +- .../markdown-preview.component.ts | 1 - .../shared/services/panel-preview.service.ts | 17 ++- 14 files changed, 121 insertions(+), 87 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts index 371b9874d9..a3a230d5c7 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts @@ -103,6 +103,10 @@ export class CloudFoundrySpaceService { this.usersCount$ = this.cfUserService.fetchTotalUsers(this.cfGuid, this.orgGuid, this.spaceGuid); } + public fetchApps() { + this.cfEndpointService.fetchApps(); + } + private initialiseSpaceObservables() { this.space$ = this.cfUserService.isConnectedUserAdmin(this.cfGuid).pipe( switchMap(isAdmin => { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html index 1f03e7bdb5..3ec288a9fa 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html @@ -75,7 +75,7 @@ + [loading$]="cfSpaceService.loadingApps$" (refresh)="cfSpaceService.fetchApps()"> diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html index 9900b6a612..f47a8f18fe 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html @@ -1,68 +1,70 @@ - - -
    - - + + + +
    + + + Summary + + + + + + {{ appSvc.cf?.name}} + + + {{ appSvc.cf?.name}} + + + + + + + +
    +
    +
    - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + +
    \ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts index 5aa2772e67..649e38af6f 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts @@ -1,30 +1,27 @@ import { Component } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs'; -import { combineLatest, map } from 'rxjs/operators'; +import { combineLatest, distinct, map } from 'rxjs/operators'; +import { IAppSummary } from '../../../../../core/src/core/cf-api.types'; import { getFullEndpointApiUrl } from '../../../../../core/src/features/endpoints/endpoint-helpers'; import { APP_GUID, CF_GUID } from '../../../../../core/src/shared/entity.tokens'; import { PreviewableComponent } from '../../../../../core/src/shared/previewable-component'; -import { ApplicationService } from '../../../features/applications/application.service'; -import { getGuids } from '../../../features/applications/application/application-base.component'; +import { EntityInfo } from '../../../../../store/src/types/api.types'; +import { ApplicationData, ApplicationService } from '../../../features/applications/application.service'; @Component({ selector: 'app-application-preview-component', templateUrl: './application-preview.component.html', styleUrls: ['./application-preview.component.scss'], - // useless, necessary to reuse ApplicationService providers: [ ApplicationService, { provide: CF_GUID, - useFactory: getGuids('cf'), - deps: [ActivatedRoute] + useValue: '', }, { provide: APP_GUID, - useFactory: getGuids(), - deps: [ActivatedRoute] + useValue: '', }, ] }) @@ -33,6 +30,7 @@ export class ApplicationPreviewComponent implements PreviewableComponent { title = null; cfEndpointService: object; sshStatus$: Observable; + detailsLoading$: Observable; getFullEndpointApiUrl = getFullEndpointApiUrl; @@ -54,8 +52,15 @@ export class ApplicationPreviewComponent implements PreviewableComponent { }) ); - // this.detailsLoading$ = combineLatest([ - // // Wait for the apps to have been fetched, this will determine if multiple small cards are shown or now + this.detailsLoading$ = this.applicationService.application$.pipe( + combineLatest( + this.applicationService.appSummary$ + ), + map(([app, appSummary]: [ApplicationData, EntityInfo]) => { + return app.fetching || appSummary.entityRequestInfo.fetching; + }), distinct()); + + // // Wait for the apps to have been fetched, this will determine if multiple small cards are shown or now // this.cfEndpointService.appsPagObs.fetchingEntities$.pipe( // filter(loading => !loading) // ), diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html index 94359a1d9b..6387b13274 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html @@ -94,7 +94,7 @@ + [loading$]="cfOrgService.loadingApps$" (refresh)="cfOrgService.fetchApps()"> diff --git a/src/frontend/packages/core/src/base-entity-schemas.ts b/src/frontend/packages/core/src/base-entity-schemas.ts index 6a1f2c9ee8..4242afd616 100644 --- a/src/frontend/packages/core/src/base-entity-schemas.ts +++ b/src/frontend/packages/core/src/base-entity-schemas.ts @@ -1,13 +1,14 @@ import { endpointSchemaKey, entityFactory, + systemInfoSchemaKey, userFavouritesSchemaKey, userProfileSchemaKey, - systemInfoSchemaKey, } from '../../store/src/helpers/entity-factory'; import { EntitySchema } from '../../store/src/helpers/entity-schema'; export const STRATOS_ENDPOINT_TYPE = 'stratos'; +export const ENDPOINT_TYPE = 'endpoint'; class StratosEntitySchema extends EntitySchema { constructor(entityType: string) { diff --git a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts index 64fa92b6c6..4ca21ea92d 100644 --- a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts +++ b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts @@ -255,7 +255,7 @@ export class StratosCatalogueEndpointEntity extends StratosBaseCatalogueEntity string, - // TODO: attach to PreviewableComponent + // TODO find a way to attach this to PreviewableComponent getPreviewableComponent?: () => object ) { const fullEntity = { diff --git a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue.types.ts b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue.types.ts index 241c7d5643..6e948950f9 100644 --- a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue.types.ts +++ b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue.types.ts @@ -149,6 +149,7 @@ export interface IStratosEntityBuilder { getStatusObservable?(entity: Y): Observable; // TODO This should be used in the entities schema. getGuid(entityMetadata: T): string; + // TODO find a way to attach this to PreviewableComponent getPreviewableComponent?(): object; getLink?(entityMetadata: T): string; getLines?(): EntityRowBuilder[]; diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss index 82277d48cb..41575a238a 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss @@ -55,7 +55,12 @@ $app-sub-header-height: 48px; padding: 0; } &__side-help { + max-width: 600px; min-width: 600px; + + @include breakpoint(mobileonly) { + min-width: auto; + } } } diff --git a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts index b4c554eba4..788d2bc4ae 100644 --- a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts @@ -6,6 +6,8 @@ import { Subscription } from 'rxjs'; import { ShowSnackBar } from '../../../../../store/src/actions/snackBar.actions'; import { EndpointOnlyAppState } from '../../../../../store/src/app-state'; import { EndpointsService } from '../../../core/endpoints.service'; +import { MarkdownPreviewComponent } from '../../../shared/components/markdown-preview/markdown-preview.component'; +import { PanelPreviewService } from '../../../shared/services/panel-preview.service'; import { ConnectEndpointConfig, ConnectEndpointService } from '../connect.service'; @@ -27,6 +29,7 @@ export class ConnectEndpointDialogComponent implements OnDestroy { @Inject(MAT_DIALOG_DATA) public data: ConnectEndpointConfig, private store: Store, endpointsService: EndpointsService, + private panelPreviewService: PanelPreviewService, ) { this.connectService = new ConnectEndpointService(store, endpointsService, data); @@ -37,8 +40,7 @@ export class ConnectEndpointDialogComponent implements OnDestroy { } showHelp() { - // this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); - // this.store.dispatch(new ShowSideHelp(MarkdownPreviewComponent, { setDocumentUrl: this.helpDocumentUrl })); + this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); } ngOnDestroy(): void { diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts index b2282192bb..ff128ef73c 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts @@ -5,7 +5,9 @@ import { map } from 'rxjs/operators'; import { EndpointOnlyAppState } from '../../../../../../store/src/app-state'; import { EndpointsService } from '../../../../core/endpoints.service'; +import { MarkdownPreviewComponent } from '../../../../shared/components/markdown-preview/markdown-preview.component'; import { IStepperStep, StepOnNextResult } from '../../../../shared/components/stepper/step/step.component'; +import { PanelPreviewService } from '../../../../shared/services/panel-preview.service'; import { ConnectEndpointConfig, ConnectEndpointService } from '../../connect.service'; @@ -25,13 +27,13 @@ export class CreateEndpointConnectComponent implements OnDestroy, IStepperStep { constructor( private store: Store, - private endpointsService: EndpointsService + private endpointsService: EndpointsService, + private panelPreviewService: PanelPreviewService, ) { } showHelp() { - // this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); - // this.store.dispatch(new ShowSideHelp(MarkdownPreviewComponent, { setDocumentUrl: this.helpDocumentUrl })); + this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); } onEnter = (data: ConnectEndpointConfig) => { diff --git a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts index 29c8e373ef..5a91f03f22 100644 --- a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts +++ b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts @@ -12,7 +12,7 @@ import { endpointsEntityRequestDataSelector, } from '../../../../../store/src/selectors/endpoint.selectors'; import { IFavoriteMetadata, UserFavorite } from '../../../../../store/src/types/user-favorites.types'; -import { userFavoritesEntitySchema } from '../../../base-entity-schemas'; +import { ENDPOINT_TYPE, userFavoritesEntitySchema } from '../../../base-entity-schemas'; import { entityCatalogue } from '../../../core/entity-catalogue/entity-catalogue.service'; import { IFavoriteEntity } from '../../../core/user-favorite-manager'; import { PanelPreviewService } from '../../services/panel-preview.service'; @@ -113,7 +113,7 @@ export class FavoritesMetaCardComponent { const previewComponent = catalogueEntity.builders.entityBuilder.getPreviewableComponent(); // TODO: use 'endpoint' as constant - if (this.favorite.entityType === 'endpoint') { + if (this.favorite.entityType === ENDPOINT_TYPE) { const entity$ = this.store.select(endpointsEntityRequestDataSelector(this.favorite.endpointId)); this.panelPreviewService.show(previewComponent, { title: this.favorite.metadata.name, diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts index dab1792c71..9dba74b1d5 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts @@ -35,7 +35,6 @@ export class MarkdownPreviewComponent implements PreviewableComponent { ) { } setProps(props: { [key: string]: any }) { - console.log('props', props); this.setDocumentUrl = props.documentUrl; } diff --git a/src/frontend/packages/core/src/shared/services/panel-preview.service.ts b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts index dda1d3e374..04b42e4010 100644 --- a/src/frontend/packages/core/src/shared/services/panel-preview.service.ts +++ b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts @@ -1,6 +1,7 @@ import { ComponentFactory, ComponentFactoryResolver, ComponentRef, Injectable, ViewContainerRef } from '@angular/core'; +import { Router } from '@angular/router'; import { asapScheduler, BehaviorSubject, Observable, Subject } from 'rxjs'; -import { observeOn, publishReplay, refCount } from 'rxjs/operators'; +import { filter, observeOn, publishReplay, refCount, tap } from 'rxjs/operators'; @Injectable() export class PanelPreviewService { @@ -9,9 +10,14 @@ export class PanelPreviewService { private container: ViewContainerRef; - constructor(private componentFactoryResolver: ComponentFactoryResolver) { + constructor( + private componentFactoryResolver: ComponentFactoryResolver, + private router: Router, + ) { this.openedSubject = new BehaviorSubject(false); this.opened$ = this.observeSubject(this.openedSubject); + + this.setupRouterListener(); } public setContainer(container: ViewContainerRef) { @@ -57,6 +63,13 @@ export class PanelPreviewService { this.openedSubject.next(false); } + private setupRouterListener() { + this.router.events.pipe( + filter(() => !!this.container), + tap((e) => this.hide())) + .subscribe(); + } + private observeSubject(subject: Subject) { return subject.asObservable().pipe( publishReplay(1), From c0cfd2da053d67c1e497b276950eee2478d3b998 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 3 Dec 2019 14:20:47 +0000 Subject: [PATCH 180/648] Tweaks following review with RC --- docs/customizing.md | 20 +++++++++++++++- examples/custom-src/frontend/sass/custom.scss | 6 +++++ .../custom-src/frontend/sass/custom/acme.scss | 15 ------------ .../packages/core/misc/custom/custom.scss | 1 + .../packages/core/sass/dark-theme.scss | 14 ----------- src/frontend/packages/core/sass/theme.scss | 16 ++++++++++++- .../packages/core/src/core/theme.service.ts | 24 +++++++++---------- src/frontend/packages/core/src/styles.scss | 8 +++++++ 8 files changed, 61 insertions(+), 43 deletions(-) delete mode 100644 src/frontend/packages/core/sass/dark-theme.scss diff --git a/docs/customizing.md b/docs/customizing.md index 2171212e3c..c167a77516 100644 --- a/docs/customizing.md +++ b/docs/customizing.md @@ -50,7 +50,7 @@ In this file you can set any or all of the following variables: |---|---| |$stratos-theme|The main theme to use for Stratos| |$stratos-nav-theme|Theme to use for the side navigation panel| -$stratos-status-theme|Theme to use for displaying status in Stratos| +|$stratos-status-theme|Theme to use for displaying status in Stratos| Note that you do not have to specify all of these - defaults will be used if they are not set. @@ -72,6 +72,24 @@ $suse-app-theme: mat-light-theme($suse-app-primary, $suse-app-primary, $suse-the $stratos-theme: $suse-app-theme; ``` +#### Creating or disabling the Dark theme + +You can also change the Dark theme, if you wish, by defining the following variables: + +|Variable|Purpose| +|---|---| +|$stratos-dark-theme|The dark theme to use for Stratos| +|$stratos-dark-nav-theme|Dark theme to use for the side navigation panel| +|$stratos-dark-status-theme|Dark theme to use for displaying status in Stratos| + +Note that minimally you must supply `stratos-dark-theme` to create a dark theme. + +By default a dark theme is assumed to be available and the default will be used if not overridden. You can disable dark theme support in the UI by setting the following variable in your `custom.scss`: + +``` +$stratos-dark-theme-supported: false; +``` + ### Changing Styles We don't generally recommend modifying styles, since from version to version of Stratos, we may change the styles used slightly which can mean any modifications you made will need updating. Should you wish to do so, you can modify these in the same `custom.scss` file that is used for theming. diff --git a/examples/custom-src/frontend/sass/custom.scss b/examples/custom-src/frontend/sass/custom.scss index 0087198f97..e3eb84c38a 100644 --- a/examples/custom-src/frontend/sass/custom.scss +++ b/examples/custom-src/frontend/sass/custom.scss @@ -5,8 +5,14 @@ $acme-primary: (50: #e8eaf6, 100: #c5cbe9, 200: #9fa8da, 300: #7985cb, 400: #5c6bc0, 500: #3f51b5, 600: #394aae, 700: #3140a5, 800: #29379d, 900: #1b278d, A100: #c6cbff, A200: #939dff, A400: #606eff, A700: #4757ff, contrast: ( 50: #000000, 100: #000000, 200: #000000, 300: #000000, 400: #ffffff, 500: #ffffff, 600: #ffffff, 700: #ffffff, 800: #ffffff, 900: #ffffff, A100: #000000, A200: #000000, A400: #ffffff, A700: #ffffff, )); $mat-red: ( 50: #ffebee, 100: #ffcdd2, 200: #ef9a9a, 300: #e57373, 400: #ef5350, 500: #f44336, 600: #e53935, 700: #d32f2f, 800: #c62828, 900: #b71c1c, A100: #ff8a80, A200: #ff5252, A400: #ff1744, A700: #d50000, contrast: ( 50: $black-87-opacity, 100: $black-87-opacity, 200: $black-87-opacity, 300: $black-87-opacity, 400: $black-87-opacity, 500: white, 600: white, 700: white, 800: $white-87-opacity, 900: $white-87-opacity, A100: $black-87-opacity, A200: white, A400: white, A700: white, )); +// Common $acme-theme-primary: mat-palette($acme-primary); $acme-theme-warn: mat-palette($mat-red); + +// Dark Theme +$stratos-dark-theme: mat-dark-theme($acme-theme-primary, $acme-theme-primary, $acme-theme-warn); + +// Default Theme $stratos-theme: mat-light-theme($acme-theme-primary, $acme-theme-primary, $acme-theme-warn); @import 'custom/acme'; diff --git a/examples/custom-src/frontend/sass/custom/acme.scss b/examples/custom-src/frontend/sass/custom/acme.scss index f68980918b..75794ebb6e 100644 --- a/examples/custom-src/frontend/sass/custom/acme.scss +++ b/examples/custom-src/frontend/sass/custom/acme.scss @@ -68,19 +68,4 @@ body.stratos { .stratos-title > .stratos-title__header { display: inline-block; } - - .dark-theme { - $dark-primary: $acme-theme-primary; - $dark-accent: $acme-theme-primary; - $dark-warn: mat-palette($mat-red); - $dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn); - - $stratos-theme: $dark-theme; - $stratos-nav-theme: null !default; - $stratos-status-theme: null !default; - - @include angular-material-theme($dark-theme); - @include app-theme($stratos-theme, $stratos-nav-theme, $stratos-status-theme); - - } } diff --git a/src/frontend/packages/core/misc/custom/custom.scss b/src/frontend/packages/core/misc/custom/custom.scss index eb5d596b7e..9fa63cd29c 100644 --- a/src/frontend/packages/core/misc/custom/custom.scss +++ b/src/frontend/packages/core/misc/custom/custom.scss @@ -4,3 +4,4 @@ // The customization build step will replace this file with the custom one if provided +$stratos-dark-theme-supported: false; diff --git a/src/frontend/packages/core/sass/dark-theme.scss b/src/frontend/packages/core/sass/dark-theme.scss deleted file mode 100644 index ee4ec386ba..0000000000 --- a/src/frontend/packages/core/sass/dark-theme.scss +++ /dev/null @@ -1,14 +0,0 @@ -.dark-theme { - $dark-primary: mat-palette($mat-blue); - $dark-accent: mat-palette($mat-amber, A400, A100, A700); - $dark-warn: mat-palette($mat-red); - $dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn); - - $stratos-theme: $dark-theme !default; - $stratos-nav-theme: null !default; - $stratos-status-theme: null !default; - - @include angular-material-theme($dark-theme); - @include app-theme($stratos-theme, $stratos-nav-theme, $stratos-status-theme); - -} diff --git a/src/frontend/packages/core/sass/theme.scss b/src/frontend/packages/core/sass/theme.scss index a2daf93033..ed6428d931 100644 --- a/src/frontend/packages/core/sass/theme.scss +++ b/src/frontend/packages/core/sass/theme.scss @@ -4,9 +4,22 @@ @import './mat-colors'; // Custom theme support -@import './dark-theme'; @import './custom'; +.dark-theme { + // Dark Theme defaults + $dark-primary: mat-palette($mat-blue); + $dark-accent: mat-palette($mat-amber, A400, A100, A700); + $dark-warn: mat-palette($mat-red); + $dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn); + + $stratos-dark-theme: $dark-theme !default; + $stratos-dark-nav-theme: null !default; + $stratos-dark-status-theme: null !default; + + @include angular-material-theme($stratos-dark-theme); + @include app-theme($stratos-dark-theme, $stratos-dark-nav-theme, $stratos-dark-status-theme); +} .default { // Themes palettes and colors @@ -24,6 +37,7 @@ @include app-theme($stratos-theme, $stratos-nav-theme, $stratos-status-theme); } +$stratos-dark-theme-supported: true !default; // Create the theme @include mat-core; diff --git a/src/frontend/packages/core/src/core/theme.service.ts b/src/frontend/packages/core/src/core/theme.service.ts index 169e324e52..43b33ac44f 100644 --- a/src/frontend/packages/core/src/core/theme.service.ts +++ b/src/frontend/packages/core/src/core/theme.service.ts @@ -51,9 +51,9 @@ export class ThemeService { // If at some point in the future we need to adjust theme on OS change at run time we should look into this. Unfortunately was unable // to test (only way I could change scheme from light to dark was in FF and about:config `ui.systemUsesDarkTheme` integer 1) - // window.matchMedia('(prefers-color-scheme: dark)').addListener(e => e.matches && this.updateFollowingOsThemeChange()); - // window.matchMedia('(prefers-color-scheme: light)').addListener(e => e.matches && this.updateFollowingOsThemeChange()); - // window.matchMedia('(prefers-color-scheme: no-preference)').addListener(e => e.matches && this.updateFollowingOsThemeChange()); + window.matchMedia('(prefers-color-scheme: dark)').addListener(e => e.matches && this.updateFollowingOsThemeChange()); + window.matchMedia('(prefers-color-scheme: light)').addListener(e => e.matches && this.updateFollowingOsThemeChange()); + window.matchMedia('(prefers-color-scheme: no-preference)').addListener(e => e.matches && this.updateFollowingOsThemeChange()); } } @@ -122,13 +122,13 @@ export class ThemeService { /** * Update theme given changes in OS theme settings */ - // private updateFollowingOsThemeChange() { - // this.osThemeInfo.isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; - // this.osThemeInfo.isLightMode = window.matchMedia('(prefers-color-scheme: light)').matches; - // this.osThemeInfo.isNotSpecified = window.matchMedia('(prefers-color-scheme: no-preference)').matches; - - // this.store.select(selectDashboardState).pipe( - // first() - // ).subscribe(dashboardState => dashboardState.themeKey === osTheme.key && this.setTheme(osTheme.key)); - // } + private updateFollowingOsThemeChange() { + this.osThemeInfo.isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; + this.osThemeInfo.isLightMode = window.matchMedia('(prefers-color-scheme: light)').matches; + this.osThemeInfo.isNotSpecified = window.matchMedia('(prefers-color-scheme: no-preference)').matches; + + this.store.select(selectDashboardState).pipe( + first() + ).subscribe(dashboardState => dashboardState.themeKey === osTheme.key && this.setTheme(osTheme.key)); + } } diff --git a/src/frontend/packages/core/src/styles.scss b/src/frontend/packages/core/src/styles.scss index 1fe35bd298..f6e062111e 100644 --- a/src/frontend/packages/core/src/styles.scss +++ b/src/frontend/packages/core/src/styles.scss @@ -65,3 +65,11 @@ button.mat-simple-snackbar-action { // flex: 1; // flex-direction: column; // } + + +// Add selector so that the UI can detect if a dark theme is available +@if $stratos-dark-theme-supported { + .dark-theme-supported { + margin: 0; + } +} From 8308474b8f8364f07bf20d23184074bb117a2c85 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 3 Dec 2019 16:31:24 +0000 Subject: [PATCH 181/648] Final tweaks following review - wired in dark-theme-supported to theme options - removed test line from custom.scss --- .../packages/core/misc/custom/custom.scss | 2 - .../packages/core/src/core/style.service.ts | 38 +++++++++++++++ .../packages/core/src/core/theme.service.ts | 47 +++++++++++++------ .../profile-info/profile-info.component.html | 2 +- .../profile-info/profile-info.component.ts | 3 ++ 5 files changed, 74 insertions(+), 18 deletions(-) create mode 100644 src/frontend/packages/core/src/core/style.service.ts diff --git a/src/frontend/packages/core/misc/custom/custom.scss b/src/frontend/packages/core/misc/custom/custom.scss index 9fa63cd29c..f6d83b743c 100644 --- a/src/frontend/packages/core/misc/custom/custom.scss +++ b/src/frontend/packages/core/misc/custom/custom.scss @@ -3,5 +3,3 @@ // This file is in the .gitignore - changes will not be flagged // The customization build step will replace this file with the custom one if provided - -$stratos-dark-theme-supported: false; diff --git a/src/frontend/packages/core/src/core/style.service.ts b/src/frontend/packages/core/src/core/style.service.ts new file mode 100644 index 0000000000..f832f06c0d --- /dev/null +++ b/src/frontend/packages/core/src/core/style.service.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class StyleService { + + private rules: string[] = []; + constructor() { + this.rules = this.getAllSelectors(); + } + + hasSelector = (selector) => { + return !!this.rules.find(ruleSelector => ruleSelector === selector); + } + + private getAllSelectors = (): string[] => { + const ret = []; + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < document.styleSheets.length; i++) { + const styleSheet = document.styleSheets[i]; + if (!(styleSheet instanceof CSSStyleSheet)) { + continue; + } + const rules = styleSheet.rules || styleSheet.cssRules; + // tslint:disable-next-line:prefer-for-of + for (let y = 0; y < rules.length; y++) { + const rule = rules[y]; + if (!(rule instanceof CSSStyleRule)) { + continue; + } + if (typeof rule.selectorText === 'string') { ret.push(rule.selectorText); } + } + } + return ret; + } + +} diff --git a/src/frontend/packages/core/src/core/theme.service.ts b/src/frontend/packages/core/src/core/theme.service.ts index 43b33ac44f..f5ee16ba18 100644 --- a/src/frontend/packages/core/src/core/theme.service.ts +++ b/src/frontend/packages/core/src/core/theme.service.ts @@ -7,6 +7,7 @@ import { first, map } from 'rxjs/operators'; import { SetThemeAction } from '../../../store/src/actions/dashboard-actions'; import { DashboardOnlyAppState } from '../../../store/src/app-state'; import { selectDashboardState } from '../../../store/src/selectors/dashboard.selectors'; +import { StyleService } from './style.service'; export interface StratosTheme { key: string; @@ -41,20 +42,13 @@ export class ThemeService { isLightMode: window.matchMedia('(prefers-color-scheme: light)').matches, isNotSpecified: window.matchMedia('(prefers-color-scheme: no-preference)').matches }; - private themes: StratosTheme[] = [lightTheme, darkTheme]; + private themes: StratosTheme[] = [lightTheme]; - constructor(private store: Store, private overlayContainer: OverlayContainer) { - this.osThemeInfo.supports = this.osThemeInfo.isDarkMode || this.osThemeInfo.isLightMode || this.osThemeInfo.isNotSpecified; - - if (this.osThemeInfo.supports) { - this.themes.push(osTheme); - - // If at some point in the future we need to adjust theme on OS change at run time we should look into this. Unfortunately was unable - // to test (only way I could change scheme from light to dark was in FF and about:config `ui.systemUsesDarkTheme` integer 1) - window.matchMedia('(prefers-color-scheme: dark)').addListener(e => e.matches && this.updateFollowingOsThemeChange()); - window.matchMedia('(prefers-color-scheme: light)').addListener(e => e.matches && this.updateFollowingOsThemeChange()); - window.matchMedia('(prefers-color-scheme: no-preference)').addListener(e => e.matches && this.updateFollowingOsThemeChange()); - } + constructor( + private store: Store, + private overlayContainer: OverlayContainer, + private styleService: StyleService) { + this.initialiseStratosThemeInfo(); } getThemes(): StratosTheme[] { @@ -80,11 +74,35 @@ export class ThemeService { this.getTheme().pipe(first()).subscribe(theme => this.setOverlay(theme)); } + private initialiseStratosThemeInfo() { + const hasDarkTheme = this.styleService.hasSelector('.dark-theme-supported'); + + if (hasDarkTheme) { + this.themes.push(darkTheme); + + this.initialiseOsThemeInfo(); + } + + } + + private initialiseOsThemeInfo() { + this.osThemeInfo.supports = this.osThemeInfo.isDarkMode || this.osThemeInfo.isLightMode || this.osThemeInfo.isNotSpecified; + + if (this.osThemeInfo.supports) { + this.themes.push(osTheme); + + // Watch for changes at run time + window.matchMedia('(prefers-color-scheme: dark)').addListener(e => e.matches && this.updateFollowingOsThemeChange()); + window.matchMedia('(prefers-color-scheme: light)').addListener(e => e.matches && this.updateFollowingOsThemeChange()); + window.matchMedia('(prefers-color-scheme: no-preference)').addListener(e => e.matches && this.updateFollowingOsThemeChange()); + } + } + /** * Find a theme in a safe way with fall backs */ private findTheme(themeKey: string): StratosTheme { - if (themeKey === osTheme.key) { + if (themeKey === osTheme.key && this.getThemes().find(theme => theme.key === osTheme.key)) { return this.getOsTheme() || lightTheme; } return this.getThemes().find(theme => theme.key === themeKey) || lightTheme; @@ -118,7 +136,6 @@ export class ThemeService { this.overlayContainer.getContainerElement().classList.add(newTheme.styleName); } - // See usages, might be needed in the future /** * Update theme given changes in OS theme settings */ diff --git a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html index 4580560673..7e807d5687 100644 --- a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html +++ b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html @@ -92,7 +92,7 @@

    User Profile

    polling may result in some pages showing out-of-date information.
    -
- diff --git a/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.scss b/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.scss index 859d78a163..3280ff302b 100644 --- a/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.scss +++ b/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.scss @@ -9,13 +9,23 @@ } &-info { display: flex; - mat-icon { + > mat-icon { font-size: 48px; height: 48px; margin-right: 8px; width: 48px; } } + &-unknown { + display: flex; + mat-icon { + margin-right: 8px; + } + &__detail { + margin-left: 32px; + opacity: .8; + } + } &-metadata { width: 100%; div { @@ -42,5 +52,73 @@ margin-top: 0; } } + &-detail { + padding-top: 12px; + } +} + +// Adapted from: http://cssdeck.com/labs/pure-css-tree-menu-framework +$hz-indent-top: 60px; +$hz-indent-left: 40px; +$hz-indent-width: 40px; +$tree-indicator-width: 3px; + +.metrics-tree { + display: flex; + flex: 1; + flex-direction: column; + + &__root { + height: 24px; + margin-left: $hz-indent-left; + width: 3px; + } +} + +.tree, +.tree ul { + list-style: none; + margin: 0 0 0 $hz-indent-left; + padding: 0; + position: relative; +} + +.tree ul { + margin-left: .5em; +} + +.tree::before, +.tree ul::before { + border-left: $tree-indicator-width solid; + bottom: 0; + content: ''; + display: block; + left: 0; + position: absolute; + top: 0; + width: 0; +} + +.tree li { + margin: 0; + padding: 0 $hz-indent-width; + position: relative; } +.tree li::before { + border-top: $tree-indicator-width solid; + content: ''; + display: block; + height: 0; + left: 0; + margin-top: -1px; + position: absolute; + top: $hz-indent-top; + width: $hz-indent-width; +} + +.tree li:last-child::before { + bottom: 0; + height: auto; + top: $hz-indent-top; +} diff --git a/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.spec.ts b/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.spec.ts index 34602f97ab..f0097b1e8e 100644 --- a/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.spec.ts +++ b/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.spec.ts @@ -2,17 +2,16 @@ import { CommonModule } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; +import { createBasicStoreModule } from '@stratos/store/testing'; import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../core/core.module'; import { SharedModule } from '../../../shared/shared.module'; import { MetricsService } from '../services/metrics-service'; import { MetricsComponent } from './metrics.component'; -// TODO: Fix after metrics has been sorted - STRAT-152 -xdescribe('MetricsComponent', () => { +describe('MetricsComponent', () => { let component: MetricsComponent; let fixture: ComponentFixture; diff --git a/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.theme.scss b/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.theme.scss new file mode 100644 index 0000000000..f89c2e350a --- /dev/null +++ b/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.theme.scss @@ -0,0 +1,21 @@ +@mixin metrics-component-theme($theme, $app-theme) { + $primary: map-get($theme, primary); + $background: map-get($theme, background); + $tree-background-color: mat-color($background, background); + $is-dark: map-get($theme, is-dark); + + // Last item uses background color to overwrisr the side bar to make it appear + // like its the last item in th tree + .tree li:last-child::before { + background-color: $tree-background-color; + } + + .metrics-tree__root { + // Should match .mat-drawer-container + @if $is-dark == true { + background-color: #ffffff; + } @else { + background-color: mat-contrast($primary, 300); + } + } +} diff --git a/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.ts b/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.ts index 348b5ebf48..090a536487 100644 --- a/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.ts +++ b/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.ts @@ -4,12 +4,16 @@ import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { filter, first, map } from 'rxjs/operators'; -import { MetricsAPIAction, MetricsAPITargets } from '../../../../../store/src/actions/metrics-api.actions'; +import { + MetricsAPIAction, + MetricsAPITargets, + MetricsStratosAction, +} from '../../../../../store/src/actions/metrics-api.actions'; import { AppState } from '../../../../../store/src/app-state'; -import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog.service'; import { getIdFromRoute } from '../../../core/utils.service'; import { IHeaderBreadcrumb } from '../../../shared/components/page-header/page-header.types'; import { EndpointIcon } from '../../endpoints/endpoint-helpers'; +import { mapMetricsData, MetricsEndpointInfo } from '../metrics.helpers'; import { MetricsEndpointProvider, MetricsService } from '../services/metrics-service'; interface EndpointMetadata { @@ -40,57 +44,48 @@ interface PrometheusJobs { styleUrls: ['./metrics.component.scss'] }) export class MetricsComponent { - - public metricsEndpoint$: Observable; + public metricsEndpoint$: Observable; + public metricsInfo$: Observable; public breadcrumbs$: Observable; public jobDetails$: Observable; + // Was there an error retrieving data from the Prometheus server? + public error = false; + constructor( private activatedRoute: ActivatedRoute, private metricsService: MetricsService, private store: Store, - ) { const metricsGuid = getIdFromRoute(this.activatedRoute, 'metricsId'); - const metricsAction = new MetricsAPIAction(metricsGuid, 'targets'); - this.store.dispatch(metricsAction); + this.store.dispatch(new MetricsAPIAction(metricsGuid, 'targets')); + this.store.dispatch(new MetricsStratosAction(metricsGuid)); + // Raw endpoint data for this metrics endpoint this.metricsEndpoint$ = this.metricsService.metricsEndpoints$.pipe( map((ep) => ep.find((item) => item.provider.guid === metricsGuid)), - map((ep) => { - const metadata = {}; - ep.endpoints.forEach(endpoint => { - const catalogEndpoint = entityCatalog.getEndpoint(endpoint.cnsi_type, endpoint.sub_type); - metadata[endpoint.guid] = { - type: catalogEndpoint.definition.type, - icon: catalogEndpoint.definition.icon - }; - }); - return { - entity: ep, - metadata - }; + ); + + // Processed endpoint data + this.metricsInfo$ = this.metricsEndpoint$.pipe(map((ep) => { + if (ep.provider && ep.provider.metadata && ep.provider.metadata && ep.provider.metadata.metrics_stratos + && (ep.provider.metadata.metrics_stratos as any).error) { + this.error = true; } - )); + return mapMetricsData(ep); + })); + // Breadcrumbs this.breadcrumbs$ = this.metricsEndpoint$.pipe( - map(() => ([ - { - breadcrumbs: [ - { - value: 'Endpoints', - routerLink: `/endpoints` - } - ] - } - ])), + map(() => ([ { breadcrumbs: [ { value: 'Endpoints', routerLink: `/endpoints` } ] } ])), first() ); + // Job details obtained from the Prometheus server this.jobDetails$ = this.metricsEndpoint$.pipe( - filter(mi => !!mi && !!mi.entity.provider && !!mi.entity.provider.metadata && !!mi.entity.provider.metadata.metrics_targets), - map(mi => mi.entity.provider.metadata.metrics_targets), + filter(mi => !!mi && !!mi.provider && !!mi.provider.metadata && !!mi.provider.metadata.metrics_targets), + map(mi => mi.provider.metadata.metrics_targets), map((targetsData: MetricsAPITargets) => targetsData.activeTargets.reduce((mapped, t) => { if (t.labels && t.labels.job) { mapped[t.labels.job] = t; diff --git a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html index 0eca5d5d7d..c08c7704c5 100644 --- a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html +++ b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.html @@ -86,7 +86,7 @@

User Profile

[ngClass]="{'user-profile__option-warning': pollingEnabled === 'false'}"> - - + \ No newline at end of file diff --git a/src/frontend/packages/store/src/selectors/endpoint.selectors.ts b/src/frontend/packages/store/src/selectors/endpoint.selectors.ts index 0400350b59..cfcd00d4c9 100644 --- a/src/frontend/packages/store/src/selectors/endpoint.selectors.ts +++ b/src/frontend/packages/store/src/selectors/endpoint.selectors.ts @@ -61,10 +61,6 @@ export const endpointsCFEntitiesSelector = createSelector( cfEndpointEntitiesSelector ); -// const log = (label) => { -// return (val) => console.log(label, val); -// }; - // TODO: Move this #3769 export const endpointsCfEntitiesConnectedSelector = connectedEndpointsOfTypesSelector('cf'); diff --git a/src/frontend/packages/store/src/types/request.types.ts b/src/frontend/packages/store/src/types/request.types.ts index 1e14568f76..e1b855210c 100644 --- a/src/frontend/packages/store/src/types/request.types.ts +++ b/src/frontend/packages/store/src/types/request.types.ts @@ -1,9 +1,9 @@ import { HttpRequest } from '@angular/common/http'; import { Action } from '@ngrx/store'; +import { ApiActionTypes, RequestTypes } from '../actions/request.actions'; import { BasePipelineRequestAction } from '../entity-catalog/action-orchestrator/action-orchestrator'; import { EntityCatalogEntityConfig } from '../entity-catalog/entity-catalog.types'; -import { ApiActionTypes, RequestTypes } from '../actions/request.actions'; import { EntitySchema } from '../helpers/entity-schema'; import { ApiRequestTypes } from '../reducers/api-request-reducer/request-helpers'; import { NormalizedResponse } from './api.types'; From ac7753f8d2e001464a1bd05934fd3aa057ff55d1 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 16 Jun 2020 11:44:44 +0100 Subject: [PATCH 536/648] Fix compile --- .../workloads/release/tabs/helm-release-helper.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom-src/frontend/app/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts b/custom-src/frontend/app/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts index f7a8ba1229..7ff04b0f07 100644 --- a/custom-src/frontend/app/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts @@ -3,7 +3,7 @@ import { Observable } from 'rxjs'; import { filter, map } from 'rxjs/operators'; import { kubeEntityCatalog } from '../../../kubernetes-entity-catalog'; -import { ContainerStateCollection, KubernetesPod, KubernetesPod } from '../../../store/kube.types'; +import { ContainerStateCollection, KubernetesPod } from '../../../store/kube.types'; import { getHelmReleaseDetailsFromGuid } from '../../store/workloads-entity-factory'; import { HelmRelease, From d835710da94ecfacf20eeb3692f0fffb7f36ab28 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 16 Jun 2020 14:45:03 +0100 Subject: [PATCH 537/648] Minor changes following review --- .../workloads/release/tabs/helm-release-helper.service.ts | 1 - .../shared/components/ring-chart/ring-chart.component.ts | 8 ++------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/custom-src/frontend/app/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts b/custom-src/frontend/app/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts index 7ff04b0f07..a7dd8aaa99 100644 --- a/custom-src/frontend/app/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts @@ -133,7 +133,6 @@ export class HelmReleaseHelperService { } private isContainerReady(state: ContainerStateCollection = {}): Boolean { - console.log(state); if (state.running) { return true; } else if (!!state.waiting) { diff --git a/src/frontend/packages/core/src/shared/components/ring-chart/ring-chart.component.ts b/src/frontend/packages/core/src/shared/components/ring-chart/ring-chart.component.ts index 681984c9d6..be9557639d 100644 --- a/src/frontend/packages/core/src/shared/components/ring-chart/ring-chart.component.ts +++ b/src/frontend/packages/core/src/shared/components/ring-chart/ring-chart.component.ts @@ -1,5 +1,5 @@ -import { Component, Input, OnChanges, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; -import { AdvancedLegendComponent, ColorHelper } from '@swimlane/ngx-charts'; +import { Component, Input, OnChanges, OnInit, ViewEncapsulation } from '@angular/core'; +import { ColorHelper } from '@swimlane/ngx-charts'; @Component({ selector: 'app-ring-chart', @@ -24,10 +24,6 @@ export class RingChartComponent implements OnInit, OnChanges { @Input() nameFormatting: (value: string) => any = label => label; @Input() percentageFormatting: (value: number) => any = percentage => percentage; - @ViewChild(AdvancedLegendComponent) lineSeriesComponent: AdvancedLegendComponent; - - constructor() { } - ngOnInit() { if (!this.data) { this.data = []; From 2da8d284247cdb758339ca9eb62ce457f69480de Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 16 Jun 2020 16:21:31 +0100 Subject: [PATCH 538/648] Workload: Add warning if manifest parse contains errors --- .../helm-release-tab-base.component.ts | 15 +++++ .../tabs/helm-release-helper.service.ts | 1 + .../store/workload-action-builders.ts | 15 ++++- .../store/workloads-entity-generator.ts | 8 ++- .../workloads/store/workloads.actions.ts | 19 ++++++- .../workloads/store/workloads.reducers.ts | 57 ++++++++++++++++++- .../kubernetes/workloads/workload.types.ts | 1 + .../plugins/kubernetes/get_release.go | 2 + .../plugins/kubernetes/helm/release.go | 13 +++-- 9 files changed, 120 insertions(+), 11 deletions(-) diff --git a/custom-src/frontend/app/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts b/custom-src/frontend/app/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts index 3c62493c90..66a1ff7641 100644 --- a/custom-src/frontend/app/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts +++ b/custom-src/frontend/app/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts @@ -5,6 +5,7 @@ import { Observable, Subject, Subscription } from 'rxjs'; import makeWebSocketObservable, { GetWebSocketResponses } from 'rxjs-websockets'; import { catchError, map, share, switchMap } from 'rxjs/operators'; +import { HideSnackBar, ShowSnackBar } from '../../../../../../../store/src/actions/snackBar.actions'; import { AppState } from '../../../../../../../store/src/app-state'; import { entityCatalog } from '../../../../../../../store/src/entity-catalog/entity-catalog'; import { EntityRequestAction, WrapperRequestActionSuccess } from '../../../../../../../store/src/types/request.types'; @@ -142,6 +143,19 @@ export class HelmReleaseTabBaseComponent implements OnDestroy { resources.endpointId, ); this.addResource(releaseResourceAction, resources); + } else if (messageObj.kind === 'ManifestErrors') { + const manifestErrors = messageObj.data; + workloadsEntityCatalog.release.api.setManifestError( + this.helmReleaseHelper.endpointGuid, + this.helmReleaseHelper.namespace, + this.helmReleaseHelper.releaseTitle, + manifestErrors + ) + if (manifestErrors) { + this.store.dispatch( + new ShowSnackBar('Errors were found whilst loading this workload. Not all resources may be shown', 'Dismiss') + ); + } } } }); @@ -189,5 +203,6 @@ export class HelmReleaseTabBaseComponent implements OnDestroy { ngOnDestroy() { this.sub.unsubscribe(); + this.store.dispatch(new HideSnackBar()); } } diff --git a/custom-src/frontend/app/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts b/custom-src/frontend/app/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts index bcdef4478b..90a4deff6d 100644 --- a/custom-src/frontend/app/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts @@ -44,6 +44,7 @@ export class HelmReleaseHelperService { this.release$ = entityService.waitForEntity$.pipe( map((item) => item.entity), + filter(item => !!item.chart), map((item: HelmRelease) => { if (!item.chart.metadata.icon) { const copy = JSON.parse(JSON.stringify(item)); diff --git a/custom-src/frontend/app/custom/kubernetes/workloads/store/workload-action-builders.ts b/custom-src/frontend/app/custom/kubernetes/workloads/store/workload-action-builders.ts index eb7591f9c5..944b7ab893 100644 --- a/custom-src/frontend/app/custom/kubernetes/workloads/store/workload-action-builders.ts +++ b/custom-src/frontend/app/custom/kubernetes/workloads/store/workload-action-builders.ts @@ -1,7 +1,13 @@ import { OrchestratedActionBuilders, } from '../../../../../../store/src/entity-catalog/action-orchestrator/action-orchestrator'; -import { GetHelmRelease, GetHelmReleaseGraph, GetHelmReleaseResource, GetHelmReleases } from './workloads.actions'; +import { + GetHelmRelease, + GetHelmReleaseGraph, + GetHelmReleaseResource, + GetHelmReleases, + UpdateHelmReleaseManifestError, +} from './workloads.actions'; export interface WorkloadReleaseBuilders extends OrchestratedActionBuilders { get: ( @@ -9,6 +15,11 @@ export interface WorkloadReleaseBuilders extends OrchestratedActionBuilders { endpointGuid: string, extraArgs: { namespace: string } ) => GetHelmRelease; + setManifestError: ( + endpointGuid: string, + namespace: string, + releaseTitle: string, + manifestError: boolean) => UpdateHelmReleaseManifestError; getMultiple: () => GetHelmReleases; } @@ -16,6 +27,8 @@ export const workloadReleaseBuilders: WorkloadReleaseBuilders = { get: (title: string, endpointGuid: string, { namespace }: { namespace: string }) => { return new GetHelmRelease(endpointGuid, namespace, title); }, + setManifestError: (endpointGuid: string, namespace: string, releaseTitle: string, manifestError: boolean) => + new UpdateHelmReleaseManifestError(endpointGuid, namespace, releaseTitle, manifestError), getMultiple: () => new GetHelmReleases() } diff --git a/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads-entity-generator.ts b/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads-entity-generator.ts index f4a0939afa..652c4287ad 100644 --- a/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads-entity-generator.ts +++ b/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads-entity-generator.ts @@ -16,6 +16,7 @@ import { workloadResourceBuilders, } from './workload-action-builders'; import { helmReleaseEntityKey, helmReleaseGraphEntityType, helmReleaseResourceEntityType } from './workloads-entity-factory'; +import { helmReleaseEntityReducer } from './workloads.reducers'; export function generateWorkloadsEntities(endpointDefinition: StratosEndpointExtensionDefinition): StratosBaseCatalogEntity[] { @@ -30,12 +31,15 @@ function generateReleaseEntity(endpointDefinition: StratosEndpointExtensionDefin const definition = { type: helmReleaseEntityKey, schema: kubernetesEntityFactory(helmReleaseEntityKey), - endpoint: endpointDefinition + endpoint: endpointDefinition, }; workloadsEntityCatalog.release = new StratosCatalogEntity( definition, { - actionBuilders: workloadReleaseBuilders + actionBuilders: workloadReleaseBuilders, + dataReducers: [ + helmReleaseEntityReducer() + ], } ); return workloadsEntityCatalog.release; diff --git a/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads.actions.ts b/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads.actions.ts index 319ef74b8b..54996403a7 100644 --- a/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads.actions.ts +++ b/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads.actions.ts @@ -1,3 +1,4 @@ +import { Action } from '@ngrx/store'; import { EntityRequestAction } from 'frontend/packages/store/src/types/request.types'; import { PaginatedAction } from '../../../../../../store/src/types/pagination.types'; @@ -26,6 +27,9 @@ export const GET_HELM_RELEASE = '[Helm] Get Release Status'; export const GET_HELM_RELEASE_SUCCESS = '[Helm] Get Release Status Success'; export const GET_HELM_RELEASE_FAILURE = '[Helm] Get Release Status Failure'; + +export const UPDATE_HELM_RELEASE_MANIFEST_ERROR = '[Helm] Update Release Error'; + export const GET_HELM_RELEASE_PODS = '[Helm] Get Release Pods'; export const GET_HELM_RELEASE_PODS_SUCCESS = '[Helm] Get Release Pods Success'; export const GET_HELM_RELEASE_PODS_FAILURE = '[Helm] Get Release Pods Failure'; @@ -66,7 +70,6 @@ export class GetHelmReleases implements MonocularPaginationAction { }; } - export class GetHelmRelease implements HelmReleaseSingleEntity { guid: string; constructor( @@ -87,6 +90,20 @@ export class GetHelmRelease implements HelmReleaseSingleEntity { ]; } +export class UpdateHelmReleaseManifestError implements Action { + guid: string; + constructor( + public endpointGuid: string, + public namespace: string, + public releaseTitle: string, + public manifestError: boolean + ) { + this.guid = getHelmReleaseId(endpointGuid, namespace, releaseTitle); + } + type = UPDATE_HELM_RELEASE_MANIFEST_ERROR; +} + + export class GetHelmReleaseGraph implements HelmReleaseSingleEntity { guid: string; constructor( diff --git a/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads.reducers.ts b/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads.reducers.ts index 1d6797ee74..2d26f38b03 100644 --- a/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads.reducers.ts +++ b/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads.reducers.ts @@ -1,12 +1,65 @@ -import { UPDATE_HELM_RELEASE } from './workloads.actions'; +import { Action } from '@ngrx/store'; + +import { IRequestEntityTypeState } from '../../../../../../store/src/app-state'; +import { HelmRelease } from '../workload.types'; +import { + UPDATE_HELM_RELEASE, + UPDATE_HELM_RELEASE_MANIFEST_ERROR, + UpdateHelmReleaseManifestError, +} from './workloads.actions'; const defaultState = {}; -export function helmReleaseReducer(state: any = defaultState, action) { +export function helmReleaseReducer(state = defaultState, action) { switch (action.type) { case UPDATE_HELM_RELEASE: return { ...state, }; + } } + +const createDefaultHelmRelease = ( + endpointGuid: string, + guid: string, + name: string, + namespace: string +): HelmRelease => ({ + chart: null, + config: null, + endpointId: endpointGuid, + firstDeployed: null, + guid, + info: null, + lastDeployed: null, + manifestError: false, + name, + namespace, + status: null, + version: null +}) +export function helmReleaseEntityReducer() { + return (state: IRequestEntityTypeState, action: Action) => { + switch (action.type) { + case UPDATE_HELM_RELEASE_MANIFEST_ERROR: + const updateErrorAction = action as UpdateHelmReleaseManifestError; + const release: HelmRelease = state[updateErrorAction.guid] || createDefaultHelmRelease( + updateErrorAction.endpointGuid, + updateErrorAction.guid, + updateErrorAction.releaseTitle, + updateErrorAction.namespace + ); + if (release.manifestError !== updateErrorAction.manifestError) { + return { + ...state, + [updateErrorAction.guid]: { + ...[updateErrorAction.guid], + manifestError: updateErrorAction.manifestError + } + } + } + } + return state; + }; +} diff --git a/custom-src/frontend/app/custom/kubernetes/workloads/workload.types.ts b/custom-src/frontend/app/custom/kubernetes/workloads/workload.types.ts index f91220bad4..814e166c5a 100644 --- a/custom-src/frontend/app/custom/kubernetes/workloads/workload.types.ts +++ b/custom-src/frontend/app/custom/kubernetes/workloads/workload.types.ts @@ -24,6 +24,7 @@ export interface HelmRelease { icon?: string; }; }; + manifestError: boolean; } export interface HelmReleaseEntity { diff --git a/src/jetstream/plugins/kubernetes/get_release.go b/src/jetstream/plugins/kubernetes/get_release.go index bd09da963d..d072870557 100644 --- a/src/jetstream/plugins/kubernetes/get_release.go +++ b/src/jetstream/plugins/kubernetes/get_release.go @@ -142,6 +142,8 @@ func (c *KubernetesSpecification) GetReleaseStatus(ec echo.Context) error { graph.ParseManifest(rel) sendResource(ws, "Graph", graph) + sendResource(ws, "ManifestErrors", rel.ManifestErrors) + stopchan := make(chan bool) go readLoop(ws, stopchan) diff --git a/src/jetstream/plugins/kubernetes/helm/release.go b/src/jetstream/plugins/kubernetes/helm/release.go index 9c15554514..f8fd9c3131 100644 --- a/src/jetstream/plugins/kubernetes/helm/release.go +++ b/src/jetstream/plugins/kubernetes/helm/release.go @@ -32,11 +32,12 @@ var resourcesWithoutStatus = map[string]bool{ // HelmRelease represents a Helm Release deployed via Helm type HelmRelease struct { *release.Release - Endpoint string `json:"-"` - User string `json:"-"` - Resources map[string]KubeResource `json:"resources"` - Jobs []KubeResourceJob `json:"-"` - PodJobs map[string]KubeResourceJob `json:"-"` + Endpoint string `json:"-"` + User string `json:"-"` + Resources map[string]KubeResource `json:"resources"` + Jobs []KubeResourceJob `json:"-"` + PodJobs map[string]KubeResourceJob `json:"-"` + ManifestErrors bool `json:"-"` } // KubeResource is a simple struct to pull out core common metadata for a Kube resource @@ -69,6 +70,7 @@ func NewHelmRelease(info *release.Release, endpoint, user string) *HelmRelease { // Parse the release manifest from the Helm release func (r *HelmRelease) parseManifest() { + r.ManifestErrors = false reader := bytes.NewReader([]byte(r.Manifest)) buffer := bufio.NewReader(reader) var bufr strings.Builder @@ -81,6 +83,7 @@ func (r *HelmRelease) parseManifest() { obj, _, err := decode([]byte(bufr.String()), nil, nil) if err != nil { log.Error(fmt.Sprintf("Helm Manifest Parser: Error while decoding YAML object. Err was: %s", err)) + r.ManifestErrors = true } else { r.processResource(obj) } From ea15508a259ebfeefc3f838c8e267d81c3b040ef Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 16 Jun 2020 16:48:40 +0100 Subject: [PATCH 539/648] Fix build --- .../custom/kubernetes/workloads/store/workloads.reducers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads.reducers.ts b/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads.reducers.ts index 2d26f38b03..532f1caeb5 100644 --- a/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads.reducers.ts +++ b/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads.reducers.ts @@ -40,7 +40,7 @@ const createDefaultHelmRelease = ( version: null }) export function helmReleaseEntityReducer() { - return (state: IRequestEntityTypeState, action: Action) => { + return (state: IRequestEntityTypeState, action: Action): IRequestEntityTypeState => { switch (action.type) { case UPDATE_HELM_RELEASE_MANIFEST_ERROR: const updateErrorAction = action as UpdateHelmReleaseManifestError; @@ -54,7 +54,7 @@ export function helmReleaseEntityReducer() { return { ...state, [updateErrorAction.guid]: { - ...[updateErrorAction.guid], + ...state[updateErrorAction.guid], manifestError: updateErrorAction.manifestError } } From 1001e258dbc989f77afef492ed2ca70a22cdce87 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Wed, 17 Jun 2020 12:25:34 +0100 Subject: [PATCH 540/648] Remove helm manifest error from store --- .../helm-release-tab-base.component.ts | 11 +--- .../tabs/helm-release-helper.service.ts | 1 - .../store/workload-action-builders.ts | 15 +----- .../store/workloads-entity-generator.ts | 8 +-- .../workloads/store/workloads.actions.ts | 18 ------- .../workloads/store/workloads.reducers.ts | 54 +------------------ .../kubernetes/workloads/workload.types.ts | 1 - 7 files changed, 6 insertions(+), 102 deletions(-) diff --git a/custom-src/frontend/app/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts b/custom-src/frontend/app/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts index 66a1ff7641..3c3721943c 100644 --- a/custom-src/frontend/app/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts +++ b/custom-src/frontend/app/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts @@ -144,16 +144,9 @@ export class HelmReleaseTabBaseComponent implements OnDestroy { ); this.addResource(releaseResourceAction, resources); } else if (messageObj.kind === 'ManifestErrors') { - const manifestErrors = messageObj.data; - workloadsEntityCatalog.release.api.setManifestError( - this.helmReleaseHelper.endpointGuid, - this.helmReleaseHelper.namespace, - this.helmReleaseHelper.releaseTitle, - manifestErrors - ) - if (manifestErrors) { + if (messageObj.data) { this.store.dispatch( - new ShowSnackBar('Errors were found whilst loading this workload. Not all resources may be shown', 'Dismiss') + new ShowSnackBar('Errors were found when parsing this workload. Not all resources may be shown', 'Dismiss') ); } } diff --git a/custom-src/frontend/app/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts b/custom-src/frontend/app/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts index 90a4deff6d..bcdef4478b 100644 --- a/custom-src/frontend/app/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts @@ -44,7 +44,6 @@ export class HelmReleaseHelperService { this.release$ = entityService.waitForEntity$.pipe( map((item) => item.entity), - filter(item => !!item.chart), map((item: HelmRelease) => { if (!item.chart.metadata.icon) { const copy = JSON.parse(JSON.stringify(item)); diff --git a/custom-src/frontend/app/custom/kubernetes/workloads/store/workload-action-builders.ts b/custom-src/frontend/app/custom/kubernetes/workloads/store/workload-action-builders.ts index 944b7ab893..eb7591f9c5 100644 --- a/custom-src/frontend/app/custom/kubernetes/workloads/store/workload-action-builders.ts +++ b/custom-src/frontend/app/custom/kubernetes/workloads/store/workload-action-builders.ts @@ -1,13 +1,7 @@ import { OrchestratedActionBuilders, } from '../../../../../../store/src/entity-catalog/action-orchestrator/action-orchestrator'; -import { - GetHelmRelease, - GetHelmReleaseGraph, - GetHelmReleaseResource, - GetHelmReleases, - UpdateHelmReleaseManifestError, -} from './workloads.actions'; +import { GetHelmRelease, GetHelmReleaseGraph, GetHelmReleaseResource, GetHelmReleases } from './workloads.actions'; export interface WorkloadReleaseBuilders extends OrchestratedActionBuilders { get: ( @@ -15,11 +9,6 @@ export interface WorkloadReleaseBuilders extends OrchestratedActionBuilders { endpointGuid: string, extraArgs: { namespace: string } ) => GetHelmRelease; - setManifestError: ( - endpointGuid: string, - namespace: string, - releaseTitle: string, - manifestError: boolean) => UpdateHelmReleaseManifestError; getMultiple: () => GetHelmReleases; } @@ -27,8 +16,6 @@ export const workloadReleaseBuilders: WorkloadReleaseBuilders = { get: (title: string, endpointGuid: string, { namespace }: { namespace: string }) => { return new GetHelmRelease(endpointGuid, namespace, title); }, - setManifestError: (endpointGuid: string, namespace: string, releaseTitle: string, manifestError: boolean) => - new UpdateHelmReleaseManifestError(endpointGuid, namespace, releaseTitle, manifestError), getMultiple: () => new GetHelmReleases() } diff --git a/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads-entity-generator.ts b/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads-entity-generator.ts index 652c4287ad..f4a0939afa 100644 --- a/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads-entity-generator.ts +++ b/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads-entity-generator.ts @@ -16,7 +16,6 @@ import { workloadResourceBuilders, } from './workload-action-builders'; import { helmReleaseEntityKey, helmReleaseGraphEntityType, helmReleaseResourceEntityType } from './workloads-entity-factory'; -import { helmReleaseEntityReducer } from './workloads.reducers'; export function generateWorkloadsEntities(endpointDefinition: StratosEndpointExtensionDefinition): StratosBaseCatalogEntity[] { @@ -31,15 +30,12 @@ function generateReleaseEntity(endpointDefinition: StratosEndpointExtensionDefin const definition = { type: helmReleaseEntityKey, schema: kubernetesEntityFactory(helmReleaseEntityKey), - endpoint: endpointDefinition, + endpoint: endpointDefinition }; workloadsEntityCatalog.release = new StratosCatalogEntity( definition, { - actionBuilders: workloadReleaseBuilders, - dataReducers: [ - helmReleaseEntityReducer() - ], + actionBuilders: workloadReleaseBuilders } ); return workloadsEntityCatalog.release; diff --git a/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads.actions.ts b/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads.actions.ts index 54996403a7..b2448b591f 100644 --- a/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads.actions.ts +++ b/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads.actions.ts @@ -1,4 +1,3 @@ -import { Action } from '@ngrx/store'; import { EntityRequestAction } from 'frontend/packages/store/src/types/request.types'; import { PaginatedAction } from '../../../../../../store/src/types/pagination.types'; @@ -27,9 +26,6 @@ export const GET_HELM_RELEASE = '[Helm] Get Release Status'; export const GET_HELM_RELEASE_SUCCESS = '[Helm] Get Release Status Success'; export const GET_HELM_RELEASE_FAILURE = '[Helm] Get Release Status Failure'; - -export const UPDATE_HELM_RELEASE_MANIFEST_ERROR = '[Helm] Update Release Error'; - export const GET_HELM_RELEASE_PODS = '[Helm] Get Release Pods'; export const GET_HELM_RELEASE_PODS_SUCCESS = '[Helm] Get Release Pods Success'; export const GET_HELM_RELEASE_PODS_FAILURE = '[Helm] Get Release Pods Failure'; @@ -90,20 +86,6 @@ export class GetHelmRelease implements HelmReleaseSingleEntity { ]; } -export class UpdateHelmReleaseManifestError implements Action { - guid: string; - constructor( - public endpointGuid: string, - public namespace: string, - public releaseTitle: string, - public manifestError: boolean - ) { - this.guid = getHelmReleaseId(endpointGuid, namespace, releaseTitle); - } - type = UPDATE_HELM_RELEASE_MANIFEST_ERROR; -} - - export class GetHelmReleaseGraph implements HelmReleaseSingleEntity { guid: string; constructor( diff --git a/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads.reducers.ts b/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads.reducers.ts index 532f1caeb5..790325ad9a 100644 --- a/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads.reducers.ts +++ b/custom-src/frontend/app/custom/kubernetes/workloads/store/workloads.reducers.ts @@ -1,12 +1,4 @@ -import { Action } from '@ngrx/store'; - -import { IRequestEntityTypeState } from '../../../../../../store/src/app-state'; -import { HelmRelease } from '../workload.types'; -import { - UPDATE_HELM_RELEASE, - UPDATE_HELM_RELEASE_MANIFEST_ERROR, - UpdateHelmReleaseManifestError, -} from './workloads.actions'; +import { UPDATE_HELM_RELEASE } from './workloads.actions'; const defaultState = {}; @@ -19,47 +11,3 @@ export function helmReleaseReducer(state = defaultState, action) { } } - -const createDefaultHelmRelease = ( - endpointGuid: string, - guid: string, - name: string, - namespace: string -): HelmRelease => ({ - chart: null, - config: null, - endpointId: endpointGuid, - firstDeployed: null, - guid, - info: null, - lastDeployed: null, - manifestError: false, - name, - namespace, - status: null, - version: null -}) -export function helmReleaseEntityReducer() { - return (state: IRequestEntityTypeState, action: Action): IRequestEntityTypeState => { - switch (action.type) { - case UPDATE_HELM_RELEASE_MANIFEST_ERROR: - const updateErrorAction = action as UpdateHelmReleaseManifestError; - const release: HelmRelease = state[updateErrorAction.guid] || createDefaultHelmRelease( - updateErrorAction.endpointGuid, - updateErrorAction.guid, - updateErrorAction.releaseTitle, - updateErrorAction.namespace - ); - if (release.manifestError !== updateErrorAction.manifestError) { - return { - ...state, - [updateErrorAction.guid]: { - ...state[updateErrorAction.guid], - manifestError: updateErrorAction.manifestError - } - } - } - } - return state; - }; -} diff --git a/custom-src/frontend/app/custom/kubernetes/workloads/workload.types.ts b/custom-src/frontend/app/custom/kubernetes/workloads/workload.types.ts index 814e166c5a..f91220bad4 100644 --- a/custom-src/frontend/app/custom/kubernetes/workloads/workload.types.ts +++ b/custom-src/frontend/app/custom/kubernetes/workloads/workload.types.ts @@ -24,7 +24,6 @@ export interface HelmRelease { icon?: string; }; }; - manifestError: boolean; } export interface HelmReleaseEntity { From 8cc207d0d173f0fcf3706f15cdc472de63d95e5d Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 23 Jun 2020 10:27:18 +0100 Subject: [PATCH 541/648] Merge upstream (#392) * WIP * First pass at permission effects * First pass at reducer * Final set of cloud-foundry code out of common * Renames, todos * Minor fixes * Fix unit tests * Tweaks * Finish todos * Fix transition from space summary to app summary page - an entity service for a space with no org was cached by guid - an entity service for a space requiring org used cached version - solution is to make cache id include schema key (determines with/without org) * Add comment, tidy up rootUpdatingKey * Fix issue where gitlab summary tab threw errors in console - fixes #4325 * Fix misaligned user button - fixes #4316 * Temporarily add branch v3.2.1 to travis * check sso whitelist in more places Signed-off-by: Ben Berry * refactor sso state checks into single function Signed-off-by: Ben Berry * Update version * First pass at changelog (additional SSO fix required) * Update changelog * Push combine of permission configs into checkers - overcomes some weird change of permission type * Fix issue where default add/remove setting in change role by username was incorrect - add/remove radio button default value governed by add/remove feature flags - when one is set to false the radio button should be disabled and the other selected * Fix store-test-helper * Fix package-lock.json * Remove v3.2.1 was .travis.taml * Remove need for --recreate-pods when upgrading * Update readme following move to Travis-ci.com * Add support for helm chart customizations * Removed change not needed * Ensure the correct key is used metacard favourite info (#4321) * Fix display of details in endpoint card in endpoint list (#4319) * Clean default/blocked logic * Rename @stratos to @stratosui * FIx references in tsconfig.json * Fix endpoint connect * Fix unit test * Fix e2e test * Change following review #1 * Bump github.com/gorilla/websocket from 1.4.1 to 1.4.2 in /src/jetstream (#4199) * Bump github.com/gorilla/websocket from 1.4.1 to 1.4.2 in /src/jetstream Bumps [github.com/gorilla/websocket](https://github.com/gorilla/websocket) from 1.4.1 to 1.4.2. - [Release notes](https://github.com/gorilla/websocket/releases) - [Commits](https://github.com/gorilla/websocket/compare/v1.4.1...v1.4.2) Signed-off-by: dependabot-preview[bot] * Update go.sum Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: Neil MacDougall * Fix errors in console log during setup screens * Helm: Change default image pull policy to Always * Add copy address and edit to endpoint list view * Use icon that is less confusing with refresh * Change following review #2 - Fix cf package module file name - Make CUSTOM_USER_PERMISSION_CHECKERS optional - Remove need to inject CUSTOM_USER_PERMISSION_CHECKERS in multiple cf modules * Add newline at end for codeclimate * Fix for buildpack filename wrapping on card * Fix code climate issue * Update endpoint-list.helpers.ts * Improve naming * Tidy up * Change following review * Bump gopkg.in/yaml.v2 from 2.2.8 to 2.3.0 in /src/jetstream (#4336) * Bump gopkg.in/yaml.v2 from 2.2.8 to 2.3.0 in /src/jetstream Bumps [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml) from 2.2.8 to 2.3.0. - [Release notes](https://github.com/go-yaml/yaml/releases) - [Commits](https://github.com/go-yaml/yaml/compare/v2.2.8...v2.3.0) Signed-off-by: dependabot-preview[bot] * CI bump Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: Neil MacDougall * [Security] Bump websocket-extensions from 0.1.3 to 0.1.4 (#4356) Bumps [websocket-extensions](https://github.com/faye/websocket-extensions-node) from 0.1.3 to 0.1.4. **This update includes a security fix.** - [Release notes](https://github.com/faye/websocket-extensions-node/releases) - [Changelog](https://github.com/faye/websocket-extensions-node/blob/master/CHANGELOG.md) - [Commits](https://github.com/faye/websocket-extensions-node/compare/0.1.3...0.1.4) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Fail CI build if imagelist generation fails * Updated __stratos.tpl as per changes in deploy/kubernetes/console/templates/deployment.yaml * Fix build * Helm Chart: Icon is missing - fixes #393 * Fixes following review Co-authored-by: Ben Berry Co-authored-by: Neil MacDougall Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall --- CONTRIBUTING.md | 4 +- README.md | 16 +- custom-src/deploy/kubernetes/__stratos.tpl | 15 + ...onocular-repository-list-config.service.ts | 7 +- deploy/ci/automation/helmtest.sh | 6 +- deploy/cloud-foundry/README.md | 4 +- deploy/kubernetes/README.md | 6 +- deploy/kubernetes/build.sh | 1 + deploy/kubernetes/console/Chart.yaml | 2 +- deploy/kubernetes/console/README.md | 4 +- .../console/templates/__stratos.tpl | 5 + .../console/templates/config-init.yaml | 2 + .../console/templates/database.yaml | 2 + .../console/templates/deployment.yaml | 15 +- deploy/kubernetes/console/values.yaml | 2 +- deploy/kubernetes/imagelist-gen.sh | 4 + .../app-action-extension.component.spec.ts | 6 +- package-lock.json | 12 +- .../autoscaler-base.component.spec.ts | 2 +- .../autoscaler-metric-page.component.spec.ts | 2 +- ...caler-scale-history-page.component.spec.ts | 2 +- ...autoscaler-tab-extension.component.spec.ts | 2 +- .../edit-autoscaler-credential.component.ts | 1 - .../edit-autoscaler-policy-service.spec.ts | 2 +- ...-autoscaler-policy-step1.component.spec.ts | 2 +- ...-autoscaler-policy-step2.component.spec.ts | 2 +- ...-autoscaler-policy-step3.component.spec.ts | 2 +- ...-autoscaler-policy-step4.component.spec.ts | 2 +- .../edit-autoscaler-policy.component.spec.ts | 2 +- .../card-autoscaler-default.component.spec.ts | 2 +- ...p-autoscaler-events-config.service.spec.ts | 2 +- .../cf-app-autoscaler-events-data-source.ts | 2 +- ...scaler-metric-chart-card.component.spec.ts | 2 +- .../combo-series-vertical.component.spec.ts | 2 +- ...app-autoscaler-metric-chart-data-source.ts | 2 +- ...r-metric-chart-list-config.service.spec.ts | 2 +- .../src/actions/application.actions.ts | 48 +- .../src/actions/organization.actions.ts | 4 +- .../src/actions/permissions.actions.ts | 165 +++-- .../src/actions/users-roles.actions.ts | 2 +- .../src/actions/users.actions.ts | 50 +- .../cloud-foundry/src/cf-api.types.ts | 2 +- .../cloud-foundry/src/cf-entity-catalog.ts | 2 +- .../cloud-foundry/src/cf-entity-factory.ts | 2 +- .../cloud-foundry/src/cf-entity-generator.ts | 16 +- .../cloud-foundry/src/cf-entity-types.ts | 2 +- .../cloud-foundry/src/cf-favorites-helpers.ts | 4 +- ...ule.ts => cloud-foundry-package.module.ts} | 4 +- .../src/cloud-foundry-test.module.ts | 11 +- .../user.action-builders.ts | 10 +- .../entity-relations-validate.spec.ts | 2 +- .../processors/org-space-post-processor.ts | 2 +- ...te-instances-routes-list-config.service.ts | 2 +- .../app-delete-routes-list-config.service.ts | 2 +- .../application-wall.component.html | 2 +- .../application-wall.component.spec.ts | 4 +- .../application-wall.component.ts | 4 +- .../application-tabs-base.component.ts | 6 +- .../tabs/build-tab/build-tab.component.html | 19 +- .../tabs/build-tab/build-tab.component.ts | 4 +- .../instances-tab.component.spec.ts | 2 +- .../routes-tab/routes-tab.component.ts | 4 +- .../applications/applications.module.ts | 2 +- .../deploy-application-steps.types.ts | 7 +- .../src/features/cloud-foundry/cf.helpers.ts | 33 +- .../cli-info-cloud-foundry.component.html | 30 +- .../cli-info-cloud-foundry.component.spec.ts | 8 +- .../cli-info-cloud-foundry.component.ts | 9 +- .../cloud-foundry-tabs-base.component.spec.ts | 2 +- .../cloud-foundry-tabs-base.component.ts | 8 +- .../cloud-foundry/cloud-foundry.module.ts | 2 +- .../cloud-foundry.component.spec.ts | 4 +- .../edit-organization-step.component.html | 7 +- .../quota-definition.component.spec.ts | 2 +- .../quota-definition.component.ts | 6 +- .../cloud-foundry-organization.service.ts | 2 +- .../services/cloud-foundry-space.service.ts | 2 +- .../space-quota-definition.component.spec.ts | 2 +- .../space-quota-definition.component.ts | 6 +- .../cf-admin-add-user-warning.component.ts | 4 +- ...dry-organization-space-quotas.component.ts | 6 +- ...foundry-organization-spaces.component.html | 2 +- ...ndry-organization-spaces.component.spec.ts | 6 +- ...d-foundry-organization-spaces.component.ts | 4 +- ...cloud-foundry-space-summary.component.html | 4 +- ...ud-foundry-space-summary.component.spec.ts | 11 +- .../cloud-foundry-space-summary.component.ts | 6 +- .../cloud-foundry-space-users.component.ts | 6 +- ...oundry-organization-summary.component.html | 4 +- ...dry-organization-summary.component.spec.ts | 4 +- ...-foundry-organization-summary.component.ts | 8 +- ...ud-foundry-organization-users.component.ts | 8 +- .../cloud-foundry-organizations.component.ts | 6 +- .../cloud-foundry-quotas.component.ts | 6 +- .../cloud-foundry-users.component.ts | 2 +- .../user-invites/user-invite.service.ts | 6 +- .../invite-users-create.component.ts | 2 +- .../users/manage-users/cf-roles.service.ts | 28 +- .../manage-users-confirm.component.ts | 19 +- .../manage-users-modify.component.ts | 36 +- .../space-roles-list-wrapper.component.ts | 4 +- .../manage-users-select.component.ts | 2 +- .../manage-users-set-usernames.component.html | 6 +- .../manage-users-set-usernames.component.ts | 50 +- .../manage-users/manage-users.component.ts | 8 +- .../remove-user/remove-user.component.ts | 14 +- .../service-catalog/service-catalog.module.ts | 2 +- .../service-tabs-base.component.html | 7 +- .../service-tabs-base.component.spec.ts | 6 +- .../service-tabs-base.component.ts | 6 +- .../services-wall.component.html | 12 +- .../services-wall.component.spec.ts | 4 +- .../services-wall/services-wall.component.ts | 6 +- .../src/shared/cf-shared.module.ts | 10 +- .../cf-role-checkbox.component.ts | 32 +- .../create-application-step1.component.ts | 4 +- .../cf-app-instances-config.service.spec.ts | 2 +- .../cf-app-map-routes-list-config.service.ts | 4 +- .../cf-app-routes-list-config-base.ts | 8 +- .../cf-app-routes-list-config.service.spec.ts | 4 +- .../cf-app-routes-list-config.service.ts | 4 +- .../app-service-binding-card.component.ts | 10 +- .../app-service-binding-data-source.ts | 2 +- ...app-service-binding-list-config.service.ts | 12 +- .../list-types/app/cf-apps-data-source.ts | 2 +- .../cf-buildpack-card.component.html | 2 +- ...ell-confirm-role-add-rem.component.spec.ts | 2 +- .../cf-events/cf-events-data-source.ts | 4 +- .../cf-org-users-list-config.service.ts | 6 +- .../cf-org-card/cf-org-card.component.spec.ts | 2 +- .../cf-org-card/cf-org-card.component.ts | 12 +- .../cf-orgs/cf-orgs-data-source.service.ts | 2 +- .../cf-quotas-data-source.service.ts | 2 +- .../cf-quotas-list-config.service.ts | 10 +- .../table-cell-quota.component.spec.ts | 2 +- .../cf-routes/cf-routes-data-source-base.ts | 2 +- .../cf-routes-list-config.service.ts | 8 +- .../cf-security-groups-data-source.ts | 4 +- .../cf-select-users-data-source.service.ts | 2 +- .../cf-select-users-list-config.service.ts | 2 +- .../cf-service-instances-list-config.base.ts | 14 +- .../cf-services/cf-services-data-source.ts | 4 +- .../cf-services-list-config.service.ts | 5 +- .../cf-user-service-instances-list-config.ts | 14 +- .../cf-space-apps-data-source.service.ts | 4 +- .../cf-space-quotas-data-source.service.ts | 2 +- .../cf-space-quotas-list-config.service.ts | 10 +- .../cf-space-routes-list-config.service.ts | 8 +- .../cf-space-users-list-config.service.ts | 6 +- ...cf-spaces-service-instances-data-source.ts | 2 +- ...s-service-instances-list-config.service.ts | 4 +- ...aces-user-service-instances-data-source.ts | 2 +- .../cf-space-card.component.spec.ts | 2 +- .../cf-space-card/cf-space-card.component.ts | 10 +- .../cf-spaces-data-source.service.ts | 2 +- .../cf-stacks/cf-stacks-data-source.ts | 2 +- ...f-users-space-roles-data-source.service.ts | 6 +- ...f-users-space-roles-list-config.service.ts | 10 +- .../table-cell-select-org.component.ts | 4 +- .../cf-org-permission-cell.component.ts | 20 +- .../list-types/cf-users/cf-permission-cell.ts | 2 +- .../cf-space-permission-cell.component.ts | 20 +- .../cf-users/cf-user-data-source.service.ts | 4 +- .../cf-user-list-config.service.spec.ts | 4 +- .../cf-users/cf-user-list-config.service.ts | 14 +- .../cf-users/cf-user-list-helpers.ts | 2 +- .../detach-apps/detach-apps-data-source.ts | 2 +- .../service-instances-data-source.ts | 2 +- .../service-instances-list-config.service.ts | 4 +- .../service-plans-data-source.ts | 2 +- .../service-instance-card.component.ts | 12 +- .../service-instances-wall-data-source.ts | 2 +- ...vice-instances-wall-list-config.service.ts | 4 +- ...rovided-service-instance-card.component.ts | 12 +- .../cf-org-space-service.service.ts | 5 +- .../shared/data-services/cf-user.service.ts | 18 +- .../shared/data-services/scm/github-scm.ts | 4 + .../shared/data-services/scm/gitlab-scm.ts | 2 +- .../src/shared/data-services/scm/scm.ts | 1 + .../app-name-unique.directive.spec.ts | 2 +- .../cf-user-permission.directive.spec.ts | 34 + .../cf-user-permission.directive.ts | 80 +++ .../current-user-permissions.service.spec.ts | 643 +++++++++--------- .../store/cloud-foundry.reducers.module.ts | 8 +- .../src/store/cloud-foundry.store.module.ts | 7 +- .../src/store/effects/app.effects.ts | 4 +- .../src/store/effects/permissions.effect.ts | 274 -------- .../src/store/effects/update-app-effects.ts | 4 +- .../src/store}/effects/users-roles.effects.ts | 62 +- ...s.reducer.ts => cf-users-roles.reducer.ts} | 4 +- .../{users.reducer.ts => cf-users.reducer.ts} | 32 +- .../current-cf-user-base-cf-role.reducer.ts | 95 +++ .../current-cf-user-reducer.helpers.ts} | 21 +- .../current-cf-user-role-session.reducer.ts | 89 +++ ...current-cf-user-roles-changed.reducers.ts} | 56 +- .../current-cf-user-roles-clear.reducers.ts | 111 +++ .../current-cf-user-roles-org.reducer.ts | 47 ++ .../current-cf-user-roles-orgs.reducer.ts | 9 + .../current-cf-user-roles-space.reducer.ts} | 20 +- .../current-cf-user-roles-spaces.reducer.ts | 9 + .../current-cf-user-roles.reducer.spec.ts | 249 +++++++ .../current-cf-user-roles.reducer.ts | 90 +++ .../current-user-base-cf-role.reducer.ts | 17 - .../current-user-cf-roles.reducer.ts | 62 -- .../current-user-roles-org.reducer.ts | 47 -- .../current-user-roles-orgs.reducer.ts | 12 - .../current-user-roles-spaces.reducer.ts | 12 - .../cf-current-user-role.selectors.ts | 26 +- .../selectors/cf-users-roles.selector.ts | 50 ++ .../store/selectors/cloud-foundry.selector.ts | 1 - .../types/cf-current-user-roles.types.ts | 3 +- .../types/{user.types.ts => cf-user.types.ts} | 0 .../src/store/types/users-roles.types.ts | 2 +- .../cf-user-permissions-checkers.ts | 492 ++++++++++++++ .../user-permissions/cf-user-roles-fetch.ts | 218 ++++++ .../cloud-foundry-endpoint-service.helper.ts | 4 +- .../packages/core/src/app.component.spec.ts | 2 +- src/frontend/packages/core/src/app.module.ts | 10 +- .../packages/core/src/core/core.module.ts | 2 - .../core/current-user-permissions.checker.ts | 317 --------- .../core/current-user-permissions.config.ts | 159 ----- .../core/current-user-permissions.service.ts | 174 ----- .../core/src/core/endpoints.service.spec.ts | 2 +- .../log-out-dialog.component.spec.ts | 4 +- .../core/src/core/logger.service.spec.ts | 2 +- .../current-user-permissions.config.ts | 20 + .../current-user-permissions.service.spec.ts | 511 ++++++++++++++ .../current-user-permissions.service.ts | 197 ++++++ .../current-user-permissions.types.ts | 61 ++ .../stratos-user-permissions.checker.ts | 131 ++++ .../core/src/core/user-favorite-helpers.ts | 8 +- .../core/src/core/user-profile.service.ts | 2 + .../core/src/core/user.service.spec.ts | 2 +- .../about-page/about-page.component.spec.ts | 2 +- .../diagnostics-page.component.spec.ts | 2 +- .../eula-page/eula-page.component.spec.ts | 2 +- .../dashboard-base.component.ts | 2 +- .../side-nav/side-nav.component.spec.ts | 2 +- .../connect-endpoint-dialog.component.spec.ts | 2 +- .../connect-endpoint.component.spec.ts | 2 +- ...reate-endpoint-base-step.component.spec.ts | 2 +- ...reate-endpoint-cf-step-1.component.spec.ts | 2 +- .../create-endpoint-connect.component.spec.ts | 2 +- .../create-endpoint.component.spec.ts | 2 +- .../endpoints-page.component.spec.ts | 7 +- .../endpoints-page.component.ts | 4 +- .../error-page/error-page.component.spec.ts | 2 +- .../events-page/events-page.component.spec.ts | 2 +- .../home/home/home-page.component.spec.ts | 2 +- .../metrics/metrics/metrics.component.spec.ts | 2 +- .../no-endpoints-non-admin.component.spec.ts | 2 +- .../local-account-wizard.component.spec.ts | 2 +- .../edit-profile-info.component.spec.ts | 7 +- .../edit-profile-info.component.ts | 6 +- .../profile-info.component.spec.ts | 4 +- .../core/src/logged-in.service.spec.ts | 2 +- .../code-block/code-block.component.spec.ts | 2 +- .../copy-to-clipboard.component.spec.ts | 2 +- .../endpoints-missing.component.spec.ts | 2 +- .../meta-card.component.spec.ts | 2 +- .../meta-card-base/meta-card.component.ts | 4 +- .../meta-card-item.component.scss | 11 + .../meta-card-item.component.ts | 1 + .../table-cell-actions.component.spec.ts | 2 +- ...ble-cell-request-monitor-icon.component.ts | 2 +- .../table-cell/table-cell.component.ts | 4 + .../list/list-table/table.component.spec.ts | 2 +- .../endpoint-card.component.html | 7 +- .../endpoint-card/endpoint-card.component.ts | 14 +- .../endpoint/endpoint-list.helpers.ts | 18 +- .../endpoints-list-config.service.spec.ts | 5 +- .../endpoint/endpoints-list-config.service.ts | 6 +- ...table-cell-endpoint-address.component.html | 5 + ...table-cell-endpoint-address.component.scss | 8 + ...le-cell-endpoint-address.component.spec.ts | 28 + .../table-cell-endpoint-address.component.ts | 33 + .../components/list/list.component.spec.ts | 6 +- .../loading-page.component.spec.ts | 4 +- .../markdown-preview.component.spec.ts | 2 +- .../metrics-chart.component.spec.ts | 2 +- ...cs-parent-range-selector.component.spec.ts | 4 +- .../metrics-range-selector.component.spec.ts | 4 +- .../page-header/page-header.component.html | 2 +- .../recent-entities.component.html | 2 +- .../recent-entities.component.spec.ts | 2 +- .../sidepanel-preview.component.spec.ts | 2 +- .../steppers/steppers.component.spec.ts | 2 +- .../src/shared/global-events.service.spec.ts | 2 +- .../packages/core/src/shared/shared.module.ts | 1 - .../shared/user-permission.directive.spec.ts | 5 +- .../src/shared/user-permission.directive.ts | 63 +- .../core/test-framework/core-test.helper.ts | 8 +- .../store/src/actions/permissions.actions.ts | 9 + .../store/src/effects/permissions.effect.ts | 73 ++ .../entity-catalog-entity.ts | 4 +- .../src/entity-catalog/entity-catalog.ts | 26 + .../entity-catalog/entity-catalog.types.ts | 12 +- .../success-entity-request.handler.ts | 18 +- .../entity-request-pipeline.types.ts | 18 +- .../packages/store/src/entity-service.ts | 3 + .../entity-monitor.factory.service.ts | 4 +- .../src/reducers/api-request-reducer/types.ts | 4 +- .../current-user-request-state.reducers.ts | 50 -- .../current-user-role-session.reducer.ts | 105 --- .../current-user-roles-clear.reducers.ts | 119 ---- .../current-user-roles.reducer.spec.ts | 249 +------ .../current-user-roles.reducer.ts | 123 ++-- .../selectors/current-user-role.selectors.ts | 38 +- .../store/src/selectors/endpoint.selectors.ts | 15 +- .../src/selectors/users-roles.selector.ts | 50 -- .../packages/store/src/store.module.ts | 6 +- .../packages/store/src/types/auth.types.ts | 11 +- .../src/types/current-user-roles.types.ts | 29 +- .../store/src/types/endpoint.types.ts | 6 +- .../store/testing/src/store-test-helper.ts | 10 +- src/jetstream/go.mod | 2 +- src/jetstream/go.sum | 7 +- ...nage-users-by-username-stepper-e2e.spec.ts | 2 +- .../cloud-foundry/users-removal-e2e.helper.ts | 2 +- src/test-e2e/helpers/cf-helpers.ts | 2 +- src/tsconfig.json | 4 +- 321 files changed, 4154 insertions(+), 3043 deletions(-) create mode 100644 custom-src/deploy/kubernetes/__stratos.tpl create mode 100644 deploy/kubernetes/console/templates/__stratos.tpl rename src/frontend/packages/cloud-foundry/src/{cloud-foundry.module.ts => cloud-foundry-package.module.ts} (91%) create mode 100644 src/frontend/packages/cloud-foundry/src/shared/directives/cf-user-permission/cf-user-permission.directive.spec.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/directives/cf-user-permission/cf-user-permission.directive.ts rename src/frontend/packages/{core/src/core => cloud-foundry/src/shared/services}/current-user-permissions.service.spec.ts (62%) delete mode 100644 src/frontend/packages/cloud-foundry/src/store/effects/permissions.effect.ts rename src/frontend/packages/{store/src => cloud-foundry/src/store}/effects/users-roles.effects.ts (78%) rename src/frontend/packages/cloud-foundry/src/store/reducers/{users-roles.reducer.ts => cf-users-roles.reducer.ts} (97%) rename src/frontend/packages/cloud-foundry/src/store/reducers/{users.reducer.ts => cf-users.reducer.ts} (90%) create mode 100644 src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-base-cf-role.reducer.ts rename src/frontend/packages/{store/src/reducers/current-user-roles-reducer/current-user-reducer.helpers.ts => cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-reducer.helpers.ts} (65%) create mode 100644 src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-role-session.reducer.ts rename src/frontend/packages/cloud-foundry/src/store/reducers/{current-user-roles-reducer/current-user-roles-changed.reducers.ts => current-cf-user-roles-reducer/current-cf-user-roles-changed.reducers.ts} (69%) create mode 100644 src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-clear.reducers.ts create mode 100644 src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-org.reducer.ts create mode 100644 src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-orgs.reducer.ts rename src/frontend/packages/cloud-foundry/src/store/reducers/{current-user-roles-reducer/current-user-roles-space.reducer.ts => current-cf-user-roles-reducer/current-cf-user-roles-space.reducer.ts} (67%) create mode 100644 src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-spaces.reducer.ts create mode 100644 src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles.reducer.spec.ts create mode 100644 src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles.reducer.ts delete mode 100644 src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-base-cf-role.reducer.ts delete mode 100644 src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-cf-roles.reducer.ts delete mode 100644 src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-roles-org.reducer.ts delete mode 100644 src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-roles-orgs.reducer.ts delete mode 100644 src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-roles-spaces.reducer.ts create mode 100644 src/frontend/packages/cloud-foundry/src/store/selectors/cf-users-roles.selector.ts delete mode 100644 src/frontend/packages/cloud-foundry/src/store/selectors/cloud-foundry.selector.ts rename src/frontend/packages/cloud-foundry/src/store/types/{user.types.ts => cf-user.types.ts} (100%) create mode 100644 src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-permissions-checkers.ts create mode 100644 src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-roles-fetch.ts delete mode 100644 src/frontend/packages/core/src/core/current-user-permissions.checker.ts delete mode 100644 src/frontend/packages/core/src/core/current-user-permissions.config.ts delete mode 100644 src/frontend/packages/core/src/core/current-user-permissions.service.ts create mode 100644 src/frontend/packages/core/src/core/permissions/current-user-permissions.config.ts create mode 100644 src/frontend/packages/core/src/core/permissions/current-user-permissions.service.spec.ts create mode 100644 src/frontend/packages/core/src/core/permissions/current-user-permissions.service.ts create mode 100644 src/frontend/packages/core/src/core/permissions/current-user-permissions.types.ts create mode 100644 src/frontend/packages/core/src/core/permissions/stratos-user-permissions.checker.ts create mode 100644 src/frontend/packages/core/src/shared/components/list/list-types/endpoint/table-cell-endpoint-address/table-cell-endpoint-address.component.html create mode 100644 src/frontend/packages/core/src/shared/components/list/list-types/endpoint/table-cell-endpoint-address/table-cell-endpoint-address.component.scss create mode 100644 src/frontend/packages/core/src/shared/components/list/list-types/endpoint/table-cell-endpoint-address/table-cell-endpoint-address.component.spec.ts create mode 100644 src/frontend/packages/core/src/shared/components/list/list-types/endpoint/table-cell-endpoint-address/table-cell-endpoint-address.component.ts create mode 100644 src/frontend/packages/store/src/actions/permissions.actions.ts create mode 100644 src/frontend/packages/store/src/effects/permissions.effect.ts delete mode 100644 src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-request-state.reducers.ts delete mode 100644 src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-role-session.reducer.ts delete mode 100644 src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-roles-clear.reducers.ts delete mode 100644 src/frontend/packages/store/src/selectors/users-roles.selector.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9ffe5499d4..5da3d8b31d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,10 +6,10 @@ There are two main forms of contribution: reporting issues and performing code c ## Reporting Issues -If you find a problem with Stratos UI, report it using [GitHub issues](https://github.com/cloudfoundry/stratos/issues/new). +If you find a problem with Stratos UI, report it using [GitHub issues](https://github.com/suse/stratos/issues/new). Before reporting a new issue, please take a moment to check whether it has already been reported -[here](https://github.com/cloudfoundry/stratos/issues). If this is the case, please: +[here](https://github.com/suse/stratos/issues). If this is the case, please: - Read all the comments to confirm that it's the same issue you're having. - Refrain from adding "same thing here" or "+1" comments. Just hit the diff --git a/README.md b/README.md index 25c152a24f..d49aa9dec1 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ # Stratos -  - - -[![GitHub release](https://img.shields.io/github/release/cloudfoundry/stratos.svg)](https://github.com/cloudfoundry/stratos/releases/latest) -[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/cloudfoundry/stratos/blob/master/LICENSE) +  + + +[![GitHub release](https://img.shields.io/github/release/suse/stratos.svg)](https://github.com/suse/stratos/releases/latest) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/suse/stratos/blob/master/LICENSE) [![slack.cloudfoundry.org](https://slack.cloudfoundry.org/badge.svg)](https://cloudfoundry.slack.com/messages/C80EP4Y57/) Stratos is an Open Source Web-based UI (Console) for managing Cloud Foundry. It allows users and administrators to both manage applications running in the Cloud Foundry cluster and perform cluster management tasks. If you are looking for a version of Stratos, you can find .. -- V1 in the [v1-master](https://github.com/cloudfoundry/stratos/tree/v1-master) branch. -- V2 in the [v2-master](https://github.com/cloudfoundry/stratos/tree/v2-master) branch. +- V1 in the [v1-master](https://github.com/suse/stratos/tree/v1-master) branch. +- V2 in the [v2-master](https://github.com/suse/stratos/tree/v2-master) branch. ![Stratos Application view](docs/images/screenshots/app-summary.png) @@ -46,7 +46,7 @@ Get an [Overview](docs/overview.md) of Stratos, its components and the different Take a look at the [Development Roadmap](docs/roadmap.md) to see where we are heading. We update our status page each week to summarize what we are working on - see the [Status Page](docs/status_updates.md). -Browse through features and issues in the project's [issues](https://github.com/cloudfoundry/stratos/issues) page or [Zenhub Board](https://github.com/cloudfoundry-incubator/stratos#boards). +Browse through features and issues in the project's [issues](https://github.com/suse/stratos/issues) page. What kind of code is in Stratos? We've integrated [Code Climate](https://codeclimate.com) for some code quality and maintainability metrics. Take a stroll around the [project page](https://codeclimate.com/github/SUSE/stratos) diff --git a/custom-src/deploy/kubernetes/__stratos.tpl b/custom-src/deploy/kubernetes/__stratos.tpl new file mode 100644 index 0000000000..3ebbce1efe --- /dev/null +++ b/custom-src/deploy/kubernetes/__stratos.tpl @@ -0,0 +1,15 @@ +# Customizations for the Helm Chart + +# Extra env vars for the Jetstream Pod in deployment.yaml +{{- define "stratosJetstreamEnv" }} +- name: MONOCULAR_CRT_PATH + value: "/etc/monocular-certs/tls.crt" +- name: MONOCULAR_KEY_PATH + value: "/etc/monocular-certs/tls.key" +- name: MONOCULAR_CA_CRT_PATH + value: "/etc/monocular-certs/ca.crt" +- name: FDB_URL + value: "mongodb://{{ .Release.Name }}-fdbdoclayer:27016" +- name: SYNC_SERVER_URL + value: "http://{{ .Release.Name }}-chartsync:8080" +{{- end }} \ No newline at end of file diff --git a/custom-src/frontend/app/custom/helm/list-types/monocular-repository-list-config.service.ts b/custom-src/frontend/app/custom/helm/list-types/monocular-repository-list-config.service.ts index 84b0b50c70..c2ff5464a6 100644 --- a/custom-src/frontend/app/custom/helm/list-types/monocular-repository-list-config.service.ts +++ b/custom-src/frontend/app/custom/helm/list-types/monocular-repository-list-config.service.ts @@ -16,8 +16,8 @@ import { InternalEventMonitorFactory } from '../../../../../store/src/monitors/i import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; import { STRATOS_ENDPOINT_TYPE } from '../../../base-entity-schemas'; -import { CurrentUserPermissions } from '../../../core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../core/current-user-permissions.service'; +import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; +import { StratosCurrentUserPermissions } from '../../../core/permissions/stratos-user-permissions.checker'; import { environment } from '../../../environments/environment'; import { getFullEndpointApiUrl } from '../../../features/endpoints/endpoint-helpers'; import { ConfirmationDialogConfig } from '../../../shared/components/confirmation-dialog.config'; @@ -127,7 +127,7 @@ export class MonocularRepositoryListConfig implements IListConfig }, label: 'Delete', description: 'Delete Helm Repository', - createVisible: () => this.currentUserPermissionsService.can(CurrentUserPermissions.ENDPOINT_REGISTER) + createVisible: () => this.currentUserPermissionsService.can(StratosCurrentUserPermissions.ENDPOINT_REGISTER) }; private handleDeleteAction(item, handleChange) { @@ -148,6 +148,7 @@ export class MonocularRepositoryListConfig implements IListConfig }); } + constructor( public store: Store, public activatedRoute: ActivatedRoute, diff --git a/deploy/ci/automation/helmtest.sh b/deploy/ci/automation/helmtest.sh index 8c05ad703f..5a50bfac18 100755 --- a/deploy/ci/automation/helmtest.sh +++ b/deploy/ci/automation/helmtest.sh @@ -164,7 +164,7 @@ CHART_FILE="${KUBE_FOLDER}/console-${DEV_IMAGE_VERSION}.tgz" echo "Chart file path: ${CHART_FILE}" log "Upgrading using latest Helm Chart" -helm upgrade ${NAME} "${CHART_FILE}" --recreate-pods --debug --set consoleVersion=${DEV_IMAGE_VERSION} --set imagePullPolicy=Always +helm upgrade ${NAME} "${CHART_FILE}" --debug --set consoleVersion=${DEV_IMAGE_VERSION} --set imagePullPolicy=Always checkVersion console-${DEV_IMAGE_VERSION} waitForHelmRelease @@ -177,7 +177,7 @@ sed -i.bak -e 's/appVersion: '"${DEV_IMAGE_VERSION}"'/appVersion: 0.2.0/g' "${HE cat "${HELM_TMP}/Chart.yaml" log "Upgrading using latest Helm Chart (checking chart upgrade)" -helm upgrade ${NAME} "${HELM_TMP}" --recreate-pods --debug --set imagePullPolicy=Always +helm upgrade ${NAME} "${HELM_TMP}" --debug --set imagePullPolicy=Always waitForHelmRelease checkVersion console-0.2.0 @@ -204,7 +204,7 @@ helm install ${HELM_REPO_NAME}/console --name ${NAME} --namespace ${NAMESPACE} waitForHelmRelease log "Upgrading using --reuse-values" -helm upgrade ${NAME} "${HELM_TMP}" --recreate-pods --debug --reuse-values +helm upgrade ${NAME} "${HELM_TMP}" --debug --reuse-values waitForHelmRelease # Should have used same values as before diff --git a/deploy/cloud-foundry/README.md b/deploy/cloud-foundry/README.md index d06e09234e..424faa2a48 100644 --- a/deploy/cloud-foundry/README.md +++ b/deploy/cloud-foundry/README.md @@ -73,7 +73,7 @@ and binding a database service instance to Stratos - for more information see [h To do so, `clone` the **stratos** repository, `cd` into the newly cloned repository and `push` to Cloud Foundry. This can be done with: ``` -git clone https://github.com/cloudfoundry/stratos +git clone https://github.com/suse/stratos cd stratos git checkout tags/stable -b stable ./build/store-git-metadata.sh @@ -91,7 +91,7 @@ If you wish to enable AOT or reduce the push time, you can pre-build the UI befo This can be done with: ``` -git clone https://github.com/cloudfoundry/stratos +git clone https://github.com/suse/stratos cd stratos npm install npm run prebuild-ui diff --git a/deploy/kubernetes/README.md b/deploy/kubernetes/README.md index 35b76359f2..10358b4ad5 100644 --- a/deploy/kubernetes/README.md +++ b/deploy/kubernetes/README.md @@ -105,7 +105,7 @@ After the install, you should be able to access the Console in a web browser by ### Deploy using an archive of the Helm Chart -Helm chart archives are available for Stratos releases from our GitHub repository, under releases - see https://github.com/cloudfoundry/stratos/releases. +Helm chart archives are available for Stratos releases from our GitHub repository, under releases - see https://github.com/suse/stratos/releases. Download the appropriate release `console-helm-chart.X.Y.Z.tgz` from the GitHub repository and unpack the archive to a local folder. The Helm Chart will be extracted to a sub-folder named `console`. @@ -122,7 +122,7 @@ helm install console --namespace=console --name my-console Clone the Stratos GitHub repository: ``` -git clone https://github.com/cloudfoundry/stratos.git +git clone https://github.com/suse/stratos.git ``` Open a terminal and cd to the `deploy/kubernetes` directory: @@ -220,7 +220,7 @@ $ helm repo update To update an instance, the following assumes your instance is called `my-console`, and overrides have been specified in a file called `overrides.yaml`. ``` -$ helm upgrade -f overrides.yaml my-console stratos/console --recreate-pods +$ helm upgrade -f overrides.yaml my-console stratos/console ``` After the upgrade, perform a `helm list` to ensure your console is the latest version. diff --git a/deploy/kubernetes/build.sh b/deploy/kubernetes/build.sh index c2e0c5e730..63cc4a0f63 100755 --- a/deploy/kubernetes/build.sh +++ b/deploy/kubernetes/build.sh @@ -236,6 +236,7 @@ pushd "${DEST_HELM_CHART_PATH}" > /dev/null rm -rf "${DEST_HELM_CHART_PATH}/**/*.orig" # Run customization script if there is one +# This can do things like provide a custom __stratos.tpl file if [ -f "${STRATOS_PATH}/custom-src/deploy/kubernetes/customize-helm.sh" ]; then printf "${YELLOW}${BOLD}Applying Helm Chart customizations${RESET}\n" "${STRATOS_PATH}/custom-src/deploy/kubernetes/customize-helm.sh" "${DEST_HELM_CHART_PATH}" diff --git a/deploy/kubernetes/console/Chart.yaml b/deploy/kubernetes/console/Chart.yaml index fcf011b8c0..c12c15f631 100644 --- a/deploy/kubernetes/console/Chart.yaml +++ b/deploy/kubernetes/console/Chart.yaml @@ -5,4 +5,4 @@ version: 0.1.0 appVersion: 0.1.0 sources: - https://github.com/cloudfoundry/stratos -icon: https://raw.githubusercontent.com/cloudfoundry/stratos/master/deploy/kubernetes/icon.png \ No newline at end of file +icon: https://github.com/cloudfoundry/stratos/raw/master/deploy/kubernetes/console/icon.png \ No newline at end of file diff --git a/deploy/kubernetes/console/README.md b/deploy/kubernetes/console/README.md index e4c920c061..cc4a7399d4 100644 --- a/deploy/kubernetes/console/README.md +++ b/deploy/kubernetes/console/README.md @@ -155,11 +155,9 @@ helm repo update To update an instance, the following assumes your instance is called `my-console`: ``` -helm upgrade my-console stratos/console --recreate-pods +helm upgrade my-console stratos/console ``` -> Note: You *must* use the `--recreate-pods` flag when upgrading - After the upgrade, perform a `helm list` to ensure your console is the latest version. ## Uninstalling diff --git a/deploy/kubernetes/console/templates/__stratos.tpl b/deploy/kubernetes/console/templates/__stratos.tpl new file mode 100644 index 0000000000..f1cc982504 --- /dev/null +++ b/deploy/kubernetes/console/templates/__stratos.tpl @@ -0,0 +1,5 @@ +# Customizations for the Helm Chart + +# Extra env vars for the Jetstream Pod in deployment.yaml +{{- define "stratosJetstreamEnv" }} +{{- end }} \ No newline at end of file diff --git a/deploy/kubernetes/console/templates/config-init.yaml b/deploy/kubernetes/console/templates/config-init.yaml index e6d2f3582a..0cc82ee389 100644 --- a/deploy/kubernetes/console/templates/config-init.yaml +++ b/deploy/kubernetes/console/templates/config-init.yaml @@ -102,6 +102,8 @@ spec: {{- end }} containers: - env: + - name: STRATOS_IMAGE_REF + value: "{{.Values.consoleVersion}}:{{ .Release.Revision }}" - name: "STRATOS_VOLUME_MIGRATION" value: "true" - name: "IS_UPGRADE" diff --git a/deploy/kubernetes/console/templates/database.yaml b/deploy/kubernetes/console/templates/database.yaml index 2f23173f49..5f64b162a7 100644 --- a/deploy/kubernetes/console/templates/database.yaml +++ b/deploy/kubernetes/console/templates/database.yaml @@ -60,6 +60,8 @@ spec: image: {{.Values.kube.registry.hostname}}/{{.Values.kube.organization}}/{{.Values.images.mariadb}}:{{.Values.consoleVersion}} imagePullPolicy: {{.Values.imagePullPolicy}} env: + - name: STRATOS_IMAGE_REF + value: "{{.Values.consoleVersion}}:{{ .Release.Revision }}" - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: diff --git a/deploy/kubernetes/console/templates/deployment.yaml b/deploy/kubernetes/console/templates/deployment.yaml index b0aec9dc1b..60656b1d83 100644 --- a/deploy/kubernetes/console/templates/deployment.yaml +++ b/deploy/kubernetes/console/templates/deployment.yaml @@ -65,6 +65,8 @@ spec: name: https protocol: TCP env: + - name: STRATOS_IMAGE_REF + value: "{{.Values.consoleVersion}}:{{ .Release.Revision }}" - name: CONSOLE_CERT_PATH value: "/{{ .Release.Name }}-cert-volume" volumeMounts: @@ -75,6 +77,8 @@ spec: imagePullPolicy: {{.Values.imagePullPolicy}} name: proxy env: + - name: STRATOS_IMAGE_REF + value: "{{.Values.consoleVersion}}:{{ .Release.Revision }}" - name: STRATOS_HELM_RELEASE value: "{{ .Release.Name }}" - name: DB_USER @@ -277,16 +281,7 @@ spec: - name: UI_LIST_ALLOW_LOAD_MAXED value: {{ default "false" .Values.console.ui.listAllowLoadMaxed | quote }} {{- end }} - - name: MONOCULAR_CRT_PATH - value: "/etc/monocular-certs/tls.crt" - - name: MONOCULAR_KEY_PATH - value: "/etc/monocular-certs/tls.key" - - name: MONOCULAR_CA_CRT_PATH - value: "/etc/monocular-certs/ca.crt" - - name: FDB_URL - value: "mongodb://{{ .Release.Name }}-fdbdoclayer:27016" - - name: SYNC_SERVER_URL - value: "http://{{ .Release.Name }}-chartsync:8080" + {{- include "stratosJetstreamEnv" . | indent 8 }} readinessProbe: httpGet: path: /pp/v1/ping diff --git a/deploy/kubernetes/console/values.yaml b/deploy/kubernetes/console/values.yaml index 8c7a7cf614..dd4b40cbdf 100644 --- a/deploy/kubernetes/console/values.yaml +++ b/deploy/kubernetes/console/values.yaml @@ -8,7 +8,7 @@ dockerRegistrySecret: regsecret #noProxy: localhost #ftpProxy: proxy.corp.net #socksProxy: sock-proxy.corp.net -imagePullPolicy: IfNotPresent +imagePullPolicy: Always console: cookieDomain: diff --git a/deploy/kubernetes/imagelist-gen.sh b/deploy/kubernetes/imagelist-gen.sh index 293c1a7ba4..77db87f409 100755 --- a/deploy/kubernetes/imagelist-gen.sh +++ b/deploy/kubernetes/imagelist-gen.sh @@ -37,6 +37,10 @@ ls -alR echo "" helm template -f ${__DIRNAME}/imagelist.values.yaml ${CHART_FOLDER} | grep "image:" | grep --extended --only-matching '([^"/[:space:]]+/)?[^"/[:space:]]+/[^:[:space:]]+:[a-zA-Z0-9\._-]+' | sort | uniq | awk -F'/' '{print $2}' > imagelist.txt +if [ $? -ne 0 ]; then + echo -e "${BOLD}${RED}ERROR: Failed to render Helm Chart in order to generate image list" + exit 1 +fi popd > /dev/null printf "${CYAN}" diff --git a/examples/custom-src/frontend/app/custom/app-action-extension/app-action-extension.component.spec.ts b/examples/custom-src/frontend/app/custom/app-action-extension/app-action-extension.component.spec.ts index 4efb359c3f..d63c3f0a38 100644 --- a/examples/custom-src/frontend/app/custom/app-action-extension/app-action-extension.component.spec.ts +++ b/examples/custom-src/frontend/app/custom/app-action-extension/app-action-extension.component.spec.ts @@ -1,10 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; -import { AppActionExtensionComponent } from './app-action-extension.component'; import { CoreModule } from '../../core/core.module'; -import { RouterTestingModule } from '@angular/router/testing'; import { SharedModule } from '../../shared/shared.module'; -import { createBasicStoreModule } from '@stratos/store/testing'; +import { AppActionExtensionComponent } from './app-action-extension.component'; describe('AppActionExtensionComponent', () => { let component: AppActionExtensionComponent; diff --git a/package-lock.json b/package-lock.json index 0fa8335d5d..200d6f47de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7113,7 +7113,7 @@ }, "duplexer": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, @@ -7816,7 +7816,7 @@ }, "event-stream": { "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", "dev": true, "requires": { @@ -12140,7 +12140,7 @@ }, "map-stream": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "resolved": "http://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", "dev": true }, @@ -14933,7 +14933,7 @@ }, "pause-stream": { "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", "dev": true, "requires": { @@ -18516,7 +18516,7 @@ }, "split": { "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "resolved": "http://registry.npmjs.org/split/-/split-0.3.3.tgz", "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", "dev": true, "requires": { @@ -18693,7 +18693,7 @@ }, "stream-combiner": { "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", "dev": true, "requires": { diff --git a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-base.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-base.component.spec.ts index fb7e09d975..5afb7cda0e 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-base.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-base.component.spec.ts @@ -2,7 +2,7 @@ import { DatePipe } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { createEmptyStoreModule } from '@stratos/store/testing'; +import { createEmptyStoreModule } from '@stratosui/store/testing'; import { ApplicationService } from '../../../cloud-foundry/src/features/applications/application.service'; import { ApplicationServiceMock } from '../../../cloud-foundry/test-framework/application-service-helper'; diff --git a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-metric-page/autoscaler-metric-page.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-metric-page/autoscaler-metric-page.component.spec.ts index e387014816..22140a5cdf 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-metric-page/autoscaler-metric-page.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-metric-page/autoscaler-metric-page.component.spec.ts @@ -2,7 +2,7 @@ import { DatePipe } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { createEmptyStoreModule } from '@stratos/store/testing'; +import { createEmptyStoreModule } from '@stratosui/store/testing'; import { ApplicationService } from '../../../../cloud-foundry/src/features/applications/application.service'; import { ApplicationServiceMock } from '../../../../cloud-foundry/test-framework/application-service-helper'; diff --git a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-scale-history-page/autoscaler-scale-history-page.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-scale-history-page/autoscaler-scale-history-page.component.spec.ts index ccb88d5a1b..78e8a572a4 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-scale-history-page/autoscaler-scale-history-page.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-scale-history-page/autoscaler-scale-history-page.component.spec.ts @@ -2,7 +2,7 @@ import { DatePipe } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { createEmptyStoreModule } from '@stratos/store/testing'; +import { createEmptyStoreModule } from '@stratosui/store/testing'; import { ApplicationService } from '../../../../cloud-foundry/src/features/applications/application.service'; import { ApplicationServiceMock } from '../../../../cloud-foundry/test-framework/application-service-helper'; diff --git a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.spec.ts index 68b583fc77..eff100853b 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.spec.ts @@ -2,7 +2,7 @@ import { DatePipe } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { createEmptyStoreModule } from '@stratos/store/testing'; +import { createEmptyStoreModule } from '@stratosui/store/testing'; import { NgxChartsModule } from '@swimlane/ngx-charts'; import { ApplicationService } from '../../../../cloud-foundry/src/features/applications/application.service'; diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-credential/edit-autoscaler-credential.component.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-credential/edit-autoscaler-credential.component.ts index e2f4f14a6d..d327f4bf5b 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-credential/edit-autoscaler-credential.component.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-credential/edit-autoscaler-credential.component.ts @@ -109,7 +109,6 @@ export class EditAutoscalerCredentialComponent implements OnInit, OnDestroy { ); this.appAutoscalerCredential$ = updateAppAutoscalerCredentialService.entityObs$.pipe( filter(({ entity, entityRequestInfo }) => { - console.log(entity, entityRequestInfo) return entityRequestInfo && !entityRequestInfo.creating && !entityRequestInfo.deleting.busy; }), map(({ entity }) => entity ? entity.entity : null), diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-service.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-service.spec.ts index 09d208f0a0..884f637d50 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-service.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-service.spec.ts @@ -1,5 +1,5 @@ import { inject, TestBed } from '@angular/core/testing'; -import { createEmptyStoreModule } from '@stratos/store/testing'; +import { createEmptyStoreModule } from '@stratosui/store/testing'; import { ApplicationsModule } from '../../../../cloud-foundry/src/features/applications/applications.module'; import { EntityServiceFactory } from '../../../../store/src/entity-service-factory.service'; diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component.spec.ts index ac3ca5bcab..02095792e5 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component.spec.ts @@ -2,7 +2,7 @@ import { DatePipe } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { createEmptyStoreModule } from '@stratos/store/testing'; +import { createEmptyStoreModule } from '@stratosui/store/testing'; import { ApplicationService } from '../../../../../cloud-foundry/src/features/applications/application.service'; import { ApplicationServiceMock } from '../../../../../cloud-foundry/test-framework/application-service-helper'; diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.spec.ts index f6dc925fa8..05552c2692 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.spec.ts @@ -2,7 +2,7 @@ import { DatePipe } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { createEmptyStoreModule } from '@stratos/store/testing'; +import { createEmptyStoreModule } from '@stratosui/store/testing'; import { ApplicationService } from '../../../../../cloud-foundry/src/features/applications/application.service'; import { ApplicationServiceMock } from '../../../../../cloud-foundry/test-framework/application-service-helper'; diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step3/edit-autoscaler-policy-step3.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step3/edit-autoscaler-policy-step3.component.spec.ts index 52b26205f7..01d38f4030 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step3/edit-autoscaler-policy-step3.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step3/edit-autoscaler-policy-step3.component.spec.ts @@ -2,7 +2,7 @@ import { DatePipe } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { createEmptyStoreModule } from '@stratos/store/testing'; +import { createEmptyStoreModule } from '@stratosui/store/testing'; import { ApplicationService } from '../../../../../cloud-foundry/src/features/applications/application.service'; import { ApplicationServiceMock } from '../../../../../cloud-foundry/test-framework/application-service-helper'; diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.spec.ts index 8bf205e4f0..a2f2dab563 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.spec.ts @@ -2,7 +2,7 @@ import { DatePipe } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { createEmptyStoreModule } from '@stratos/store/testing'; +import { createEmptyStoreModule } from '@stratosui/store/testing'; import { ApplicationService } from '../../../../../cloud-foundry/src/features/applications/application.service'; import { ApplicationServiceMock } from '../../../../../cloud-foundry/test-framework/application-service-helper'; diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy.component.spec.ts index b9e5aac8c8..9be13c7f96 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy.component.spec.ts @@ -2,7 +2,7 @@ import { DatePipe } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { createEmptyStoreModule } from '@stratos/store/testing'; +import { createEmptyStoreModule } from '@stratosui/store/testing'; import { ApplicationService } from '../../../../cloud-foundry/src/features/applications/application.service'; import { ApplicationServiceMock } from '../../../../cloud-foundry/test-framework/application-service-helper'; diff --git a/src/frontend/packages/cf-autoscaler/src/shared/card-autoscaler-default/card-autoscaler-default.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/shared/card-autoscaler-default/card-autoscaler-default.component.spec.ts index e0b1c180b2..872a62cad6 100644 --- a/src/frontend/packages/cf-autoscaler/src/shared/card-autoscaler-default/card-autoscaler-default.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/shared/card-autoscaler-default/card-autoscaler-default.component.spec.ts @@ -1,7 +1,7 @@ import { CommonModule } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { createEmptyStoreModule } from '@stratos/store/testing'; +import { createEmptyStoreModule } from '@stratosui/store/testing'; import { ApplicationService } from '../../../../cloud-foundry/src/features/applications/application.service'; import { diff --git a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-config.service.spec.ts b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-config.service.spec.ts index a30d9ddc7e..56fe3b5865 100644 --- a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-config.service.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-config.service.spec.ts @@ -3,7 +3,7 @@ import { HttpBackend, HttpClient, HttpClientModule } from '@angular/common/http' import { HttpTestingController } from '@angular/common/http/testing'; import { inject, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { createEmptyStoreModule } from '@stratos/store/testing'; +import { createEmptyStoreModule } from '@stratosui/store/testing'; import { GetApplication } from '../../../../../cloud-foundry/src/actions/application.actions'; import { cfEntityFactory } from '../../../../../cloud-foundry/src/cf-entity-factory'; diff --git a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-data-source.ts b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-data-source.ts index 22d2449f95..32b9e419e4 100644 --- a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-data-source.ts +++ b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-data-source.ts @@ -1,5 +1,5 @@ import { Store } from '@ngrx/store'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; import { ListDataSource } from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; diff --git a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-card/app-autoscaler-metric-chart-card.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-card/app-autoscaler-metric-chart-card.component.spec.ts index 7f0a6a1cbf..6444356618 100644 --- a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-card/app-autoscaler-metric-chart-card.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-card/app-autoscaler-metric-chart-card.component.spec.ts @@ -1,6 +1,6 @@ import { DatePipe } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { createEmptyStoreModule } from '@stratos/store/testing'; +import { createEmptyStoreModule } from '@stratosui/store/testing'; import { NgxChartsModule } from '@swimlane/ngx-charts'; import { diff --git a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-card/combo-chart/combo-series-vertical.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-card/combo-chart/combo-series-vertical.component.spec.ts index d8ec4e79aa..367ee3f0d1 100644 --- a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-card/combo-chart/combo-series-vertical.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-card/combo-chart/combo-series-vertical.component.spec.ts @@ -2,7 +2,7 @@ import { DatePipe } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { createEmptyStoreModule } from '@stratos/store/testing'; +import { createEmptyStoreModule } from '@stratosui/store/testing'; import { NgxChartsModule } from '@swimlane/ngx-charts'; import { ApplicationService } from '../../../../../../../cloud-foundry/src/features/applications/application.service'; diff --git a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-data-source.ts b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-data-source.ts index bbff81af73..562f11e8b9 100644 --- a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-data-source.ts +++ b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-data-source.ts @@ -1,5 +1,5 @@ import { Store } from '@ngrx/store'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; import { ListDataSource } from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; diff --git a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-list-config.service.spec.ts b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-list-config.service.spec.ts index 4c6669fe61..d4bf8da651 100644 --- a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-list-config.service.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-list-config.service.spec.ts @@ -1,6 +1,6 @@ import { DatePipe } from '@angular/common'; import { inject, TestBed } from '@angular/core/testing'; -import { createEmptyStoreModule } from '@stratos/store/testing'; +import { createEmptyStoreModule } from '@stratosui/store/testing'; import { ApplicationEnvVarsHelper, diff --git a/src/frontend/packages/cloud-foundry/src/actions/application.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/application.actions.ts index 50488bd836..a3bd914226 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/application.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/application.actions.ts @@ -12,37 +12,33 @@ import { createEntityRelationPaginationKey, EntityInlineParentAction } from '../ import { AppMetadataTypes } from './app-metadata.actions'; import { CFStartAction } from './cf-action.types'; -export const GET_ALL = '[Application] Get all'; -export const GET_ALL_SUCCESS = '[Application] Get all success'; -export const GET_ALL_FAILED = '[Application] Get all failed'; +const GET_ALL = '[Application] Get all'; +const GET_ALL_SUCCESS = '[Application] Get all success'; +const GET_ALL_FAILED = '[Application] Get all failed'; -export const GET = '[Application] Get one'; -export const GET_SUCCESS = '[Application] Get one success'; -export const GET_FAILED = '[Application] Get one failed'; +const GET = '[Application] Get one'; +const GET_SUCCESS = '[Application] Get one success'; +const GET_FAILED = '[Application] Get one failed'; -export const GET_SUMMARY = '[Application] Get summary'; -export const GET_SUMMARY_SUCCESS = '[Application] Get summary success'; -export const GET_SUMMARY_FAILED = '[Application] Get summary failed'; +const CREATE = '[Application] Create'; +const CREATE_SUCCESS = '[Application] Create success'; +const CREATE_FAILED = '[Application] Create failed'; -export const CREATE = '[Application] Create'; -export const CREATE_SUCCESS = '[Application] Create success'; -export const CREATE_FAILED = '[Application] Create failed'; +export const CF_APP_UPDATE = '[Application] Update'; +export const CF_APP_UPDATE_SUCCESS = '[Application] Update success'; +export const CF_APP_UPDATE_FAILED = '[Application] Update failed'; -export const UPDATE = '[Application] Update'; -export const UPDATE_SUCCESS = '[Application] Update success'; -export const UPDATE_FAILED = '[Application] Update failed'; +const DELETE = '[Application] Delete'; +const DELETE_SUCCESS = '[Application] Delete success'; +const DELETE_FAILED = '[Application] Delete failed'; -export const DELETE = '[Application] Delete'; -export const DELETE_SUCCESS = '[Application] Delete success'; -export const DELETE_FAILED = '[Application] Delete failed'; +const DELETE_INSTANCE = '[Application Instance] Delete'; +const DELETE_INSTANCE_SUCCESS = '[Application Instance] Delete success'; +const DELETE_INSTANCE_FAILED = '[Application Instance] Delete failed'; -export const DELETE_INSTANCE = '[Application Instance] Delete'; -export const DELETE_INSTANCE_SUCCESS = '[Application Instance] Delete success'; -export const DELETE_INSTANCE_FAILED = '[Application Instance] Delete failed'; - -export const RESTAGE = '[Application] Restage'; -export const RESTAGE_SUCCESS = '[Application] Restage success'; -export const RESTAGE_FAILED = '[Application] Restage failed'; +const RESTAGE = '[Application] Restage'; +const RESTAGE_SUCCESS = '[Application] Restage success'; +const RESTAGE_FAILED = '[Application] Restage failed'; const applicationEntitySchema = cfEntityFactory(applicationEntityType); @@ -140,7 +136,7 @@ export class UpdateExistingApplication extends CFStartAction implements ICFActio newApplication ); } - actions = [UPDATE, UPDATE_SUCCESS, UPDATE_FAILED]; + actions = [CF_APP_UPDATE, CF_APP_UPDATE_SUCCESS, CF_APP_UPDATE_FAILED]; entity = [applicationEntitySchema]; entityType = applicationEntityType; options: HttpRequest; diff --git a/src/frontend/packages/cloud-foundry/src/actions/organization.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/organization.actions.ts index ff01a09be0..0603548dcf 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/organization.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/organization.actions.ts @@ -19,7 +19,7 @@ import { EntityInlineParentAction, } from '../entity-relations/entity-relations.types'; import { CFStartAction } from './cf-action.types'; -import { createDefaultUserRelations } from './users.actions'; +import { createDefaultCfUserRelations } from './users.actions'; export const GET_ORGANIZATION = '[Organization] Get one'; export const GET_ORGANIZATION_SUCCESS = '[Organization] Get one success'; @@ -231,7 +231,7 @@ export class GetAllOrgUsers extends CFStartAction implements PaginatedAction, En public paginationKey: string, public endpointGuid: string, public isAdmin: boolean, - public includeRelations: string[] = createDefaultUserRelations()) { + public includeRelations: string[] = createDefaultCfUserRelations()) { super(); this.options = new HttpRequest( 'GET', diff --git a/src/frontend/packages/cloud-foundry/src/actions/permissions.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/permissions.actions.ts index 1cd3efeb4e..15f3cafc93 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/permissions.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/permissions.actions.ts @@ -1,54 +1,47 @@ +import { HttpRequest } from '@angular/common/http'; import { Action } from '@ngrx/store'; import { APIResource } from '../../../store/src/types/api.types'; import { organizationEntityType, spaceEntityType } from '../cf-entity-types'; -import { HttpRequest } from '@angular/common/http'; -export const GET_AUDITED_ORG_CURRENT_USER_RELATIONS = '[Current User] Get audited org Relations'; -export const GET_AUDITED_ORG_CURRENT_USER_RELATIONS_SUCCESS = '[Current User] Get audited org Relations success'; -export const GET_AUDITED_ORG_CURRENT_USER_RELATIONS_FAILED = '[Current User] Get audited org Relations failed'; +export const GET_AUDITED_ORG_CURRENT_CF_USER_RELATIONS = '[Current User] Get audited org Relations'; +export const GET_AUDITED_ORG_CURRENT_CF_USER_RELATIONS_SUCCESS = '[Current User] Get audited org Relations success'; +export const GET_AUDITED_ORG_CURRENT_CF_USER_RELATIONS_FAILED = '[Current User] Get audited org Relations failed'; -export const GET_BILLING_MANAGED_ORG_CURRENT_USER_RELATIONS = '[Current User] Get BILLING_MANAGED org Relations'; -export const GET_BILLING_MANAGED_ORG_CURRENT_USER_RELATIONS_SUCCESS = '[Users] Get BILLING_MANAGED org Relations success'; -export const GET_BILLING_MANAGED_ORG_CURRENT_USER_RELATIONS_FAILED = '[Users] Get BILLING_MANAGED org Relations failed'; +export const GET_BILLING_MANAGED_ORG_CURRENT_CF_USER_RELATIONS = '[Current User] Get BILLING_MANAGED org Relations'; +export const GET_BILLING_MANAGED_ORG_CURRENT_CF_USER_RELATIONS_SUCCESS = '[Users] Get BILLING_MANAGED org Relations success'; +export const GET_BILLING_MANAGED_ORG_CURRENT_CF_USER_RELATIONS_FAILED = '[Users] Get BILLING_MANAGED org Relations failed'; -export const GET_MANAGED_ORG_CURRENT_USER_RELATIONS = '[Current User] Get MANAGED org Relations'; -export const GET_MANAGED_ORG_CURRENT_USER_RELATIONS_SUCCESS = '[Current User] Get MANAGED org Relations success'; -export const GET_MANAGED_ORG_CURRENT_USER_RELATIONS_FAILED = '[Current User] Get MANAGED org Relations failed'; +export const GET_MANAGED_ORG_CURRENT_CF_USER_RELATIONS = '[Current User] Get MANAGED org Relations'; +export const GET_MANAGED_ORG_CURRENT_CF_USER_RELATIONS_SUCCESS = '[Current User] Get MANAGED org Relations success'; +export const GET_MANAGED_ORG_CURRENT_CF_USER_RELATIONS_FAILED = '[Current User] Get MANAGED org Relations failed'; -export const GET_ORG_CURRENT_USER_RELATIONS = '[Current User] Get org Relations'; -export const GET_ORG_CURRENT_USER_RELATIONS_SUCCESS = '[Current User] Get org Relations success'; -export const GET_ORG_CURRENT_USER_RELATIONS_FAILED = '[Current User] Get org Relations failed'; +export const GET_ORG_CURRENT_CF_USER_RELATIONS = '[Current User] Get org Relations'; +export const GET_ORG_CURRENT_CF_USER_RELATIONS_SUCCESS = '[Current User] Get org Relations success'; +export const GET_ORG_CURRENT_CF_USER_RELATIONS_FAILED = '[Current User] Get org Relations failed'; -export const GET_AUDITED_SPACE_CURRENT_USER_RELATIONS = '[Current User] Get AUDITED_SPACE Relations'; -export const GET_AUDITED_SPACE_CURRENT_USER_RELATIONS_SUCCESS = '[Current User] Get AUDITED_SPACE Relations success'; -export const GET_AUDITED_SPACE_CURRENT_USER_RELATIONS_FAILED = '[Current User] Get AUDITED_SPACE Relations failed'; +export const GET_AUDITED_SPACE_CURRENT_CF_USER_RELATIONS = '[Current User] Get AUDITED_SPACE Relations'; +export const GET_AUDITED_SPACE_CURRENT_CF_USER_RELATIONS_SUCCESS = '[Current User] Get AUDITED_SPACE Relations success'; +export const GET_AUDITED_SPACE_CURRENT_CF_USER_RELATIONS_FAILED = '[Current User] Get AUDITED_SPACE Relations failed'; -export const GET_MANAGED_SPACE_CURRENT_USER_RELATIONS = '[Current User] Get MANAGED_SPACE Relations'; -export const GET_MANAGED_SPACE_CURRENT_USER_RELATIONS_SUCCESS = '[Current User] Get MANAGED_SPACE Relations success'; -export const GET_MANAGED_SPACE_CURRENT_USER_RELATIONS_FAILED = '[Current User] Get MANAGED_SPACE Relations failed'; +export const GET_MANAGED_SPACE_CURRENT_CF_USER_RELATIONS = '[Current User] Get MANAGED_SPACE Relations'; +export const GET_MANAGED_SPACE_CURRENT_CF_USER_RELATIONS_SUCCESS = '[Current User] Get MANAGED_SPACE Relations success'; +export const GET_MANAGED_SPACE_CURRENT_CF_USER_RELATIONS_FAILED = '[Current User] Get MANAGED_SPACE Relations failed'; -export const GET_SPACE_CURRENT_USER_RELATIONS = '[Current User] Get SPACE Relations'; -export const GET_SPACE_CURRENT_USER_RELATIONS_SUCCESS = '[Current User] Get SPACE Relations success'; -export const GET_SPACE_CURRENT_USER_RELATIONS_FAILED = '[Current User] Get SPACE Relations failed'; +export const GET_SPACE_CURRENT_CF_USER_RELATIONS = '[Current User] Get SPACE Relations'; +export const GET_SPACE_CURRENT_CF_USER_RELATIONS_SUCCESS = '[Current User] Get SPACE Relations success'; +export const GET_SPACE_CURRENT_CF_USER_RELATIONS_FAILED = '[Current User] Get SPACE Relations failed'; -export const GET_CURRENT_USER_RELATION = '[Current User] Get relation'; -export const GET_CURRENT_USER_RELATION_SUCCESS = '[Current User] Get relation success'; -export const GET_CURRENT_USER_RELATION_FAILED = '[Current User] Get relation failed'; +export const GET_CURRENT_CF_USER_RELATION = '[Current User] Get relation'; +export const GET_CURRENT_CF_USER_RELATION_SUCCESS = '[Current User] Get relation success'; +export const GET_CURRENT_CF_USER_RELATION_FAILED = '[Current User] Get relation failed'; -export const GET_CURRENT_USER_RELATIONS = '[Current User] Get relations'; -export const GET_CURRENT_USER_RELATIONS_SUCCESS = '[Current User] Get relations success'; -export const GET_CURRENT_USER_RELATIONS_FAILED = '[Current User] Get relations failed'; -export const GET_CURRENT_USER_CF_RELATIONS = '[Current User] Get CF relations'; -export const GET_CURRENT_USER_CF_RELATIONS_SUCCESS = '[Current User] Get CF relations success'; -export const GET_CURRENT_USER_CF_RELATIONS_FAILED = '[Current User] Get CF relations failed'; - -export class GetCurrentUsersRelations implements Action { - type = GET_CURRENT_USER_RELATIONS; -} +export const GET_CURRENT_CF_USER_RELATIONS = '[Current User] Get CF relations'; +export const GET_CURRENT_CF_USER_RELATIONS_SUCCESS = '[Current User] Get CF relations success'; +export const GET_CURRENT_CF_USER_RELATIONS_FAILED = '[Current User] Get CF relations failed'; -export enum UserRelationTypes { +export enum CfUserRelationTypes { AUDITED_ORGANIZATIONS = 'audited_organizations', BILLING_MANAGED_ORGANIZATION = 'billing_managed_organizations', MANAGED_ORGANIZATION = 'managed_organizations', @@ -58,7 +51,7 @@ export enum UserRelationTypes { SPACES = 'spaces' } -export interface IUserRelationTypes { +export interface ICfUserRelationTypes { [key: string]: { actions: [ string, @@ -69,128 +62,128 @@ export interface IUserRelationTypes { }; } -export class GetUserCfRelations implements Action { +export class GetCfUserRelations implements Action { constructor(public cfGuid: string, public type: string) { } } /** * Used in conjunction with `permissions.effects.ts` to fetch roles of a user connected to a cf that power the permissions model */ -export class GetUserRelations implements Action { - public type = GET_CURRENT_USER_RELATION; +export class GetCurrentCfUserRelations implements Action { + public type = GET_CURRENT_CF_USER_RELATION; public actions: string[]; public options: HttpRequest; - constructor(public guid: string, public relationType: UserRelationTypes, public endpointGuid: string) { + constructor(public guid: string, public relationType: CfUserRelationTypes, public endpointGuid: string) { const typeOptions = this.types[relationType]; this.options = new HttpRequest( 'GET', `users/${guid}/${relationType}` ); this.actions = typeOptions.actions; - this.type = GET_CURRENT_USER_RELATION; + this.type = GET_CURRENT_CF_USER_RELATION; } - private types: IUserRelationTypes = { - [UserRelationTypes.AUDITED_ORGANIZATIONS]: { + private types: ICfUserRelationTypes = { + [CfUserRelationTypes.AUDITED_ORGANIZATIONS]: { actions: [ - GET_AUDITED_ORG_CURRENT_USER_RELATIONS, - GET_AUDITED_ORG_CURRENT_USER_RELATIONS_SUCCESS, - GET_AUDITED_ORG_CURRENT_USER_RELATIONS_FAILED + GET_AUDITED_ORG_CURRENT_CF_USER_RELATIONS, + GET_AUDITED_ORG_CURRENT_CF_USER_RELATIONS_SUCCESS, + GET_AUDITED_ORG_CURRENT_CF_USER_RELATIONS_FAILED ], entityType: organizationEntityType }, - [UserRelationTypes.BILLING_MANAGED_ORGANIZATION]: { + [CfUserRelationTypes.BILLING_MANAGED_ORGANIZATION]: { actions: [ - GET_BILLING_MANAGED_ORG_CURRENT_USER_RELATIONS, - GET_BILLING_MANAGED_ORG_CURRENT_USER_RELATIONS_SUCCESS, - GET_BILLING_MANAGED_ORG_CURRENT_USER_RELATIONS_FAILED + GET_BILLING_MANAGED_ORG_CURRENT_CF_USER_RELATIONS, + GET_BILLING_MANAGED_ORG_CURRENT_CF_USER_RELATIONS_SUCCESS, + GET_BILLING_MANAGED_ORG_CURRENT_CF_USER_RELATIONS_FAILED ], entityType: organizationEntityType }, - [UserRelationTypes.MANAGED_ORGANIZATION]: { + [CfUserRelationTypes.MANAGED_ORGANIZATION]: { actions: [ - GET_MANAGED_ORG_CURRENT_USER_RELATIONS, - GET_MANAGED_ORG_CURRENT_USER_RELATIONS_SUCCESS, - GET_MANAGED_ORG_CURRENT_USER_RELATIONS_FAILED + GET_MANAGED_ORG_CURRENT_CF_USER_RELATIONS, + GET_MANAGED_ORG_CURRENT_CF_USER_RELATIONS_SUCCESS, + GET_MANAGED_ORG_CURRENT_CF_USER_RELATIONS_FAILED ], entityType: organizationEntityType }, - [UserRelationTypes.ORGANIZATIONS]: { - actions: [GET_ORG_CURRENT_USER_RELATIONS, GET_ORG_CURRENT_USER_RELATIONS_SUCCESS, GET_ORG_CURRENT_USER_RELATIONS_FAILED], + [CfUserRelationTypes.ORGANIZATIONS]: { + actions: [GET_ORG_CURRENT_CF_USER_RELATIONS, GET_ORG_CURRENT_CF_USER_RELATIONS_SUCCESS, GET_ORG_CURRENT_CF_USER_RELATIONS_FAILED], entityType: organizationEntityType }, - [UserRelationTypes.AUDITED_SPACES]: { + [CfUserRelationTypes.AUDITED_SPACES]: { actions: [ - GET_AUDITED_SPACE_CURRENT_USER_RELATIONS, - GET_AUDITED_SPACE_CURRENT_USER_RELATIONS_SUCCESS, - GET_AUDITED_SPACE_CURRENT_USER_RELATIONS_FAILED + GET_AUDITED_SPACE_CURRENT_CF_USER_RELATIONS, + GET_AUDITED_SPACE_CURRENT_CF_USER_RELATIONS_SUCCESS, + GET_AUDITED_SPACE_CURRENT_CF_USER_RELATIONS_FAILED ], entityType: spaceEntityType }, - [UserRelationTypes.MANAGED_SPACES]: { + [CfUserRelationTypes.MANAGED_SPACES]: { actions: [ - GET_MANAGED_SPACE_CURRENT_USER_RELATIONS, - GET_MANAGED_SPACE_CURRENT_USER_RELATIONS_SUCCESS, - GET_MANAGED_SPACE_CURRENT_USER_RELATIONS_FAILED + GET_MANAGED_SPACE_CURRENT_CF_USER_RELATIONS, + GET_MANAGED_SPACE_CURRENT_CF_USER_RELATIONS_SUCCESS, + GET_MANAGED_SPACE_CURRENT_CF_USER_RELATIONS_FAILED ], entityType: spaceEntityType }, - [UserRelationTypes.SPACES]: { + [CfUserRelationTypes.SPACES]: { actions: [ - GET_SPACE_CURRENT_USER_RELATIONS, - GET_SPACE_CURRENT_USER_RELATIONS_SUCCESS, - GET_SPACE_CURRENT_USER_RELATIONS_SUCCESS + GET_SPACE_CURRENT_CF_USER_RELATIONS, + GET_SPACE_CURRENT_CF_USER_RELATIONS_SUCCESS, + GET_SPACE_CURRENT_CF_USER_RELATIONS_SUCCESS ], entityType: spaceEntityType } }; } -export class GetCurrentUserRelationsComplete { - public type = GET_CURRENT_USER_RELATION_SUCCESS; +export class GetCurrentCfUserRelationsComplete { + public type = GET_CURRENT_CF_USER_RELATION_SUCCESS; constructor( - public relationType: UserRelationTypes, public endpointGuid: string, public data: APIResource[] + public relationType: CfUserRelationTypes, public endpointGuid: string, public data: APIResource[] ) { } } -export class GetCurrentUsersAuditedOrganizations extends GetUserRelations { +export class GetCurrentCfUsersAuditedOrganizations extends GetCurrentCfUserRelations { constructor(public guid: string, endpointGuid: string) { - super(guid, UserRelationTypes.AUDITED_ORGANIZATIONS, endpointGuid); + super(guid, CfUserRelationTypes.AUDITED_ORGANIZATIONS, endpointGuid); } } -export class GetCurrentUsersBillingOrganizations extends GetUserRelations { +export class GetCurrentCfUsersBillingOrganizations extends GetCurrentCfUserRelations { constructor(public guid: string, endpointGuid: string) { - super(guid, UserRelationTypes.BILLING_MANAGED_ORGANIZATION, endpointGuid); + super(guid, CfUserRelationTypes.BILLING_MANAGED_ORGANIZATION, endpointGuid); } } -export class GetCurrentUsersManagedOrganizations extends GetUserRelations { +export class GetCurrentCfUsersManagedOrganizations extends GetCurrentCfUserRelations { constructor(public guid: string, endpointGuid: string) { - super(guid, UserRelationTypes.MANAGED_ORGANIZATION, endpointGuid); + super(guid, CfUserRelationTypes.MANAGED_ORGANIZATION, endpointGuid); } } -export class GetCurrentUsersOrganizations extends GetUserRelations { +export class GetCurrentCfUsersOrganizations extends GetCurrentCfUserRelations { constructor(public guid: string, endpointGuid: string) { - super(guid, UserRelationTypes.ORGANIZATIONS, endpointGuid); + super(guid, CfUserRelationTypes.ORGANIZATIONS, endpointGuid); } } -export class GetCurrentUsersAuditedSpaces extends GetUserRelations { +export class GetCurrentCfUsersAuditedSpaces extends GetCurrentCfUserRelations { constructor(public guid: string, endpointGuid: string) { - super(guid, UserRelationTypes.AUDITED_SPACES, endpointGuid); + super(guid, CfUserRelationTypes.AUDITED_SPACES, endpointGuid); } } -export class GetCurrentUsersManagedSpaces extends GetUserRelations { +export class GetCurrentCfUsersManagedSpaces extends GetCurrentCfUserRelations { constructor(public guid: string, endpointGuid: string) { - super(guid, UserRelationTypes.MANAGED_SPACES, endpointGuid); + super(guid, CfUserRelationTypes.MANAGED_SPACES, endpointGuid); } } -export class GetCurrentUsersSpaces extends GetUserRelations { +export class GetCurrentCfUsersSpaces extends GetCurrentCfUserRelations { constructor(public guid: string, endpointGuid: string) { - super(guid, UserRelationTypes.SPACES, endpointGuid); + super(guid, CfUserRelationTypes.SPACES, endpointGuid); } } diff --git a/src/frontend/packages/cloud-foundry/src/actions/users-roles.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/users-roles.actions.ts index 66d36f8827..4682fd67ed 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/users-roles.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/users-roles.actions.ts @@ -1,6 +1,6 @@ import { Action } from '@ngrx/store'; -import { CfUser } from '../store/types/user.types'; +import { CfUser } from '../store/types/cf-user.types'; import { CfRoleChange } from '../store/types/users-roles.types'; export const UsersRolesActions = { diff --git a/src/frontend/packages/cloud-foundry/src/actions/users.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/users.actions.ts index 8aa7e4be31..40f7d57afc 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/users.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/users.actions.ts @@ -12,29 +12,23 @@ import { createEntityRelationPaginationKey, EntityInlineParentAction, } from '../entity-relations/entity-relations.types'; -import { CfUserRoleParams, OrgUserRoleNames, SpaceUserRoleNames } from '../store/types/user.types'; +import { CfUserRoleParams, OrgUserRoleNames, SpaceUserRoleNames } from '../store/types/cf-user.types'; import { CFStartAction } from './cf-action.types'; -export const GET_ALL = '[Users] Get all'; -export const GET_ALL_SUCCESS = '[Users] Get all success'; -export const GET_ALL_FAILED = '[Users] Get all failed'; +export const GET_ALL_CF_USERS = '[Users] Get all'; +export const GET_ALL_CF_USERS_SUCCESS = '[Users] Get all success'; +export const GET_ALL_CF_USERS_FAILED = '[Users] Get all failed'; -export const REMOVE_ROLE = '[Users] Remove role'; -export const REMOVE_ROLE_SUCCESS = '[Users] Remove role success'; -export const REMOVE_ROLE_FAILED = '[Users] Remove role failed'; +export const REMOVE_CF_ROLE = '[Users] Remove role'; +export const REMOVE_CF_ROLE_SUCCESS = '[Users] Remove role success'; +export const REMOVE_CF_ROLE_FAILED = '[Users] Remove role failed'; -export const ADD_ROLE = '[Users] Add role'; -export const ADD_ROLE_SUCCESS = '[Users] Add role success'; -export const ADD_ROLE_FAILED = '[Users] Add role failed'; +export const ADD_CF_ROLE = '[Users] Add role'; +export const ADD_CF_ROLE_SUCCESS = '[Users] Add role success'; +export const ADD_CF_ROLE_FAILED = '[Users] Add role failed'; -export const GET_CF_USER = '[Users] Get cf user '; -export const GET_CF_USER_SUCCESS = '[Users] Get cf user success'; -export const GET_CF_USER_FAILED = '[Users] Get cf user failed'; -export const GET_CF_USERS_AS_NON_ADMIN = '[Users] Get cf users by org '; -export const GET_CF_USERS_AS_NON_ADMIN_SUCCESS = '[Users] Get cf users by org success'; - -export function createDefaultUserRelations() { +export function createDefaultCfUserRelations() { return [ createEntityRelationKey(cfUserEntityType, CfUserRoleParams.ORGANIZATIONS), createEntityRelationKey(cfUserEntityType, CfUserRoleParams.AUDITED_ORGS), @@ -47,12 +41,12 @@ export function createDefaultUserRelations() { } -export class GetAllUsersAsAdmin extends CFStartAction implements PaginatedAction, EntityInlineParentAction { +export class GetAllCfUsersAsAdmin extends CFStartAction implements PaginatedAction, EntityInlineParentAction { isGetAllUsersAsAdmin = true; paginationKey: string; constructor( public endpointGuid: string, - public includeRelations: string[] = createDefaultUserRelations(), + public includeRelations: string[] = createDefaultCfUserRelations(), public populateMissing = true, paginationKey?: string ) { @@ -63,7 +57,7 @@ export class GetAllUsersAsAdmin extends CFStartAction implements PaginatedAction 'users' ); } - actions = [GET_ALL, GET_ALL_SUCCESS, GET_ALL_FAILED]; + actions = [GET_ALL_CF_USERS, GET_ALL_CF_USERS_SUCCESS, GET_ALL_CF_USERS_FAILED]; entity = [cfEntityFactory(cfUserEntityType)]; entityType = cfUserEntityType; options: HttpRequest; @@ -98,7 +92,7 @@ enum ChangeUserRoleType { /** * Add or remove a user's role, either by user guid or name */ -export class ChangeUserRole extends CFStartAction implements EntityRequestAction { +export class ChangeCfUserRole extends CFStartAction implements EntityRequestAction { public endpointType = 'cf'; constructor( public endpointGuid: string, @@ -115,7 +109,7 @@ export class ChangeUserRole extends CFStartAction implements EntityRequestAction ) { super(); this.guid = entityGuid; - this.updatingKey = ChangeUserRole.generateUpdatingKey(permissionTypeKey, userGuid); + this.updatingKey = ChangeCfUserRole.generateUpdatingKey(permissionTypeKey, userGuid); this.options = new HttpRequest( this.createMethod(), this.createUrl(), @@ -166,7 +160,7 @@ export class ChangeUserRole extends CFStartAction implements EntityRequestAction } } -export class AddUserRole extends ChangeUserRole { +export class AddCfUserRole extends ChangeCfUserRole { constructor( endpointGuid: string, userGuid: string, @@ -182,7 +176,7 @@ export class AddUserRole extends ChangeUserRole { endpointGuid, userGuid, ChangeUserRoleType.ADD, - [ADD_ROLE, ADD_ROLE_SUCCESS, ADD_ROLE_FAILED], + [ADD_CF_ROLE, ADD_CF_ROLE_SUCCESS, ADD_CF_ROLE_FAILED], permissionTypeKey, entityGuid, isSpace, @@ -194,7 +188,7 @@ export class AddUserRole extends ChangeUserRole { } } -export class RemoveUserRole extends ChangeUserRole { +export class RemoveCfUserRole extends ChangeCfUserRole { constructor( endpointGuid: string, userGuid: string, @@ -210,7 +204,7 @@ export class RemoveUserRole extends ChangeUserRole { endpointGuid, userGuid, ChangeUserRoleType.REMOVE, - [REMOVE_ROLE, REMOVE_ROLE_SUCCESS, REMOVE_ROLE_FAILED], + [REMOVE_CF_ROLE, REMOVE_CF_ROLE_SUCCESS, REMOVE_CF_ROLE_FAILED], permissionTypeKey, entityGuid, isSpace, @@ -222,11 +216,11 @@ export class RemoveUserRole extends ChangeUserRole { } } -export class GetUser extends CFStartAction { +export class GetCfUser extends CFStartAction { constructor( public endpointGuid: string, public guid: string, - public includeRelations: string[] = createDefaultUserRelations(), + public includeRelations: string[] = createDefaultCfUserRelations(), public populateMissing = true) { super(); this.options = new HttpRequest( diff --git a/src/frontend/packages/cloud-foundry/src/cf-api.types.ts b/src/frontend/packages/cloud-foundry/src/cf-api.types.ts index 0ccae9e71a..0449db54d5 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-api.types.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-api.types.ts @@ -1,6 +1,6 @@ import { APIResource } from '../../store/src/types/api.types'; import { IService, IServiceBinding } from './cf-api-svc.types'; -import { CfUser } from './store/types/user.types'; +import { CfUser } from './store/types/cf-user.types'; export interface StratosCFEntity { cfGuid: string; diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-catalog.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-catalog.ts index 380c47cce7..a3db99e684 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-catalog.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-catalog.ts @@ -61,8 +61,8 @@ import { StackActionBuilders } from './entity-action-builders/stack-action-build import { UserProvidedServiceActionBuilder } from './entity-action-builders/user-provided-service.action-builders'; import { UserActionBuilders } from './entity-action-builders/user.action-builders'; import { AppStat } from './store/types/app-metadata.types'; +import { CfUser } from './store/types/cf-user.types'; import { GitBranch, GitCommit, GitRepo } from './store/types/git.types'; -import { CfUser } from './store/types/user.types'; /** * A strongly typed collection of Cloud Foundry Catalog Entities. diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts index 0f2d1ebb9e..1112888031 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts @@ -46,7 +46,7 @@ import { userProvidedServiceInstanceEntityType, } from './cf-entity-types'; import { getAPIResourceGuid } from './store/selectors/api.selectors'; -import { CfUser, CfUserRoleParams, OrgUserRoleNames, SpaceUserRoleNames } from './store/types/user.types'; +import { CfUser, CfUserRoleParams, OrgUserRoleNames, SpaceUserRoleNames } from './store/types/cf-user.types'; const entityCache: { [key: string]: EntitySchema diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts index 26a24ca124..beba13c732 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts @@ -118,6 +118,7 @@ import { GitCommitActionBuilders, gitCommitActionBuilders, GitCommitActionBuildersConfig, + GitMeta, GitRepoActionBuilders, gitRepoActionBuilders, } from './entity-action-builders/git-action-builder'; @@ -165,17 +166,19 @@ import { populatePaginationFromParent } from './entity-relations/entity-relation import { isEntityInlineParentAction } from './entity-relations/entity-relations.types'; import { CfEndpointDetailsComponent } from './shared/components/cf-endpoint-details/cf-endpoint-details.component'; import { updateApplicationRoutesReducer } from './store/reducers/application-route.reducer'; +import { cfUserReducer, endpointDisconnectUserReducer, userSpaceOrgReducer } from './store/reducers/cf-users.reducer'; +import { currentCfUserRolesReducer } from './store/reducers/current-cf-user-roles-reducer/current-cf-user-roles.reducer'; import { endpointDisconnectRemoveEntitiesReducer } from './store/reducers/endpoint-disconnect-application.reducer'; import { updateOrganizationQuotaReducer } from './store/reducers/organization-quota.reducer'; import { updateOrganizationSpaceReducer } from './store/reducers/organization-space.reducer'; import { routeReducer, updateAppSummaryRoutesReducer } from './store/reducers/routes.reducer'; import { serviceInstanceReducer } from './store/reducers/service-instance.reducer'; import { updateSpaceQuotaReducer } from './store/reducers/space-quota.reducer'; -import { endpointDisconnectUserReducer, userReducer, userSpaceOrgReducer } from './store/reducers/users.reducer'; import { AppStat } from './store/types/app-metadata.types'; import { CFResponse } from './store/types/cf-api.types'; +import { CfUser } from './store/types/cf-user.types'; import { GitBranch, GitCommit, GitRepo } from './store/types/git.types'; -import { CfUser } from './store/types/user.types'; +import { cfUserRolesFetch } from './user-permissions/cf-user-roles-fetch'; function safePopulatePaginationFromParent(store: Store, action: PaginatedAction): Observable { return populatePaginationFromParent(store, action).pipe( @@ -364,7 +367,9 @@ export function generateCFEntities(): StratosBaseCatalogEntity[] { map(([beValue, userOverride]) => userOverride || beValue || entityTypeDefault) ); }, - } + }, + userRolesFetch: cfUserRolesFetch, + userRolesReducer: currentCfUserRolesReducer }; return [ generateCfEndpointEntity(endpointDefinition), @@ -846,7 +851,7 @@ function generateCFUserEntity(endpointDefinition: StratosEndpointExtensionDefini definition, { actionBuilders: userActionBuilders, - dataReducers: [userReducer, endpointDisconnectUserReducer], + dataReducers: [cfUserReducer, endpointDisconnectUserReducer], entityBuilder: { getMetadata: ent => ({ name: ent.entity.username || ent.entity.guid || ent.metadata.guid, @@ -899,8 +904,9 @@ function generateGitCommitEntity(endpointDefinition: StratosEndpointExtensionDef endpoint: endpointDefinition, nonJetstreamRequest: true, successfulRequestDataMapper: (data, endpointGuid, guid, entityType, endpointType, action) => { + const metadata = (action.metadata as GitMeta[])[0]; return { - ...data, + ...metadata.scm.convertCommit(metadata.projectName, data), guid: action.guid }; }, diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-types.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-types.ts index 91555203d5..4175b2d4e4 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-types.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-types.ts @@ -25,8 +25,8 @@ import { IStack, } from './cf-api.types'; import { AppStats } from './store/types/app-metadata.types'; +import { CfUser } from './store/types/cf-user.types'; import { GitBranch, GitCommit, GitRepo } from './store/types/git.types'; -import { CfUser } from './store/types/user.types'; export const applicationEntityType = 'application'; export const stackEntityType = 'stack'; diff --git a/src/frontend/packages/cloud-foundry/src/cf-favorites-helpers.ts b/src/frontend/packages/cloud-foundry/src/cf-favorites-helpers.ts index 05c628ff38..ec23f4125f 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-favorites-helpers.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-favorites-helpers.ts @@ -5,12 +5,12 @@ import { CfAPIResource } from './store/types/cf-api.types'; export function getFavoriteFromCfEntity( entity, - entityKey: string, + entityType: string, favoritesConfigMapper: FavoritesConfigMapper ): UserFavorite { if (isCfEntity(entity as CfAPIResource)) { return favoritesConfigMapper.getFavoriteFromEntity( - entityKey, + entityType, 'cf', entity.entity.cfGuid, entity diff --git a/src/frontend/packages/cloud-foundry/src/cloud-foundry.module.ts b/src/frontend/packages/cloud-foundry/src/cloud-foundry-package.module.ts similarity index 91% rename from src/frontend/packages/cloud-foundry/src/cloud-foundry.module.ts rename to src/frontend/packages/cloud-foundry/src/cloud-foundry-package.module.ts index 40bc688274..6e088679f4 100644 --- a/src/frontend/packages/cloud-foundry/src/cloud-foundry.module.ts +++ b/src/frontend/packages/cloud-foundry/src/cloud-foundry-package.module.ts @@ -16,6 +16,7 @@ import { LongRunningCfOperationsService } from './shared/data-services/long-runn import { ServiceActionHelperService } from './shared/data-services/service-action-helper.service'; import { CloudFoundryUserProvidedServicesService } from './shared/services/cloud-foundry-user-provided-services.service'; import { CloudFoundryStoreModule } from './store/cloud-foundry.store.module'; +import { cfCurrentUserPermissionsService } from './user-permissions/cf-user-permissions-checkers'; @NgModule({ imports: [ @@ -37,11 +38,12 @@ import { CloudFoundryStoreModule } from './store/cloud-foundry.store.module'; // ]) ], providers: [ + ...cfCurrentUserPermissionsService, CfUserService, CloudFoundryService, ServiceActionHelperService, LongRunningCfOperationsService, - CloudFoundryUserProvidedServicesService + CloudFoundryUserProvidedServicesService, ] }) export class CloudFoundryPackageModule { } diff --git a/src/frontend/packages/cloud-foundry/src/cloud-foundry-test.module.ts b/src/frontend/packages/cloud-foundry/src/cloud-foundry-test.module.ts index ae56fa584e..11769b81a4 100644 --- a/src/frontend/packages/cloud-foundry/src/cloud-foundry-test.module.ts +++ b/src/frontend/packages/cloud-foundry/src/cloud-foundry-test.module.ts @@ -8,7 +8,11 @@ import { getGitHubAPIURL, GITHUB_API_URL } from '../../core/src/core/github.help import { LoggerService } from '../../core/src/core/logger.service'; import { CATALOGUE_ENTITIES, EntityCatalogFeatureModule } from '../../store/src/entity-catalog.module'; import { entityCatalog, TestEntityCatalog } from '../../store/src/entity-catalog/entity-catalog'; +import { testSCFEndpointGuid } from '../../store/testing/public-api'; +import { BaseCfOrgSpaceRouteMock } from '../test-framework/cloud-foundry-endpoint-service.helper'; import { generateCFEntities } from './cf-entity-generator'; +import { ActiveRouteCfOrgSpace } from './features/cloud-foundry/cf-page.types'; +import { CfUserService } from './shared/data-services/cf-user.service'; import { LongRunningCfOperationsService } from './shared/data-services/long-running-cf-op.service'; import { GitSCMService } from './shared/data-services/scm/scm.service'; import { CloudFoundryStoreModule } from './store/cloud-foundry.store.module'; @@ -39,7 +43,12 @@ import { CloudFoundryStoreModule } from './store/cloud-foundry.store.module'; { provide: GITHUB_API_URL, useFactory: getGitHubAPIURL }, GitSCMService, LoggerService, - LongRunningCfOperationsService + LongRunningCfOperationsService, + CfUserService, + { + provide: ActiveRouteCfOrgSpace, + useFactory: () => new BaseCfOrgSpaceRouteMock(testSCFEndpointGuid) + } ] }) export class CloudFoundryTestingModule { } diff --git a/src/frontend/packages/cloud-foundry/src/entity-action-builders/user.action-builders.ts b/src/frontend/packages/cloud-foundry/src/entity-action-builders/user.action-builders.ts index 174a9105a0..7c3ba84717 100644 --- a/src/frontend/packages/cloud-foundry/src/entity-action-builders/user.action-builders.ts +++ b/src/frontend/packages/cloud-foundry/src/entity-action-builders/user.action-builders.ts @@ -1,20 +1,20 @@ import { OrchestratedActionBuilders } from '../../../store/src/entity-catalog/action-orchestrator/action-orchestrator'; import { GetAllOrgUsers } from '../actions/organization.actions'; import { GetAllSpaceUsers } from '../actions/space.actions'; -import { GetAllUsersAsAdmin, GetUser } from '../actions/users.actions'; +import { GetAllCfUsersAsAdmin, GetCfUser } from '../actions/users.actions'; import { CFBasePipelineRequestActionMeta } from '../cf-entity-generator'; export interface UserActionBuilders extends OrchestratedActionBuilders { get: ( guid: string, endpointGuid: string - ) => GetUser; + ) => GetCfUser; // Must be admin user for this to succeed. getMultiple: ( endpointGuid: string, paginationKey: string, { includeRelations, populateMissing }?: CFBasePipelineRequestActionMeta - ) => GetAllUsersAsAdmin; + ) => GetAllCfUsersAsAdmin; getAllInOrganization: ( guid: string, endpointGuid: string, @@ -35,13 +35,13 @@ export const userActionBuilders: UserActionBuilders = { get: ( guid, endpointGuid - ) => new GetUser(endpointGuid, guid), + ) => new GetCfUser(endpointGuid, guid), // Must be admin user for this to succeed. getMultiple: ( endpointGuid: string, paginationKey: string, { includeRelations, populateMissing }: CFBasePipelineRequestActionMeta = {} - ) => new GetAllUsersAsAdmin(endpointGuid, includeRelations, populateMissing, paginationKey), + ) => new GetAllCfUsersAsAdmin(endpointGuid, includeRelations, populateMissing, paginationKey), getAllInOrganization: ( guid: string, endpointGuid: string, diff --git a/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relations-validate.spec.ts b/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relations-validate.spec.ts index ea5cb3eca5..2a0f7a07f7 100644 --- a/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relations-validate.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relations-validate.spec.ts @@ -1,6 +1,6 @@ import { inject, TestBed } from '@angular/core/testing'; import { Store } from '@ngrx/store'; -import { createBasicStoreModule, createEntityStoreState, TestStoreEntity } from '@stratos/store/testing'; +import { createBasicStoreModule, createEntityStoreState, TestStoreEntity } from '@stratosui/store/testing'; import { environment } from '../../../core/src/environments/environment'; import { SetInitialParams } from '../../../store/src/actions/pagination.actions'; diff --git a/src/frontend/packages/cloud-foundry/src/entity-relations/processors/org-space-post-processor.ts b/src/frontend/packages/cloud-foundry/src/entity-relations/processors/org-space-post-processor.ts index 557e5ef776..687e7323be 100644 --- a/src/frontend/packages/cloud-foundry/src/entity-relations/processors/org-space-post-processor.ts +++ b/src/frontend/packages/cloud-foundry/src/entity-relations/processors/org-space-post-processor.ts @@ -14,7 +14,7 @@ import { GetSpace } from '../../actions/space.actions'; import { getCFEntityKey } from '../../cf-entity-helpers'; import { cfUserEntityType, organizationEntityType, spaceEntityType } from '../../cf-entity-types'; import { CF_ENDPOINT_TYPE } from '../../cf-types'; -import { CfUser, CfUserRoleParams, OrgUserRoleNames, SpaceUserRoleNames } from '../../store/types/user.types'; +import { CfUser, CfUserRoleParams, OrgUserRoleNames, SpaceUserRoleNames } from '../../store/types/cf-user.types'; import { createEntityRelationPaginationKey, ValidateEntityResult, diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-instances/app-delete-instances-routes-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-instances/app-delete-instances-routes-list-config.service.ts index 9c2f0d8ea2..91f805f77c 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-instances/app-delete-instances-routes-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-instances/app-delete-instances-routes-list-config.service.ts @@ -9,7 +9,7 @@ import { serviceBindingEntityType } from '../../../../../../cloud-foundry/src/cf import { createEntityRelationPaginationKey, } from '../../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; -import { CurrentUserPermissionsService } from '../../../../../../core/src/core/current-user-permissions.service'; +import { CurrentUserPermissionsService } from '../../../../../../core/src/core/permissions/current-user-permissions.service'; import { RowState } from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; import { ListViewTypes } from '../../../../../../core/src/shared/components/list/list.component.types'; import { endpointSchemaKey } from '../../../../../../store/src/helpers/entity-factory'; diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-routes/app-delete-routes-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-routes/app-delete-routes-list-config.service.ts index e99d7c41c8..f8bc8de0cf 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-routes/app-delete-routes-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-routes/app-delete-routes-list-config.service.ts @@ -4,7 +4,7 @@ import { Store } from '@ngrx/store'; import { Observable, of as observableOf } from 'rxjs'; import { CFAppState } from '../../../../../../cloud-foundry/src/cf-app-state'; -import { CurrentUserPermissionsService } from '../../../../../../core/src/core/current-user-permissions.service'; +import { CurrentUserPermissionsService } from '../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ConfirmationDialogService } from '../../../../../../core/src/shared/components/confirmation-dialog.service'; import { RowState } from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; import { IListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.html b/src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.html index 67660b76fd..34ef360c0f 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.html @@ -1,7 +1,7 @@

Applications

- + diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.spec.ts index bc8d31cb10..e92a929b87 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.spec.ts @@ -5,6 +5,7 @@ import { TabNavService } from '../../../../../core/tab-nav.service'; import { generateCfBaseTestModules } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CfEndpointsMissingComponent } from '../../../shared/components/cf-endpoints-missing/cf-endpoints-missing.component'; import { CloudFoundryService } from '../../../shared/data-services/cloud-foundry.service'; +import { CfUserPermissionDirective } from '../../../shared/directives/cf-user-permission/cf-user-permission.directive'; import { ApplicationWallComponent } from './application-wall.component'; describe('ApplicationWallComponent', () => { @@ -15,7 +16,8 @@ describe('ApplicationWallComponent', () => { TestBed.configureTestingModule({ declarations: [ ApplicationWallComponent, - CfEndpointsMissingComponent + CfEndpointsMissingComponent, + CfUserPermissionDirective ], imports: generateCfBaseTestModules(), providers: [ diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.ts index e1bb9ccd14..c6cfa5d7dc 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.ts @@ -6,12 +6,12 @@ import { map } from 'rxjs/operators'; import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; import { applicationEntityType } from '../../../../../cloud-foundry/src/cf-entity-types'; -import { CurrentUserPermissions } from '../../../../../core/src/core/current-user-permissions.config'; import { ListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; import { CfAppConfigService } from '../../../shared/components/list/list-types/app/cf-app-config.service'; import { CfAppsDataSource } from '../../../shared/components/list/list-types/app/cf-apps-data-source'; import { CfOrgSpaceDataService, initCfOrgSpaceService } from '../../../shared/data-services/cf-org-space-service.service'; import { CloudFoundryService } from '../../../shared/data-services/cloud-foundry.service'; +import { CfCurrentUserPermissions } from '../../../user-permissions/cf-user-permissions-checkers'; @Component({ selector: 'app-application-wall', @@ -52,7 +52,7 @@ export class ApplicationWallComponent implements OnDestroy { this.cfIds$ = cloudFoundryService.cFEndpoints$.pipe( map(endpoints => endpoints.map(endpoint => endpoint.guid)), ); - this.canCreateApplication = CurrentUserPermissions.APPLICATION_CREATE; + this.canCreateApplication = CfCurrentUserPermissions.APPLICATION_CREATE; this.haveConnectedCf$ = cloudFoundryService.connectedCFEndpoints$.pipe( map(endpoints => !!endpoints && endpoints.length > 0) diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-tabs-base.component.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-tabs-base.component.ts index d5b04d1268..ca2194ba6e 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-tabs-base.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-tabs-base.component.ts @@ -6,8 +6,6 @@ import { filter, first, map, startWith, switchMap, withLatestFrom } from 'rxjs/o import { CFAppState } from '../../../../../../cloud-foundry/src/cf-app-state'; import { applicationEntityType } from '../../../../../../cloud-foundry/src/cf-entity-types'; import { IAppFavMetadata } from '../../../../../../cloud-foundry/src/cf-metadata-types'; -import { CurrentUserPermissions } from '../../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../../core/src/core/current-user-permissions.service'; import { EndpointsService } from '../../../../../../core/src/core/endpoints.service'; import { getActionsFromExtensions, @@ -16,6 +14,7 @@ import { StratosActionType, StratosTabType, } from '../../../../../../core/src/core/extension/extension-service'; +import { CurrentUserPermissionsService } from '../../../../../../core/src/core/permissions/current-user-permissions.service'; import { getFavoriteFromEntity } from '../../../../../../core/src/core/user-favorite-helpers'; import { safeUnsubscribe } from '../../../../../../core/src/core/utils.service'; import { IPageSideNavTab } from '../../../../../../core/src/features/dashboard/page-side-nav/page-side-nav.component'; @@ -37,6 +36,7 @@ import { IApp, IOrganization, ISpace } from '../../../../cf-api.types'; import { CF_ENDPOINT_TYPE } from '../../../../cf-types'; import { GitSCMService, GitSCMType } from '../../../../shared/data-services/scm/scm.service'; import { ApplicationStateData } from '../../../../shared/services/application-state.service'; +import { CfCurrentUserPermissions } from '../../../../user-permissions/cf-user-permissions-checkers'; import { ApplicationService } from '../../application.service'; import { ApplicationPollingService } from './application-polling.service'; @@ -91,7 +91,7 @@ export class ApplicationTabsBaseComponent implements OnInit, OnDestroy { ); const appDoesNotHaveEnvVars$ = this.applicationService.appSpace$.pipe( - switchMap(space => this.currentUserPermissionsService.can(CurrentUserPermissions.APPLICATION_VIEW_ENV_VARS, + switchMap(space => this.currentUserPermissionsService.can(CfCurrentUserPermissions.APPLICATION_VIEW_ENV_VARS, this.applicationService.cfGuid, space.metadata.guid) ), map(can => !can) diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/build-tab/build-tab.component.html b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/build-tab/build-tab.component.html index 97501ccc56..a1ceaa6f53 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/build-tab/build-tab.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/build-tab/build-tab.component.html @@ -2,7 +2,7 @@
+ *appCfUserPermission="manageAppPermission;spaceGuid:space.metadata.guid;endpointGuid:applicationService.cfGuid"> - + +
@@ -17,13 +17,17 @@

Organization Commands

- + - + - +
@@ -32,13 +36,17 @@

Space Commands

- + - + - + - + \ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.spec.ts index ec3e649441..25e3004052 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.spec.ts @@ -7,6 +7,7 @@ import { } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CliCommandComponent } from '../../../shared/components/cli-info/cli-command/cli-command.component'; import { CliInfoComponent } from '../../../shared/components/cli-info/cli-info.component'; +import { CfUserPermissionDirective } from '../../../shared/directives/cf-user-permission/cf-user-permission.directive'; import { ApplicationStateService } from '../../../shared/services/application-state.service'; import { CloudFoundryUserProvidedServicesService, @@ -20,7 +21,12 @@ describe('CliInfoCloudFoundryComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [CliInfoCloudFoundryComponent, CliInfoComponent, CliCommandComponent], + declarations: [ + CliInfoCloudFoundryComponent, + CliInfoComponent, + CliCommandComponent, + CfUserPermissionDirective + ], imports: generateCfBaseTestModules(), providers: [ generateTestCfEndpointServiceProvider(), diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.ts index ee4e82af47..82ddcf4295 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.ts @@ -5,8 +5,6 @@ import { first, map } from 'rxjs/operators'; import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; import { CFAppCLIInfoContext } from '../../../../../cloud-foundry/src/shared/components/cli-info/cli-info.component'; -import { CurrentUserPermissionsChecker } from '../../../../../core/src/core/current-user-permissions.checker'; -import { CurrentUserPermissions } from '../../../../../core/src/core/current-user-permissions.config'; import { getFullEndpointApiUrl } from '../../../../../core/src/features/endpoints/endpoint-helpers'; import { IHeaderBreadcrumb } from '../../../../../core/src/shared/components/page-header/page-header.types'; import { RouterNav } from '../../../../../store/src/actions/router.actions'; @@ -17,6 +15,7 @@ import { IOrganization, ISpace } from '../../../cf-api.types'; import { CloudFoundryUserProvidedServicesService, } from '../../../shared/services/cloud-foundry-user-provided-services.service'; +import { CfCurrentUserPermissions, CfUserPermissionsChecker } from '../../../user-permissions/cf-user-permissions-checkers'; import { ActiveRouteCfOrgSpace } from '../cf-page.types'; import { getActiveRouteCfOrgSpaceProvider } from '../cf.helpers'; import { CloudFoundryEndpointService } from '../services/cloud-foundry-endpoint.service'; @@ -38,8 +37,8 @@ import { CloudFoundrySpaceService } from '../services/cloud-foundry-space.servic }) export class CliInfoCloudFoundryComponent implements OnInit { - permsOrgEdit = CurrentUserPermissions.ORGANIZATION_EDIT; - permsSpaceEdit = CurrentUserPermissions.SPACE_EDIT; + permsOrgEdit = CfCurrentUserPermissions.ORGANIZATION_EDIT; + permsSpaceEdit = CfCurrentUserPermissions.SPACE_EDIT; orgGuid: string; spaceGuid: string; @@ -70,7 +69,7 @@ export class CliInfoCloudFoundryComponent implements OnInit { this.breadcrumbs$ = new BehaviorSubject([]); if (activeRouteCfOrgSpace.orgGuid) { this.orgGuid = activeRouteCfOrgSpace.orgGuid; - this.spaceGuid = activeRouteCfOrgSpace.spaceGuid || CurrentUserPermissionsChecker.ALL_SPACES; + this.spaceGuid = activeRouteCfOrgSpace.spaceGuid || CfUserPermissionsChecker.ALL_SPACES; } } diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.spec.ts index 7cec150ad2..726f61014c 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { populateStoreWithTestEndpoint, testSCFEndpointGuid } from '@stratosui/store/testing'; import { TabNavService } from '../../../../../core/tab-nav.service'; -import { populateStoreWithTestEndpoint, testSCFEndpointGuid } from '@stratos/store/testing'; import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.ts index 6d8b45c1eb..57ddd512a7 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.ts @@ -2,8 +2,6 @@ import { Component, OnInit } from '@angular/core'; import { Observable, of as observableOf } from 'rxjs'; import { first, map, startWith } from 'rxjs/operators'; -import { CurrentUserPermissions } from '../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../core/src/core/current-user-permissions.service'; import { EndpointsService } from '../../../../../core/src/core/endpoints.service'; import { getActionsFromExtensions, @@ -12,10 +10,12 @@ import { StratosActionType, StratosTabType, } from '../../../../../core/src/core/extension/extension-service'; +import { CurrentUserPermissionsService } from '../../../../../core/src/core/permissions/current-user-permissions.service'; import { environment } from '../../../../../core/src/environments/environment.prod'; import { IPageSideNavTab } from '../../../../../core/src/features/dashboard/page-side-nav/page-side-nav.component'; import { FavoritesConfigMapper } from '../../../../../core/src/shared/components/favorites-meta-card/favorite-config-mapper'; import { UserFavoriteEndpoint } from '../../../../../store/src/types/user-favorites.types'; +import { CfCurrentUserPermissions } from '../../../user-permissions/cf-user-permissions-checkers'; import { CloudFoundryEndpointService } from '../services/cloud-foundry-endpoint.service'; @Component({ @@ -54,7 +54,7 @@ export class CloudFoundryTabsBaseComponent implements OnInit { ); const firehoseHidden$ = this.currentUserPermissionsService - .can(CurrentUserPermissions.FIREHOSE_VIEW, this.cfEndpointService.cfGuid) + .can(CfCurrentUserPermissions.FIREHOSE_VIEW, this.cfEndpointService.cfGuid) .pipe(map(visible => !visible)); const usersHidden$ = cfEndpointService.usersCount$.pipe( @@ -101,7 +101,7 @@ export class CloudFoundryTabsBaseComponent implements OnInit { ngOnInit() { this.isFetching$ = observableOf(false); - this.canAddOrg$ = this.currentUserPermissionsService.can(CurrentUserPermissions.ORGANIZATION_CREATE, this.cfEndpointService.cfGuid); + this.canAddOrg$ = this.currentUserPermissionsService.can(CfCurrentUserPermissions.ORGANIZATION_CREATE, this.cfEndpointService.cfGuid); } } diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry.module.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry.module.ts index 3e6a816d3e..76e9e15ff3 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry.module.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry.module.ts @@ -227,7 +227,7 @@ import { RemoveUserComponent } from './users/remove-user/remove-user.component'; // CfRolesService, CloudFoundryCellService, UserInviteService, - UserInviteConfigureService + UserInviteConfigureService, ], entryComponents: [ UserInviteConfigurationDialogComponent diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry/cloud-foundry.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry/cloud-foundry.component.spec.ts index 76f124d947..4b8d81f2a0 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry/cloud-foundry.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry/cloud-foundry.component.spec.ts @@ -1,12 +1,12 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { populateStoreWithTestEndpoint } from '@stratosui/store/testing'; -import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; import { TabNavService } from '../../../../../core/tab-nav.service'; +import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; import { generateCfBaseTestModules, generateTestCfServiceProvider, } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { populateStoreWithTestEndpoint } from '@stratos/store/testing'; import { CfEndpointsMissingComponent } from '../../../shared/components/cf-endpoints-missing/cf-endpoints-missing.component'; import { CloudFoundryComponent } from './cloud-foundry.component'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.html b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.html index d6594dbd8e..9dd3dc9c47 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.html @@ -8,10 +8,11 @@ - {{ quota.entity.name }} + + {{ quota.entity.name }} - + -
+
\ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition/quota-definition.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition/quota-definition.component.spec.ts index fb330862e3..4fb96c3435 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition/quota-definition.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition/quota-definition.component.spec.ts @@ -1,8 +1,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; +import { populateStoreWithTestEndpoint, testSCFEndpointGuid } from '@stratosui/store/testing'; import { TabNavService } from '../../../../../core/tab-nav.service'; -import { populateStoreWithTestEndpoint, testSCFEndpointGuid } from '@stratos/store/testing'; import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition/quota-definition.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition/quota-definition.component.ts index 4764ba5876..34d2c00086 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition/quota-definition.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition/quota-definition.component.ts @@ -4,14 +4,14 @@ import { Store } from '@ngrx/store'; import { Observable, of, Subscription } from 'rxjs'; import { filter, first, map, switchMap } from 'rxjs/operators'; -import { CurrentUserPermissions } from '../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../core/src/core/current-user-permissions.service'; +import { CurrentUserPermissionsService } from '../../../../../core/src/core/permissions/current-user-permissions.service'; import { IHeaderBreadcrumb } from '../../../../../core/src/shared/components/page-header/page-header.types'; import { AppState } from '../../../../../store/src/app-state'; import { APIResource } from '../../../../../store/src/types/api.types'; import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; import { IOrganization, IOrgQuotaDefinition, ISpace } from '../../../cf-api.types'; import { cfEntityCatalog } from '../../../cf-entity-catalog'; +import { CfCurrentUserPermissions } from '../../../user-permissions/cf-user-permissions-checkers'; import { ActiveRouteCfOrgSpace } from '../cf-page.types'; import { getActiveRouteCfOrgSpaceProvider } from '../cf.helpers'; import { QuotaDefinitionBaseComponent } from '../quota-definition-base/quota-definition-base.component'; @@ -51,7 +51,7 @@ export class QuotaDefinitionComponent extends QuotaDefinitionBaseComponent { super(store, activeRouteCfOrgSpace, activatedRoute); this.setupQuotaDefinitionObservable(); const { cfGuid, orgGuid } = activeRouteCfOrgSpace; - this.canEditQuota$ = currentUserPermissionsService.can(CurrentUserPermissions.QUOTA_EDIT, cfGuid); + this.canEditQuota$ = currentUserPermissionsService.can(CfCurrentUserPermissions.QUOTA_EDIT, cfGuid); this.isCf = !orgGuid; this.editParams = { [QUOTA_ORG_GUID]: orgGuid }; } diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts index a85683a86f..07848a222f 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts @@ -12,7 +12,6 @@ import { quotaDefinitionEntityType, spaceEntityType, } from '../../../../../cloud-foundry/src/cf-entity-types'; -import { OrgUserRoleNames } from '../../../../../cloud-foundry/src/store/types/user.types'; import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; import { APIResource, EntityInfo } from '../../../../../store/src/types/api.types'; import { @@ -30,6 +29,7 @@ import { CfUserService } from '../../../shared/data-services/cf-user.service'; import { CloudFoundryUserProvidedServicesService, } from '../../../shared/services/cloud-foundry-user-provided-services.service'; +import { OrgUserRoleNames } from '../../../store/types/cf-user.types'; import { fetchServiceInstancesCount } from '../../service-catalog/services-helper'; import { ActiveRouteCfOrgSpace } from '../cf-page.types'; import { getOrgRolesString } from '../cf.helpers'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts index 794c014a7b..f3faadd465 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts @@ -12,7 +12,6 @@ import { spaceEntityType, spaceQuotaEntityType, } from '../../../../../cloud-foundry/src/cf-entity-types'; -import { SpaceUserRoleNames } from '../../../../../cloud-foundry/src/store/types/user.types'; import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; import { APIResource, EntityInfo } from '../../../../../store/src/types/api.types'; import { IApp, IOrgQuotaDefinition, IRoute, ISpace, ISpaceQuotaDefinition } from '../../../cf-api.types'; @@ -23,6 +22,7 @@ import { CfUserService } from '../../../shared/data-services/cf-user.service'; import { CloudFoundryUserProvidedServicesService, } from '../../../shared/services/cloud-foundry-user-provided-services.service'; +import { SpaceUserRoleNames } from '../../../store/types/cf-user.types'; import { fetchServiceInstancesCount } from '../../service-catalog/services-helper'; import { ActiveRouteCfOrgSpace } from '../cf-page.types'; import { getSpaceRolesString } from '../cf.helpers'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition/space-quota-definition.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition/space-quota-definition.component.spec.ts index eb4f5d792b..6ef73eb96f 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition/space-quota-definition.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition/space-quota-definition.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; -import { testSCFEndpoint, testSCFEndpointGuid } from '@stratos/store/testing'; +import { testSCFEndpoint, testSCFEndpointGuid } from '@stratosui/store/testing'; import { endpointEntitySchema } from '../../../../../core/src/base-entity-schemas'; import { TabNavService } from '../../../../../core/tab-nav.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition/space-quota-definition.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition/space-quota-definition.component.ts index 41e3bb4f58..010b22e72c 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition/space-quota-definition.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition/space-quota-definition.component.ts @@ -4,14 +4,14 @@ import { Store } from '@ngrx/store'; import { Observable, of, Subscription } from 'rxjs'; import { filter, first, map, switchMap } from 'rxjs/operators'; -import { CurrentUserPermissions } from '../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../core/src/core/current-user-permissions.service'; +import { CurrentUserPermissionsService } from '../../../../../core/src/core/permissions/current-user-permissions.service'; import { IHeaderBreadcrumb } from '../../../../../core/src/shared/components/page-header/page-header.types'; import { AppState } from '../../../../../store/src/app-state'; import { APIResource } from '../../../../../store/src/types/api.types'; import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; import { IOrganization, ISpace, ISpaceQuotaDefinition } from '../../../cf-api.types'; import { cfEntityCatalog } from '../../../cf-entity-catalog'; +import { CfCurrentUserPermissions } from '../../../user-permissions/cf-user-permissions-checkers'; import { ActiveRouteCfOrgSpace } from '../cf-page.types'; import { getActiveRouteCfOrgSpaceProvider } from '../cf.helpers'; import { QuotaDefinitionBaseComponent } from '../quota-definition-base/quota-definition-base.component'; @@ -49,7 +49,7 @@ export class SpaceQuotaDefinitionComponent extends QuotaDefinitionBaseComponent super(store, activeRouteCfOrgSpace, activatedRoute); this.setupQuotaDefinitionObservable(); const { cfGuid, orgGuid, spaceGuid } = activeRouteCfOrgSpace; - this.canEditQuota$ = currentUserPermissionsService.can(CurrentUserPermissions.SPACE_QUOTA_EDIT, cfGuid, orgGuid); + this.canEditQuota$ = currentUserPermissionsService.can(CfCurrentUserPermissions.SPACE_QUOTA_EDIT, cfGuid, orgGuid); this.isOrg = !spaceGuid; this.editParams = { [QUOTA_SPACE_GUID]: spaceGuid }; } diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.ts index 75984014e2..7cb9cae72d 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.ts @@ -3,7 +3,7 @@ import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { filter, map, switchMap } from 'rxjs/operators'; -import { GetAllUsersAsAdmin } from '../../../../../../cloud-foundry/src/actions/users.actions'; +import { GetAllCfUsersAsAdmin } from '../../../../../../cloud-foundry/src/actions/users.actions'; import { CFAppState } from '../../../../../../cloud-foundry/src/cf-app-state'; import { CfUserService } from '../../../../shared/data-services/cf-user.service'; import { ActiveRouteCfOrgSpace } from '../../cf-page.types'; @@ -36,7 +36,7 @@ export class CfAdminAddUserWarningComponent { activeRouteCfOrgSpace.orgGuid, activeRouteCfOrgSpace.spaceGuid)), map(fetchUsersAction => { - return !GetAllUsersAsAdmin.is(fetchUsersAction); + return !GetAllCfUsersAsAdmin.is(fetchUsersAction); }) ); } diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organization-space-quotas/cloud-foundry-organization-space-quotas.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organization-space-quotas/cloud-foundry-organization-space-quotas.component.ts index 9c814c204c..9a27b50498 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organization-space-quotas/cloud-foundry-organization-space-quotas.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organization-space-quotas/cloud-foundry-organization-space-quotas.component.ts @@ -1,12 +1,12 @@ import { Component } from '@angular/core'; import { Observable } from 'rxjs'; -import { CurrentUserPermissions } from '../../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../../core/src/core/current-user-permissions.service'; +import { CurrentUserPermissionsService } from '../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; import { CfSpaceQuotasListConfigService, } from '../../../../shared/components/list/list-types/cf-space-quotas/cf-space-quotas-list-config.service'; +import { CfCurrentUserPermissions } from '../../../../user-permissions/cf-user-permissions-checkers'; import { ActiveRouteCfOrgSpace } from '../../cf-page.types'; import { CloudFoundryEndpointService } from '../../services/cloud-foundry-endpoint.service'; @@ -30,6 +30,6 @@ export class CloudFoundryOrganizationSpaceQuotasComponent { currentUserPermissionsService: CurrentUserPermissionsService ) { const { cfGuid, orgGuid } = this.activeRouteCfOrgSpace; - this.canAddQuota$ = currentUserPermissionsService.can(CurrentUserPermissions.SPACE_QUOTA_CREATE, cfGuid, orgGuid); + this.canAddQuota$ = currentUserPermissionsService.can(CfCurrentUserPermissions.SPACE_QUOTA_CREATE, cfGuid, orgGuid); } } diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-organization-spaces.component.html b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-organization-spaces.component.html index bc6510ee2a..54a6d00f8e 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-organization-spaces.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-organization-spaces.component.html @@ -1,6 +1,6 @@ diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts index 7153674c1d..e6e27e1e5b 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts @@ -16,6 +16,9 @@ import { CardCfSpaceDetailsComponent, } from '../../../../../../../shared/components/cards/card-cf-space-details/card-cf-space-details.component'; import { CfUserService } from '../../../../../../../shared/data-services/cf-user.service'; +import { + CfUserPermissionDirective, +} from '../../../../../../../shared/directives/cf-user-permission/cf-user-permission.directive'; import { CloudFoundryUserProvidedServicesService, } from '../../../../../../../shared/services/cloud-foundry-user-provided-services.service'; @@ -30,7 +33,13 @@ describe('CloudFoundrySpaceSummaryComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [CloudFoundrySpaceSummaryComponent, CardCfSpaceDetailsComponent, CardCfRecentAppsComponent, CompactAppCardComponent], + declarations: [ + CloudFoundrySpaceSummaryComponent, + CardCfSpaceDetailsComponent, + CardCfRecentAppsComponent, + CompactAppCardComponent, + CfUserPermissionDirective + ], imports: generateCfBaseTestModules(), providers: [ generateActiveRouteCfOrgSpaceMock(), diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.ts index 361689d6b3..5a5bf9eec0 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.ts @@ -4,7 +4,6 @@ import { Store } from '@ngrx/store'; import { combineLatest, Observable } from 'rxjs'; import { filter, first, map, pairwise, startWith, tap } from 'rxjs/operators'; -import { CurrentUserPermissions } from '../../../../../../../../../core/src/core/current-user-permissions.config'; import { ConfirmationDialogConfig } from '../../../../../../../../../core/src/shared/components/confirmation-dialog.config'; import { ConfirmationDialogService, @@ -15,6 +14,7 @@ import { entityCatalog } from '../../../../../../../../../store/src/entity-catal import { selectDeletionInfo } from '../../../../../../../../../store/src/selectors/api.selectors'; import { spaceEntityType } from '../../../../../../../cf-entity-types'; import { CF_ENDPOINT_TYPE } from '../../../../../../../cf-types'; +import { CfCurrentUserPermissions } from '../../../../../../../user-permissions/cf-user-permissions-checkers'; import { CloudFoundryEndpointService } from '../../../../../services/cloud-foundry-endpoint.service'; import { CloudFoundryOrganizationService } from '../../../../../services/cloud-foundry-organization.service'; import { CloudFoundrySpaceService } from '../../../../../services/cloud-foundry-space.service'; @@ -27,8 +27,8 @@ import { CloudFoundrySpaceService } from '../../../../../services/cloud-foundry- export class CloudFoundrySpaceSummaryComponent { detailsLoading$: Observable; name$: Observable; - public permsSpaceEdit = CurrentUserPermissions.SPACE_EDIT; - public permsSpaceDelete = CurrentUserPermissions.SPACE_DELETE; + public permsSpaceEdit = CfCurrentUserPermissions.SPACE_EDIT; + public permsSpaceDelete = CfCurrentUserPermissions.SPACE_DELETE; constructor( public cfEndpointService: CloudFoundryEndpointService, diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.ts index be30449c73..3b82a9e0e0 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.ts @@ -1,8 +1,7 @@ import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import { CFAppState } from 'frontend/packages/cloud-foundry/src/cf-app-state'; -import { CurrentUserPermissions } from 'frontend/packages/core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from 'frontend/packages/core/src/core/current-user-permissions.service'; +import { CurrentUserPermissionsService } from 'frontend/packages/core/src/core/permissions/current-user-permissions.service'; import { combineLatest, Observable } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; @@ -11,6 +10,7 @@ import { CFFeatureFlagTypes } from '../../../../../../../cf-api.types'; import { CfSpaceUsersListConfigService, } from '../../../../../../../shared/components/list/list-types/cf-space-users/cf-space-users-list-config.service'; +import { CfCurrentUserPermissions } from '../../../../../../../user-permissions/cf-user-permissions-checkers'; import { ActiveRouteCfOrgSpace } from '../../../../../cf-page.types'; import { createCfOrgSpaceSteppersUrl, someFeatureFlags, waitForCFPermissions } from '../../../../../cf.helpers'; @@ -42,7 +42,7 @@ export class CloudFoundrySpaceUsersComponent { switchMap(() => combineLatest([ someFeatureFlags(requiredFeatureFlags, activeRouteCfOrgSpace.cfGuid, store, userPerms), userPerms.can( - CurrentUserPermissions.SPACE_CHANGE_ROLES, + CfCurrentUserPermissions.SPACE_CHANGE_ROLES, activeRouteCfOrgSpace.cfGuid, activeRouteCfOrgSpace.orgGuid, activeRouteCfOrgSpace.spaceGuid diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.html b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.html index aca6cf5016..c1565ef97b 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.html @@ -1,11 +1,11 @@ - diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.spec.ts index 86b4a72c57..933c783b42 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.spec.ts @@ -17,6 +17,7 @@ import { import { CompactAppCardComponent, } from '../../../../../shared/components/cards/card-cf-recent-apps/compact-app-card/compact-app-card.component'; +import { CfUserPermissionDirective } from '../../../../../shared/directives/cf-user-permission/cf-user-permission.directive'; import { CloudFoundryOrganizationService } from '../../../services/cloud-foundry-organization.service'; import { CloudFoundryOrganizationSummaryComponent } from './cloud-foundry-organization-summary.component'; @@ -30,7 +31,8 @@ describe('CloudFoundryOrganizationSummaryComponent', () => { CloudFoundryOrganizationSummaryComponent, CardCfOrgUserDetailsComponent, CardCfRecentAppsComponent, - CompactAppCardComponent + CompactAppCardComponent, + CfUserPermissionDirective ], imports: generateCfBaseTestModules(), providers: [ diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.ts index 2b3a9cf8f9..163a261a4a 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.ts @@ -5,14 +5,14 @@ import { combineLatest, Observable } from 'rxjs'; import { filter, first, map, pairwise, startWith, tap } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; -import { CurrentUserPermissions } from '../../../../../../../core/src/core/current-user-permissions.config'; import { ConfirmationDialogConfig } from '../../../../../../../core/src/shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from '../../../../../../../core/src/shared/components/confirmation-dialog.service'; -import { entityCatalog } from '../../../../../../../store/src/entity-catalog/entity-catalog'; import { RouterNav } from '../../../../../../../store/src/actions/router.actions'; +import { entityCatalog } from '../../../../../../../store/src/entity-catalog/entity-catalog'; import { selectDeletionInfo } from '../../../../../../../store/src/selectors/api.selectors'; import { organizationEntityType } from '../../../../../cf-entity-types'; import { CF_ENDPOINT_TYPE } from '../../../../../cf-types'; +import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { goToAppWall } from '../../../cf.helpers'; import { CloudFoundryEndpointService } from '../../../services/cloud-foundry-endpoint.service'; import { CloudFoundryOrganizationService } from '../../../services/cloud-foundry-organization.service'; @@ -26,8 +26,8 @@ import { CloudFoundryOrganizationService } from '../../../services/cloud-foundry export class CloudFoundryOrganizationSummaryComponent { appLink: () => void; detailsLoading$: Observable; - public permsOrgEdit = CurrentUserPermissions.ORGANIZATION_EDIT; - public permsOrgDelete = CurrentUserPermissions.ORGANIZATION_DELETE; + public permsOrgEdit = CfCurrentUserPermissions.ORGANIZATION_EDIT; + public permsOrgDelete = CfCurrentUserPermissions.ORGANIZATION_DELETE; constructor( private store: Store, diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users/cloud-foundry-organization-users.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users/cloud-foundry-organization-users.component.ts index df764482bc..cc902d28b7 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users/cloud-foundry-organization-users.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users/cloud-foundry-organization-users.component.ts @@ -3,14 +3,16 @@ import { Store } from '@ngrx/store'; import { combineLatest, Observable } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; -import { CurrentUserPermissions } from '../../../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../../../core/src/core/current-user-permissions.service'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ListConfig } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { CFFeatureFlagTypes } from '../../../../../cf-api.types'; import { CFAppState } from '../../../../../cf-app-state'; import { CfOrgUsersListConfigService, } from '../../../../../shared/components/list/list-types/cf-org-users/cf-org-users-list-config.service'; +import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { ActiveRouteCfOrgSpace } from '../../../cf-page.types'; import { createCfOrgSpaceSteppersUrl, someFeatureFlags, waitForCFPermissions } from '../../../cf.helpers'; @@ -42,7 +44,7 @@ export class CloudFoundryOrganizationUsersComponent { this.addRolesByUsernameLink$ = waitForCFPermissions(store, activeRouteCfOrgSpace.cfGuid).pipe( switchMap(() => combineLatest([ someFeatureFlags(requiredFeatureFlags, activeRouteCfOrgSpace.cfGuid, store, userPerms), - userPerms.can(CurrentUserPermissions.ORGANIZATION_CHANGE_ROLES, activeRouteCfOrgSpace.cfGuid, activeRouteCfOrgSpace.orgGuid) + userPerms.can(CfCurrentUserPermissions.ORGANIZATION_CHANGE_ROLES, activeRouteCfOrgSpace.cfGuid, activeRouteCfOrgSpace.orgGuid) ])), map(([canSetRolesByUsername, canChangeOrgRole]) => { if (canSetRolesByUsername && canChangeOrgRole) { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organizations.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organizations.component.ts index 035bfbf51d..479d1c9618 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organizations.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organizations.component.ts @@ -1,11 +1,11 @@ import { Component } from '@angular/core'; import { Observable } from 'rxjs'; -import { CurrentUserPermissions } from '../../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../../core/src/core/current-user-permissions.service'; +import { CurrentUserPermissionsService } from '../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; import { CfOrgCardComponent } from '../../../../shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component'; import { CfOrgsListConfigService } from '../../../../shared/components/list/list-types/cf-orgs/cf-orgs-list-config.service'; +import { CfCurrentUserPermissions } from '../../../../user-permissions/cf-user-permissions-checkers'; import { CloudFoundryEndpointService } from '../../services/cloud-foundry-endpoint.service'; @Component({ @@ -25,7 +25,7 @@ export class CloudFoundryOrganizationsComponent { public cfEndpointService: CloudFoundryEndpointService, currentUserPermissionsService: CurrentUserPermissionsService ) { - this.canAddOrg$ = currentUserPermissionsService.can(CurrentUserPermissions.ORGANIZATION_CREATE, this.cfEndpointService.cfGuid); + this.canAddOrg$ = currentUserPermissionsService.can(CfCurrentUserPermissions.ORGANIZATION_CREATE, this.cfEndpointService.cfGuid); } cardComponent = CfOrgCardComponent; } diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-quotas/cloud-foundry-quotas.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-quotas/cloud-foundry-quotas.component.ts index 566ea33df0..12b9daccf0 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-quotas/cloud-foundry-quotas.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-quotas/cloud-foundry-quotas.component.ts @@ -1,12 +1,12 @@ import { Component } from '@angular/core'; import { Observable } from 'rxjs'; -import { CurrentUserPermissions } from '../../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../../core/src/core/current-user-permissions.service'; +import { CurrentUserPermissionsService } from '../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; import { CfQuotasListConfigService, } from '../../../../shared/components/list/list-types/cf-quotas/cf-quotas-list-config.service'; +import { CfCurrentUserPermissions } from '../../../../user-permissions/cf-user-permissions-checkers'; import { CloudFoundryEndpointService } from '../../services/cloud-foundry-endpoint.service'; @Component({ @@ -27,6 +27,6 @@ export class CloudFoundryQuotasComponent { public cfEndpointService: CloudFoundryEndpointService, currentUserPermissionsService: CurrentUserPermissionsService ) { - this.canAddQuota$ = currentUserPermissionsService.can(CurrentUserPermissions.QUOTA_CREATE, this.cfEndpointService.cfGuid); + this.canAddQuota$ = currentUserPermissionsService.can(CfCurrentUserPermissions.QUOTA_CREATE, this.cfEndpointService.cfGuid); } } diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-users/cloud-foundry-users.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-users/cloud-foundry-users.component.ts index f990478288..f184bf4ec8 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-users/cloud-foundry-users.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-users/cloud-foundry-users.component.ts @@ -3,7 +3,7 @@ import { Router } from '@angular/router'; import { Store } from '@ngrx/store'; import { CFAppState } from '../../../../../../cloud-foundry/src/cf-app-state'; -import { CurrentUserPermissionsService } from '../../../../../../core/src/core/current-user-permissions.service'; +import { CurrentUserPermissionsService } from '../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; import { CfUserService } from '../../../../shared/data-services/cf-user.service'; import { ActiveRouteCfOrgSpace } from '../../cf-page.types'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/user-invite.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/user-invite.service.ts index 676e0cb299..755128bd1b 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/user-invite.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/user-invite.service.ts @@ -6,12 +6,12 @@ import { combineLatest, Observable, of as observableOf } from 'rxjs'; import { catchError, filter, map, switchMap } from 'rxjs/operators'; import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; -import { CurrentUserPermissions } from '../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../core/src/core/current-user-permissions.service'; +import { CurrentUserPermissionsService } from '../../../../../core/src/core/permissions/current-user-permissions.service'; import { environment } from '../../../../../core/src/environments/environment.prod'; import { ConfirmationDialogConfig } from '../../../../../core/src/shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from '../../../../../core/src/shared/components/confirmation-dialog.service'; import { GetSystemInfo } from '../../../../../store/src/actions/system.actions'; +import { CfCurrentUserPermissions } from '../../../user-permissions/cf-user-permissions-checkers'; import { ActiveRouteCfOrgSpace } from '../cf-page.types'; import { waitForCFPermissions } from '../cf.helpers'; import { CloudFoundryEndpointService } from '../services/cloud-foundry-endpoint.service'; @@ -162,7 +162,7 @@ export class UserInviteService { switchMap(() => combineLatest( this.configured$, this.currentUserPermissionsService.can( - CurrentUserPermissions.ORGANIZATION_CHANGE_ROLES, + CfCurrentUserPermissions.ORGANIZATION_CHANGE_ROLES, cfGuid, orgGuid, spaceGuid diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users-create/invite-users-create.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users-create/invite-users-create.component.ts index 00b15b39e3..c6c5044e00 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users-create/invite-users-create.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users-create/invite-users-create.component.ts @@ -18,7 +18,7 @@ import { CFAppState } from '../../../../../cf-app-state'; import { cfEntityCatalog } from '../../../../../cf-entity-catalog'; import { cfUserEntityType } from '../../../../../cf-entity-types'; import { CFEntityConfig } from '../../../../../cf-types'; -import { SpaceUserRoleNames } from '../../../../../store/types/user.types'; +import { SpaceUserRoleNames } from '../../../../../store/types/cf-user.types'; import { UserRoleLabels } from '../../../../../store/types/users-roles.types'; import { ActiveRouteCfOrgSpace } from '../../../cf-page.types'; import { UserInviteSendSpaceRoles, UserInviteService } from '../../../user-invites/user-invite.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.ts index 37cb95f700..56a88ed334 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.ts @@ -17,14 +17,8 @@ import { createEntityRelationKey, createEntityRelationPaginationKey, } from '../../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; -import { CurrentUserPermissionsChecker } from '../../../../../../core/src/core/current-user-permissions.checker'; -import { CurrentUserPermissionsService } from '../../../../../../core/src/core/current-user-permissions.service'; +import { CurrentUserPermissionsService } from '../../../../../../core/src/core/permissions/current-user-permissions.service'; import { endpointSchemaKey } from '../../../../../../store/src/helpers/entity-factory'; -import { - selectUsersRolesCf, - selectUsersRolesPicked, - selectUsersRolesRoles, -} from '../../../../../../store/src/selectors/users-roles.selector'; import { APIResource, EntityInfo } from '../../../../../../store/src/types/api.types'; import { UsersRolesSetChanges } from '../../../../actions/users-roles.actions'; import { IOrganization, ISpace } from '../../../../cf-api.types'; @@ -32,9 +26,15 @@ import { CFAppState } from '../../../../cf-app-state'; import { cfEntityCatalog } from '../../../../cf-entity-catalog'; import { organizationEntityType, spaceEntityType } from '../../../../cf-entity-types'; import { CfUserService } from '../../../../shared/data-services/cf-user.service'; -import { createDefaultOrgRoles, createDefaultSpaceRoles } from '../../../../store/reducers/users-roles.reducer'; -import { CfUser, IUserPermissionInOrg, UserRoleInOrg, UserRoleInSpace } from '../../../../store/types/user.types'; +import { createDefaultOrgRoles, createDefaultSpaceRoles } from '../../../../store/reducers/cf-users-roles.reducer'; +import { + selectCfUsersRolesCf, + selectCfUsersRolesPicked, + selectCfUsersRolesRoles, +} from '../../../../store/selectors/cf-users-roles.selector'; +import { CfUser, IUserPermissionInOrg, UserRoleInOrg, UserRoleInSpace } from '../../../../store/types/cf-user.types'; import { CfRoleChange, CfUserRolesSelected } from '../../../../store/types/users-roles.types'; +import { CfUserPermissionsChecker } from '../../../../user-permissions/cf-user-permissions-checkers'; import { canUpdateOrgSpaceRoles } from '../../cf.helpers'; @Injectable() @@ -63,7 +63,7 @@ export class CfRolesService { orgOrSpace.metadata.guid, orgOrSpace.entity.cfGuid, isOrg ? orgOrSpace.metadata.guid : (orgOrSpace as APIResource).entity.organization_guid, - isOrg ? CurrentUserPermissionsChecker.ALL_SPACES : orgOrSpace.metadata.guid, + isOrg ? CfUserPermissionsChecker.ALL_SPACES : orgOrSpace.metadata.guid, )))); }), // Filter out orgs than the current user cannot edit @@ -91,15 +91,15 @@ export class CfRolesService { private cfUserService: CfUserService, private userPerms: CurrentUserPermissionsService, ) { - this.existingRoles$ = this.store.select(selectUsersRolesPicked).pipe( - combineLatestOperators(this.store.select(selectUsersRolesCf)), + this.existingRoles$ = this.store.select(selectCfUsersRolesPicked).pipe( + combineLatestOperators(this.store.select(selectCfUsersRolesCf)), filter(([users, cfGuid]) => !!cfGuid), switchMap(([users, cfGuid]) => this.populateRoles(cfGuid, users)), distinctUntilChanged(), publishReplay(1), refCount() ); - this.newRoles$ = this.store.select(selectUsersRolesRoles).pipe( + this.newRoles$ = this.store.select(selectCfUsersRolesRoles).pipe( distinctUntilChanged(), publishReplay(1), refCount() @@ -169,7 +169,7 @@ export class CfRolesService { return this.existingRoles$.pipe( combineLatestOperators( this.newRoles$, - this.store.select(selectUsersRolesPicked), + this.store.select(selectCfUsersRolesPicked), ), first(), map(([existingRoles, newRoles, pickedUsers]) => { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-confirm/manage-users-confirm.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-confirm/manage-users-confirm.component.ts index c3360df331..969da7b695 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-confirm/manage-users-confirm.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-confirm/manage-users-confirm.component.ts @@ -11,13 +11,9 @@ import { } from '../../../../../../../core/src/shared/components/list/list-table/table-cell-request-monitor-icon/table-cell-request-monitor-icon.component'; import { ITableColumn } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; import { entityCatalog } from '../../../../../../../store/src/entity-catalog/entity-catalog'; -import { - selectUsersRoles, - selectUsersRolesChangedRoles, -} from '../../../../../../../store/src/selectors/users-roles.selector'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { UsersRolesClearUpdateState } from '../../../../../actions/users-roles.actions'; -import { ChangeUserRole } from '../../../../../actions/users.actions'; +import { ChangeCfUserRole } from '../../../../../actions/users.actions'; import { CFAppState } from '../../../../../cf-app-state'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; import { cfUserEntityType, organizationEntityType, spaceEntityType } from '../../../../../cf-entity-types'; @@ -29,7 +25,8 @@ import { TableCellConfirmRoleAddRemComponent, } from '../../../../../shared/components/list/list-types/cf-confirm-roles/table-cell-confirm-role-add-rem/table-cell-confirm-role-add-rem.component'; import { CfUserService } from '../../../../../shared/data-services/cf-user.service'; -import { CfUser, OrgUserRoleNames, SpaceUserRoleNames } from '../../../../../store/types/user.types'; +import { selectCfUsersRoles, selectCfUsersRolesChangedRoles } from '../../../../../store/selectors/cf-users-roles.selector'; +import { CfUser, OrgUserRoleNames, SpaceUserRoleNames } from '../../../../../store/types/cf-user.types'; import { CfRoleChangeWithNames, UserRoleLabels } from '../../../../../store/types/users-roles.types'; import { ManageUsersSetUsernamesHelper } from '../manage-users-set-usernames/manage-users-set-usernames.component'; @@ -102,7 +99,7 @@ export class UsersRolesConfirmComponent implements OnInit, AfterContentInit { entityKey: entityCatalog.getEntityKey(schema), schema, monitorState: AppMonitorComponentTypes.UPDATE, - updateKey: ChangeUserRole.generateUpdatingKey(row.role, row.userGuid), + updateKey: ChangeCfUserRole.generateUpdatingKey(row.role, row.userGuid), getId: () => guid }; } @@ -129,7 +126,7 @@ export class UsersRolesConfirmComponent implements OnInit, AfterContentInit { // Kick off an update this.updateChanges.next(new Date().getTime()); // Ensure that any entity we're going to show the state for is clear of any previous or unrelated errors - this.store.select(selectUsersRoles).pipe( + this.store.select(selectCfUsersRoles).pipe( first(), ).subscribe(usersRoles => this.store.dispatch(new UsersRolesClearUpdateState(usersRoles.changedRoles))); } @@ -149,7 +146,7 @@ export class UsersRolesConfirmComponent implements OnInit, AfterContentInit { } private createCfObs() { - this.cfGuid$ = this.store.select(selectUsersRoles).pipe( + this.cfGuid$ = this.store.select(selectCfUsersRoles).pipe( map(mu => mu.cfGuid), filter(cfGuid => !!cfGuid), distinctUntilChanged(), @@ -158,7 +155,7 @@ export class UsersRolesConfirmComponent implements OnInit, AfterContentInit { private createChangesObs() { const changesViaUsername = this.updateChanges.pipe( - switchMap(() => this.store.select(selectUsersRolesChangedRoles)), + switchMap(() => this.store.select(selectCfUsersRolesChangedRoles)), map(changes => changes .map(change => ({ ...change, @@ -171,7 +168,7 @@ export class UsersRolesConfirmComponent implements OnInit, AfterContentInit { const changesViaUserGuid = this.updateChanges.pipe( withLatestFrom(this.cfGuid$), mergeMap(([, cfGuid]) => this.cfUserService.getUsers(cfGuid)), - withLatestFrom(this.store.select(selectUsersRolesChangedRoles)), + withLatestFrom(this.store.select(selectCfUsersRolesChangedRoles)), map(([users, changes]) => changes .map(change => ({ diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/manage-users-modify.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/manage-users-modify.component.ts index 4a03a0182b..7782611d97 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/manage-users-modify.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/manage-users-modify.component.ts @@ -12,7 +12,7 @@ import { } from '@angular/core'; import { MatSnackBar, MatSnackBarRef, SimpleSnackBar } from '@angular/material/snack-bar'; import { Store } from '@ngrx/store'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { BehaviorSubject, combineLatest, Observable, of as observableOf, Subscription } from 'rxjs'; import { catchError, @@ -32,13 +32,6 @@ import { ITableListDataSource, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; import { ITableColumn } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; -import { - selectUsersIsRemove, - selectUsersIsSetByUsername, - selectUsersRolesOrgGuid, - selectUsersRolesPicked, - selectUsersRolesRoles, -} from '../../../../../../../store/src/selectors/users-roles.selector'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { UsersRolesFlipSetRoles, UsersRolesSetOrg } from '../../../../../actions/users-roles.actions'; import { IOrganization } from '../../../../../cf-api.types'; @@ -49,7 +42,14 @@ import { import { TableCellSelectOrgComponent, } from '../../../../../shared/components/list/list-types/cf-users-org-space-roles/table-cell-select-org/table-cell-select-org.component'; -import { CfUser, OrgUserRoleNames } from '../../../../../store/types/user.types'; +import { + selectCfUsersIsRemove, + selectCfUsersIsSetByUsername, + selectCfUsersRolesOrgGuid, + selectCfUsersRolesPicked, + selectCfUsersRolesRoles, +} from '../../../../../store/selectors/cf-users-roles.selector'; +import { CfUser, OrgUserRoleNames } from '../../../../../store/types/cf-user.types'; import { ActiveRouteCfOrgSpace } from '../../../cf-page.types'; import { CfRolesService } from '../cf-roles.service'; import { SpaceRolesListWrapperComponent } from './space-roles-list-wrapper/space-roles-list-wrapper.component'; @@ -154,7 +154,7 @@ export class UsersRolesModifyComponent implements OnInit, OnDestroy { this.cfRolesService.loading$.subscribe(loading => this.blocked.next(loading)); } - const orgEntity$ = this.store.select(selectUsersRolesOrgGuid).pipe( + const orgEntity$ = this.store.select(selectCfUsersRolesOrgGuid).pipe( startWith(''), distinctUntilChanged(), filter(orgGuid => !!orgGuid), @@ -197,7 +197,7 @@ export class UsersRolesModifyComponent implements OnInit, OnDestroy { }); } - const users$: Observable = this.store.select(selectUsersRolesPicked).pipe( + const users$: Observable = this.store.select(selectCfUsersRolesPicked).pipe( filter(users => !!users), distinctUntilChanged(), map(users => users.map(this.mapUser.bind(this))) @@ -211,14 +211,14 @@ export class UsersRolesModifyComponent implements OnInit, OnDestroy { map(users => users.filter(user => !!user.showWarning).map(user => user.username)) ); - this.valid$ = this.store.select(selectUsersRolesRoles).pipe( + this.valid$ = this.store.select(selectCfUsersRolesRoles).pipe( debounceTime(150), switchMap(newRoles => this.cfRolesService.createRolesDiff(newRoles.orgGuid)), map(changes => !!changes.length) ); - this.isSetByUsername$ = this.store.select(selectUsersIsSetByUsername); - this.isRemove$ = this.store.select(selectUsersIsRemove); + this.isSetByUsername$ = this.store.select(selectCfUsersIsSetByUsername); + this.isRemove$ = this.store.select(selectCfUsersIsRemove); } private mapUser(user: CfUser): CfUserWithWarning { @@ -263,7 +263,7 @@ export class UsersRolesModifyComponent implements OnInit, OnDestroy { } // When the state is ready (org guid is correct), recreate the space roles table for the selected org - this.store.select(selectUsersRolesRoles).pipe( + this.store.select(selectCfUsersRolesRoles).pipe( // Wait for the store to have the correct org filter(newRoles => newRoles && newRoles.orgGuid === orgGuid), first() @@ -289,7 +289,7 @@ export class UsersRolesModifyComponent implements OnInit, OnDestroy { } // In order to show the removed roles correctly (as ticks) flip them from remove to add - this.store.select(selectUsersIsRemove).pipe(first()).subscribe(isRemove => { + this.store.select(selectCfUsersIsRemove).pipe(first()).subscribe(isRemove => { if (isRemove) { this.store.dispatch(new UsersRolesFlipSetRoles()); } @@ -305,10 +305,10 @@ export class UsersRolesModifyComponent implements OnInit, OnDestroy { onNext = () => { return combineLatest([ - this.store.select(selectUsersIsRemove).pipe(first()), + this.store.select(selectCfUsersIsRemove).pipe(first()), this.cfRolesService.createRolesDiff(this.selectedOrgGuid) ]).pipe( - map(([isRemove,]) => { + map(([isRemove, ]) => { if (isRemove) { // If we're going to eventually remove the roles flip the add to remove this.store.dispatch(new UsersRolesFlipSetRoles()); diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.ts index f540626ace..69481c04be 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.ts @@ -1,7 +1,9 @@ import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; -import { CurrentUserPermissionsService } from '../../../../../../../../core/src/core/current-user-permissions.service'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ListConfig } from '../../../../../../../../core/src/shared/components/list/list.component.types'; import { CFAppState } from '../../../../../../cf-app-state'; import { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-select/manage-users-select.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-select/manage-users-select.component.ts index 03f2b5ad32..524836b8ee 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-select/manage-users-select.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-select/manage-users-select.component.ts @@ -13,7 +13,7 @@ import { CfSelectUsersListConfigService, } from '../../../../../shared/components/list/list-types/cf-select-users/cf-select-users-list-config.service'; import { CfUserService } from '../../../../../shared/data-services/cf-user.service'; -import { CfUser } from '../../../../../store/types/user.types'; +import { CfUser } from '../../../../../store/types/cf-user.types'; import { ActiveRouteCfOrgSpace } from '../../../cf-page.types'; import { CfRolesService } from '../cf-roles.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.html b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.html index c1d88ad0d8..6d2ce160a2 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.html @@ -1,10 +1,10 @@
- - + + Add Roles{{(canAdd$ | async) ? '' : ' (Disabled via feature flag)'}} - + Remove Roles{{(canRemove$ | async) ? '' : ' (Disabled via feature flag)'}}
diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.ts index db0ede2260..17365fc60e 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.ts @@ -2,10 +2,12 @@ import { Component, OnInit } from '@angular/core'; import { MatRadioChange } from '@angular/material/radio'; import { Store } from '@ngrx/store'; import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs'; -import { first, map, publishReplay, refCount, startWith, switchMap, take, tap } from 'rxjs/operators'; +import { first, map, publishReplay, refCount, startWith, switchMap } from 'rxjs/operators'; -import { PermissionConfig, PermissionTypes } from '../../../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../../../core/src/core/current-user-permissions.service'; +import { PermissionConfig } from '../../../../../../../core/src/core/permissions/current-user-permissions.config'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { StackedInputActionConfig, } from '../../../../../../../core/src/shared/components/stacked-input-actions/stacked-input-action/stacked-input-action.component'; @@ -21,7 +23,8 @@ import { } from '../../../../../actions/users-roles.actions'; import { CFFeatureFlagTypes } from '../../../../../cf-api.types'; import { CFAppState } from '../../../../../cf-app-state'; -import { CfUser } from '../../../../../store/types/user.types'; +import { CfUser } from '../../../../../store/types/cf-user.types'; +import { CfPermissionTypes } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { ActiveRouteCfOrgSpace } from '../../../cf-page.types'; import { waitForCFPermissions } from '../../../cf.helpers'; @@ -50,6 +53,7 @@ export class ManageUsersSetUsernamesComponent implements OnInit { public canAdd$: Observable; public canRemove$: Observable; public blocked$: Observable; + public currentValue: boolean; public stackedActionConfig: StackedInputActionConfig = { isEmailInput: false, @@ -67,41 +71,33 @@ export class ManageUsersSetUsernamesComponent implements OnInit { private activeRouteCfOrgSpace: ActiveRouteCfOrgSpace, userPerms: CurrentUserPermissionsService, ) { - const ffSetPermConfig = new PermissionConfig(PermissionTypes.FEATURE_FLAG, CFFeatureFlagTypes.set_roles_by_username); - const ffRemovePermConfig = new PermissionConfig(PermissionTypes.FEATURE_FLAG, CFFeatureFlagTypes.unset_roles_by_username); + const ffSetPermConfig = new PermissionConfig(CfPermissionTypes.FEATURE_FLAG, CFFeatureFlagTypes.set_roles_by_username); + const ffRemovePermConfig = new PermissionConfig(CfPermissionTypes.FEATURE_FLAG, CFFeatureFlagTypes.unset_roles_by_username); this.canAdd$ = waitForCFPermissions(store, activeRouteCfOrgSpace.cfGuid).pipe( switchMap(() => userPerms.can(ffSetPermConfig, activeRouteCfOrgSpace.cfGuid)), - tap(canAdd => { - if (!canAdd) { - this.setIsRemove({ source: null, value: false }); - } - }), first(), publishReplay(1), refCount() ); this.canRemove$ = waitForCFPermissions(store, activeRouteCfOrgSpace.cfGuid).pipe( switchMap(() => userPerms.can(ffRemovePermConfig, activeRouteCfOrgSpace.cfGuid)), - tap(canRemove => { - if (!canRemove) { - this.setIsRemove({ source: null, value: true }); - } - }), first(), publishReplay(1), refCount() ); - this.blocked$ = combineLatest([this.canAdd$, this.canRemove$]).pipe( - map(([canAdd, canRemove]) => { - if (canAdd && canRemove) { - // Set initial value to be add - this.setIsRemove({ source: null, value: true }); - } - return false; - }), + const canAddRemove = combineLatest([this.canAdd$, this.canRemove$]); + + // Set starting value of add/remove radio button + canAddRemove.pipe(first()).subscribe(([canAdd]) => this.setIsRemove({ source: null, value: !canAdd })) + + // Block content until we know the add/remove state + this.blocked$ = canAddRemove.pipe( + map(() => false), + first(), startWith(true), - take(2) + publishReplay(1), + refCount(), ); } @@ -125,8 +121,8 @@ export class ManageUsersSetUsernamesComponent implements OnInit { } setIsRemove(event: MatRadioChange) { - // Note - event.value is flipped - this.store.dispatch(new UsersRolesSetIsRemove(!event.value)); + this.store.dispatch(new UsersRolesSetIsRemove(event.value)); + this.currentValue = event.value; } onNext: StepOnNextFunction = () => { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users.component.ts index 3470759dcb..9eb662d103 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users.component.ts @@ -5,11 +5,11 @@ import { Observable, of as observableOf, of } from 'rxjs'; import { combineLatest, filter, first, map } from 'rxjs/operators'; import { StepOnNextFunction } from '../../../../../../core/src/shared/components/stepper/step/step.component'; -import { selectUsersRoles, selectUsersRolesPicked } from '../../../../../../store/src/selectors/users-roles.selector'; import { UsersRolesClear, UsersRolesExecuteChanges, UsersRolesSetUsers } from '../../../../actions/users-roles.actions'; import { CFAppState } from '../../../../cf-app-state'; import { CfUserService } from '../../../../shared/data-services/cf-user.service'; -import { CfUser } from '../../../../store/types/user.types'; +import { selectCfUsersRoles, selectCfUsersRolesPicked } from '../../../../store/selectors/cf-users-roles.selector'; +import { CfUser } from '../../../../store/types/cf-user.types'; import { ActiveRouteCfOrgSpace } from '../../cf-page.types'; import { getActiveRouteCfOrgSpaceProvider } from '../../cf.helpers'; import { CfRolesService } from './cf-roles.service'; @@ -55,7 +55,7 @@ export class UsersRolesComponent implements OnDestroy { first() ); } else { - this.initialUsers$ = this.store.select(selectUsersRolesPicked).pipe(first()); + this.initialUsers$ = this.store.select(selectCfUsersRolesPicked).pipe(first()); } this.singleUser$ = this.initialUsers$.pipe( @@ -65,7 +65,7 @@ export class UsersRolesComponent implements OnDestroy { ); // Ensure that when we arrive here directly the store is set up with all it needs - this.store.select(selectUsersRoles).pipe( + this.store.select(selectCfUsersRoles).pipe( combineLatest(this.initialUsers$), first() ).subscribe(([usersRoles, users]) => { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/remove-user/remove-user.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/remove-user/remove-user.component.ts index cfccea2fa2..7c4a435f3d 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/remove-user/remove-user.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/remove-user/remove-user.component.ts @@ -4,12 +4,10 @@ import { Store } from '@ngrx/store'; import { combineLatest as obsCombineLatest, Observable, of as observableOf } from 'rxjs'; import { combineLatest, filter, first, map, startWith } from 'rxjs/operators'; -import { CurrentUserPermissions } from '../../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../../core/src/core/current-user-permissions.service'; import { LoggerService } from '../../../../../../core/src/core/logger.service'; +import { CurrentUserPermissionsService } from '../../../../../../core/src/core/permissions/current-user-permissions.service'; import { StepOnNextFunction } from '../../../../../../core/src/shared/components/stepper/step/step.component'; import { AppState } from '../../../../../../store/src/app-state'; -import { selectUsersRoles } from '../../../../../../store/src/selectors/users-roles.selector'; import { UsersRolesClear, UsersRolesExecuteChanges, @@ -17,8 +15,10 @@ import { UsersRolesSetUsers, } from '../../../../actions/users-roles.actions'; import { CfUserService } from '../../../../shared/data-services/cf-user.service'; -import { CfUser, IUserPermissionInOrg, IUserPermissionInSpace } from '../../../../store/types/user.types'; +import { selectCfUsersRoles } from '../../../../store/selectors/cf-users-roles.selector'; +import { CfUser, IUserPermissionInOrg, IUserPermissionInSpace } from '../../../../store/types/cf-user.types'; import { CfRoleChange } from '../../../../store/types/users-roles.types'; +import { CfCurrentUserPermissions } from '../../../../user-permissions/cf-user-permissions-checkers'; import { ActiveRouteCfOrgSpace } from '../../cf-page.types'; import { getActiveRouteCfOrgSpaceProvider } from '../../cf.helpers'; import { CfRolesService } from '../manage-users/cf-roles.service'; @@ -71,7 +71,7 @@ export class RemoveUserComponent implements OnDestroy { return; } - const cfGuid$ = this.store.select(selectUsersRoles).pipe( + const cfGuid$ = this.store.select(selectCfUsersRoles).pipe( combineLatest(this.singleUser$), first() ); @@ -114,12 +114,12 @@ export class RemoveUserComponent implements OnDestroy { const isOrgRole = !c.spaceGuid; if (isOrgRole) { - return this.userPerms.can(CurrentUserPermissions.ORGANIZATION_CHANGE_ROLES, this.cfGuid, c.orgGuid).pipe( + return this.userPerms.can(CfCurrentUserPermissions.ORGANIZATION_CHANGE_ROLES, this.cfGuid, c.orgGuid).pipe( map((can) => ({ can, change: c })) ); } - return this.userPerms.can(CurrentUserPermissions.SPACE_CHANGE_ROLES, this.cfGuid, c.orgGuid, c.spaceGuid).pipe( + return this.userPerms.can(CfCurrentUserPermissions.SPACE_CHANGE_ROLES, this.cfGuid, c.orgGuid, c.spaceGuid).pipe( map((can) => ({ can, change: c })) ); }); diff --git a/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-catalog.module.ts b/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-catalog.module.ts index 015bb2c5af..a959216d92 100644 --- a/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-catalog.module.ts +++ b/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-catalog.module.ts @@ -33,6 +33,6 @@ import { ServiceTabsBaseComponent } from './service-tabs-base/service-tabs-base. ], exports: [ ServiceTabsBaseComponent, - ], + ] }) export class ServiceCatalogModule { } diff --git a/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.html b/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.html index 839286509e..f6b4c53c11 100644 --- a/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.html @@ -3,8 +3,9 @@ {{ getServiceLabel() | async }}
- - @@ -12,4 +13,4 @@ - + \ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.spec.ts index b5a07f34de..e25c5c6f2e 100644 --- a/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.spec.ts @@ -2,6 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TabNavService } from '../../../../../core/tab-nav.service'; import { generateCfBaseTestModules } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; +import { CfUserPermissionDirective } from '../../../shared/directives/cf-user-permission/cf-user-permission.directive'; import { ServicesService } from '../services.service'; import { ServicesServiceMock } from '../services.service.mock'; import { ServiceTabsBaseComponent } from './service-tabs-base.component'; @@ -12,7 +13,10 @@ describe('ServiceTabsBaseComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ServiceTabsBaseComponent], + declarations: [ + ServiceTabsBaseComponent, + CfUserPermissionDirective + ], imports: generateCfBaseTestModules(), providers: [{ provide: ServicesService, useClass: ServicesServiceMock diff --git a/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.ts b/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.ts index b54de1291a..817f55a6e2 100644 --- a/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.ts @@ -4,9 +4,9 @@ import { Observable, Subscription } from 'rxjs'; import { map, publishReplay, refCount } from 'rxjs/operators'; import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; -import { CurrentUserPermissions } from '../../../../../core/src/core/current-user-permissions.config'; import { IPageSideNavTab } from '../../../../../core/src/features/dashboard/page-side-nav/page-side-nav.component'; import { IHeaderBreadcrumb } from '../../../../../core/src/shared/components/page-header/page-header.types'; +import { CfCurrentUserPermissions } from '../../../user-permissions/cf-user-permissions-checkers'; import { getServiceName } from '../services-helper'; import { ServicesService } from '../services.service'; @@ -16,7 +16,7 @@ import { ServicesService } from '../services.service'; styleUrls: ['./service-tabs-base.component.scss'], }) export class ServiceTabsBaseComponent { - canCreateServiceInstance: CurrentUserPermissions; + canCreateServiceInstance: CfCurrentUserPermissions; toolTipText$: Observable; hasVisiblePlans$: Observable; servicesSubscription: Subscription; @@ -50,7 +50,7 @@ export class ServiceTabsBaseComponent { constructor(private servicesService: ServicesService, private store: Store) { this.hasVisiblePlans$ = this.servicesService.servicePlans$.pipe( map(p => p.length > 0)); - this.canCreateServiceInstance = CurrentUserPermissions.SERVICE_INSTANCE_CREATE; + this.canCreateServiceInstance = CfCurrentUserPermissions.SERVICE_INSTANCE_CREATE; this.toolTipText$ = this.hasVisiblePlans$.pipe( map(hasPlans => { if (hasPlans) { diff --git a/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.html b/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.html index 8fb67cc7bd..cf6322f5e7 100644 --- a/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.html @@ -2,7 +2,7 @@

Services

- + @@ -12,15 +12,19 @@

Services

- - - + + \ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.spec.ts index 4087596081..7d20df5522 100644 --- a/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.spec.ts @@ -5,6 +5,7 @@ import { generateCfBaseTestModules } from '../../../../test-framework/cloud-foun import { CfEndpointsMissingComponent } from '../../../shared/components/cf-endpoints-missing/cf-endpoints-missing.component'; import { CfOrgSpaceDataService } from '../../../shared/data-services/cf-org-space-service.service'; import { CloudFoundryService } from '../../../shared/data-services/cloud-foundry.service'; +import { CfUserPermissionDirective } from '../../../shared/directives/cf-user-permission/cf-user-permission.directive'; import { ServicesWallComponent } from './services-wall.component'; describe('ServicesWallComponent', () => { @@ -15,7 +16,8 @@ describe('ServicesWallComponent', () => { TestBed.configureTestingModule({ declarations: [ ServicesWallComponent, - CfEndpointsMissingComponent + CfEndpointsMissingComponent, + CfUserPermissionDirective ], imports: generateCfBaseTestModules(), providers: [ diff --git a/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.ts b/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.ts index 4b36fab461..96d806b0c5 100644 --- a/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.ts @@ -13,8 +13,8 @@ import { initCfOrgSpaceService, } from '../../../../../cloud-foundry/src/shared/data-services/cf-org-space-service.service'; import { CloudFoundryService } from '../../../../../cloud-foundry/src/shared/data-services/cloud-foundry.service'; -import { CurrentUserPermissions } from '../../../../../core/src/core/current-user-permissions.config'; import { ListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; +import { CfCurrentUserPermissions } from '../../../user-permissions/cf-user-permissions-checkers'; @Component({ selector: 'app-services-wall', @@ -32,7 +32,7 @@ export class ServicesWallComponent implements OnDestroy { public haveConnectedCf$: Observable; - canCreateServiceInstance: CurrentUserPermissions; + canCreateServiceInstance: CfCurrentUserPermissions; initCfOrgSpaceService: Subscription; cfIds$: Observable; @@ -41,7 +41,7 @@ export class ServicesWallComponent implements OnDestroy { public store: Store, private cfOrgSpaceService: CfOrgSpaceDataService) { - this.canCreateServiceInstance = CurrentUserPermissions.SERVICE_INSTANCE_CREATE; + this.canCreateServiceInstance = CfCurrentUserPermissions.SERVICE_INSTANCE_CREATE; this.cfIds$ = cloudFoundryService.cFEndpoints$.pipe( map(endpoints => endpoints .filter(endpoint => endpoint.connectionStatus === 'connected') diff --git a/src/frontend/packages/cloud-foundry/src/shared/cf-shared.module.ts b/src/frontend/packages/cloud-foundry/src/shared/cf-shared.module.ts index 6c0ce65be1..7da0831fa8 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/cf-shared.module.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/cf-shared.module.ts @@ -204,6 +204,7 @@ import { ServicePlanPriceComponent } from './components/service-plan-price/servi import { ServicePlanPublicComponent } from './components/service-plan-public/service-plan-public.component'; import { GitSCMService } from './data-services/scm/scm.service'; import { AppNameUniqueDirective } from './directives/app-name-unique.directive/app-name-unique.directive'; +import { CfUserPermissionDirective } from './directives/cf-user-permission/cf-user-permission.directive'; import { ApplicationStateService } from './services/application-state.service'; import { CloudFoundryUserProvidedServicesService } from './services/cloud-foundry-user-provided-services.service'; @@ -321,7 +322,8 @@ const cfListCards: Type>[] = [ AppNameUniqueDirective, ApplicationInstanceChartComponent, GithubCommitAuthorComponent, - EnvVarViewComponent + EnvVarViewComponent, + CfUserPermissionDirective ], exports: [ ServiceIconComponent, @@ -363,7 +365,8 @@ const cfListCards: Type>[] = [ AppNameUniqueDirective, ApplicationInstanceChartComponent, GithubCommitAuthorComponent, - EnvVarViewComponent + EnvVarViewComponent, + CfUserPermissionDirective ], entryComponents: [ CfEndpointDetailsComponent, @@ -376,7 +379,8 @@ const cfListCards: Type>[] = [ providers: [ ApplicationStateService, GitSCMService, - CloudFoundryUserProvidedServicesService, + CloudFoundryUserProvidedServicesService ] }) export class CloudFoundrySharedModule { } + diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cf-role-checkbox/cf-role-checkbox.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cf-role-checkbox/cf-role-checkbox.component.ts index a4652f892f..8472dd6633 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cf-role-checkbox/cf-role-checkbox.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cf-role-checkbox/cf-role-checkbox.component.ts @@ -5,23 +5,23 @@ import { combineLatest as combineLatestOp, filter, first, map } from 'rxjs/opera import { UsersRolesSetOrgRole, UsersRolesSetSpaceRole } from '../../../../../cloud-foundry/src/actions/users-roles.actions'; import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; +import { CfUserRolesSelected } from '../../../../../cloud-foundry/src/store/types/users-roles.types'; +import { CurrentUserPermissionsService } from '../../../../../core/src/core/permissions/current-user-permissions.service'; +import { canUpdateOrgSpaceRoles } from '../../../features/cloud-foundry/cf.helpers'; +import { CfRolesService } from '../../../features/cloud-foundry/users/manage-users/cf-roles.service'; +import { + selectCfUsersIsRemove, + selectCfUsersIsSetByUsername, + selectCfUsersRolesPicked, +} from '../../../store/selectors/cf-users-roles.selector'; import { CfUser, IUserPermissionInOrg, IUserPermissionInSpace, OrgUserRoleNames, SpaceUserRoleNames, -} from '../../../../../cloud-foundry/src/store/types/user.types'; -import { CfUserRolesSelected } from '../../../../../cloud-foundry/src/store/types/users-roles.types'; -import { CurrentUserPermissions } from '../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../core/src/core/current-user-permissions.service'; -import { - selectUsersIsRemove, - selectUsersIsSetByUsername, - selectUsersRolesPicked, -} from '../../../../../store/src/selectors/users-roles.selector'; -import { canUpdateOrgSpaceRoles } from '../../../features/cloud-foundry/cf.helpers'; -import { CfRolesService } from '../../../features/cloud-foundry/users/manage-users/cf-roles.service'; +} from '../../../store/types/cf-user.types'; +import { CfCurrentUserPermissions } from '../../../user-permissions/cf-user-permissions-checkers'; enum CfRoleCheckboxMode { @@ -250,17 +250,17 @@ export class CfRoleCheckboxComponent implements OnInit, OnDestroy { ngOnInit() { this.isOrgRole = !this.spaceGuid; - const users$ = this.store.select(selectUsersRolesPicked); + const users$ = this.store.select(selectCfUsersRolesPicked); // If setting an org role user must be admin or org manager. // If setting a space role user must be admin, org manager or space manager const canEditRole$ = this.isOrgRole ? - this.userPerms.can(CurrentUserPermissions.ORGANIZATION_CHANGE_ROLES, this.cfGuid, this.orgGuid) : + this.userPerms.can(CfCurrentUserPermissions.ORGANIZATION_CHANGE_ROLES, this.cfGuid, this.orgGuid) : canUpdateOrgSpaceRoles( this.userPerms, this.cfGuid, this.orgGuid, this.spaceGuid); - const selectUsersIsSetByUsername$ = this.store.select(selectUsersIsSetByUsername); + const selectUsersIsSetByUsername$ = this.store.select(selectCfUsersIsSetByUsername); this.sub = this.cfRolesService.existingRoles$.pipe( combineLatestOp(this.cfRolesService.newRoles$, users$, canEditRole$, selectUsersIsSetByUsername$), @@ -285,8 +285,8 @@ export class CfRoleCheckboxComponent implements OnInit, OnDestroy { }); this.mode$ = combineLatest([ - this.store.select(selectUsersIsSetByUsername), - this.store.select(selectUsersIsRemove) + this.store.select(selectCfUsersIsSetByUsername), + this.store.select(selectCfUsersIsRemove) ]).pipe( map(([isSetByUsername, isRemove]) => { if (!isSetByUsername) { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/create-application/create-application-step1/create-application-step1.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/create-application/create-application-step1/create-application-step1.component.ts index e455fddee4..1e3dd731a4 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/create-application/create-application-step1/create-application-step1.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/create-application/create-application-step1/create-application-step1.component.ts @@ -5,12 +5,12 @@ import { Store } from '@ngrx/store'; import { asapScheduler, Observable, of } from 'rxjs'; import { map, observeOn, startWith, switchMap, withLatestFrom } from 'rxjs/operators'; -import { PermissionStrings } from '../../../../../../core/src/core/current-user-permissions.config'; import { StepOnNextFunction } from '../../../../../../core/src/shared/components/stepper/step/step.component'; import { SetCFDetails } from '../../../../actions/create-applications-page.actions'; import { ISpace } from '../../../../cf-api.types'; import { CFAppState } from '../../../../cf-app-state'; import { getSpacesFromOrgWithRole } from '../../../../store/selectors/cf-current-user-role.selectors'; +import { CfPermissionStrings } from '../../../../user-permissions/cf-user-permissions-checkers'; import { CfOrgSpaceDataService } from '../../../data-services/cf-org-space-service.service'; @Component({ @@ -83,7 +83,7 @@ export class CreateApplicationStep1Component implements OnInit, AfterContentInit return this.cfOrgSpaceService.org.select.pipe( withLatestFrom(this.cfOrgSpaceService.cf.select), switchMap(([orgGuid, endpointGuid]) => { - return this.store.select(getSpacesFromOrgWithRole(endpointGuid, orgGuid, PermissionStrings.SPACE_DEVELOPER)); + return this.store.select(getSpacesFromOrgWithRole(endpointGuid, orgGuid, CfPermissionStrings.SPACE_DEVELOPER)); }), switchMap((spacesOrAll => { if (spacesOrAll === 'all') { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.spec.ts index d015f401ca..dd2d9a08a4 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.spec.ts @@ -1,7 +1,7 @@ import { CommonModule } from '@angular/common'; import { inject, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { testSCFEndpointGuid } from '@stratos/store/testing'; +import { testSCFEndpointGuid } from '@stratosui/store/testing'; import { CoreModule } from '../../../../../../../core/src/core/core.module'; import { CF_GUID } from '../../../../../../../core/src/shared/entity.tokens'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-route/cf-app-map-routes-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-route/cf-app-map-routes-list-config.service.ts index a07393539f..a113574270 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-route/cf-app-map-routes-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-route/cf-app-map-routes-list-config.service.ts @@ -8,7 +8,9 @@ import { spaceEntityType } from '../../../../../../../cloud-foundry/src/cf-entit import { createEntityRelationPaginationKey, } from '../../../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; -import { CurrentUserPermissionsService } from '../../../../../../../core/src/core/current-user-permissions.service'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ConfirmationDialogService } from '../../../../../../../core/src/shared/components/confirmation-dialog.service'; import { TableCellRadioComponent, diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-route/cf-app-routes-list-config-base.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-route/cf-app-routes-list-config-base.ts index 2f970c7cfa..2a658beb99 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-route/cf-app-routes-list-config-base.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-route/cf-app-routes-list-config-base.ts @@ -5,14 +5,16 @@ import { publishReplay, refCount, switchMap } from 'rxjs/operators'; import { GetAppRoutes } from '../../../../../../../cloud-foundry/src/actions/application-service-routes.actions'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; -import { CurrentUserPermissions } from '../../../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../../../core/src/core/current-user-permissions.service'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ConfirmationDialogService } from '../../../../../../../core/src/shared/components/confirmation-dialog.service'; import { IListConfig } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { PaginatedAction } from '../../../../../../../store/src/types/pagination.types'; import { cfEntityCatalog } from '../../../../../cf-entity-catalog'; import { ApplicationService } from '../../../../../features/applications/application.service'; +import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { CfRoutesListConfigBase } from '../cf-routes/cf-routes-list-config-base'; import { CfAppRoutesDataSource } from './cf-app-routes-data-source'; @@ -42,7 +44,7 @@ export abstract class CfAppRoutesListConfigServiceBase extends CfRoutesListConfi ) { const canEditAppsInSpace = hasActions ? appService.app$.pipe( switchMap(app => currentUserPermissionsService.can( - CurrentUserPermissions.APPLICATION_EDIT, + CfCurrentUserPermissions.APPLICATION_EDIT, appService.cfGuid, app.entity.entity.space_guid )), diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-route/cf-app-routes-list-config.service.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-route/cf-app-routes-list-config.service.spec.ts index 3094755217..59cbb61331 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-route/cf-app-routes-list-config.service.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-route/cf-app-routes-list-config.service.spec.ts @@ -4,7 +4,9 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { Store } from '@ngrx/store'; import { CoreModule } from '../../../../../../../core/src/core/core.module'; -import { CurrentUserPermissionsService } from '../../../../../../../core/src/core/current-user-permissions.service'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ConfirmationDialogService } from '../../../../../../../core/src/shared/components/confirmation-dialog.service'; import { SharedModule } from '../../../../../../../core/src/shared/shared.module'; import { ApplicationServiceMock } from '../../../../../../test-framework/application-service-helper'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-route/cf-app-routes-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-route/cf-app-routes-list-config.service.ts index 109dc07920..a631fac6ae 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-route/cf-app-routes-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-route/cf-app-routes-list-config.service.ts @@ -4,7 +4,9 @@ import { Store } from '@ngrx/store'; import { take } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; -import { CurrentUserPermissionsService } from '../../../../../../../core/src/core/current-user-permissions.service'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ConfirmationDialogService } from '../../../../../../../core/src/shared/components/confirmation-dialog.service'; import { IGlobalListAction, IListConfig } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { RouterNav } from '../../../../../../../store/src/actions/router.actions'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.ts index f3219c6d18..5f15687925 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.ts @@ -4,8 +4,9 @@ import { MatDialog } from '@angular/material/dialog'; import { combineLatest as observableCombineLatest, Observable, of as observableOf, of } from 'rxjs'; import { filter, first, map, switchMap } from 'rxjs/operators'; -import { CurrentUserPermissions } from '../../../../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../../../../core/src/core/current-user-permissions.service'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { AppChip } from '../../../../../../../../core/src/shared/components/chips/chips.component'; import { MetaCardMenuItem, @@ -31,6 +32,7 @@ import { getServiceSummaryUrl, } from '../../../../../../features/service-catalog/services-helper'; import { AppEnvVarsState } from '../../../../../../store/types/app-metadata.types'; +import { CfCurrentUserPermissions } from '../../../../../../user-permissions/cf-user-permissions-checkers'; import { ServiceActionHelperService } from '../../../../../data-services/service-action-helper.service'; import { EnvVarViewComponent } from '../../../../env-var-view/env-var-view.component'; @@ -77,7 +79,7 @@ export class AppServiceBindingCardComponent extends CardCell this.currentUserPermissionsService.can( - CurrentUserPermissions.SERVICE_BINDING_EDIT, + CfCurrentUserPermissions.SERVICE_BINDING_EDIT, this.appService.cfGuid, app.entity.entity.space_guid ))) @@ -87,7 +89,7 @@ export class AppServiceBindingCardComponent extends CardCell this.currentUserPermissionsService.can( - CurrentUserPermissions.SERVICE_BINDING_EDIT, + CfCurrentUserPermissions.SERVICE_BINDING_EDIT, this.appService.cfGuid, app.entity.entity.space_guid ))) diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-data-source.ts index 236f9e6abd..932b497638 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-data-source.ts @@ -1,5 +1,5 @@ import { Store } from '@ngrx/store'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-list-config.service.ts index a11dc7151d..68288fa9ec 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-list-config.service.ts @@ -4,8 +4,9 @@ import { Store } from '@ngrx/store'; import { switchMap } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; -import { CurrentUserPermissions } from '../../../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../../../core/src/core/current-user-permissions.service'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { DataFunctionDefinition, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; @@ -22,6 +23,7 @@ import { GetAppServiceBindings } from '../../../../../actions/application-servic import { IServiceBinding } from '../../../../../cf-api-svc.types'; import { ApplicationService } from '../../../../../features/applications/application.service'; import { isServiceInstance, isUserProvidedServiceInstance } from '../../../../../features/cloud-foundry/cf.helpers'; +import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { ServiceActionHelperService } from '../../../../data-services/service-action-helper.service'; import { BaseCfListConfig } from '../base-cf/base-cf-list-config'; import { @@ -46,7 +48,7 @@ export class AppServiceBindingListConfigService extends BaseCfListConfig this.currentUserPermissionsService.can( - CurrentUserPermissions.SERVICE_INSTANCE_CREATE, + CfCurrentUserPermissions.SERVICE_INSTANCE_CREATE, this.appService.cfGuid, app.entity.entity.space_guid )) @@ -70,7 +72,7 @@ export class AppServiceBindingListConfigService extends BaseCfListConfig this.appService.waitForAppEntity$.pipe( switchMap(app => this.currentUserPermissionsService.can( - CurrentUserPermissions.SERVICE_BINDING_EDIT, + CfCurrentUserPermissions.SERVICE_BINDING_EDIT, this.appService.cfGuid, app.entity.entity.space_guid )) @@ -91,7 +93,7 @@ export class AppServiceBindingListConfigService extends BaseCfListConfig this.appService.waitForAppEntity$.pipe( switchMap(app => this.currentUserPermissionsService.can( - CurrentUserPermissions.SERVICE_BINDING_EDIT, + CfCurrentUserPermissions.SERVICE_BINDING_EDIT, this.appService.cfGuid, app.entity.entity.space_guid )) diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/cf-apps-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/cf-apps-data-source.ts index 3a4849a95a..0e4e9e1269 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/cf-apps-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/cf-apps-data-source.ts @@ -1,5 +1,5 @@ import { Store } from '@ngrx/store'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { Subscription } from 'rxjs'; import { tag } from 'rxjs-spy/operators/tag'; import { debounceTime, delay, distinctUntilChanged, map, withLatestFrom } from 'rxjs/operators'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-buildpacks/cf-buildpack-card/cf-buildpack-card.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-buildpacks/cf-buildpack-card/cf-buildpack-card.component.html index 923acf666d..f4a209c775 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-buildpacks/cf-buildpack-card/cf-buildpack-card.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-buildpacks/cf-buildpack-card/cf-buildpack-card.component.html @@ -27,7 +27,7 @@ Updated {{ row.metadata.updated_at | date: 'medium' }} - + File Name {{ row.entity.filename }} diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-confirm-roles/table-cell-confirm-role-add-rem/table-cell-confirm-role-add-rem.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-confirm-roles/table-cell-confirm-role-add-rem/table-cell-confirm-role-add-rem.component.spec.ts index 0610d25aae..d0ff520581 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-confirm-roles/table-cell-confirm-role-add-rem/table-cell-confirm-role-add-rem.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-confirm-roles/table-cell-confirm-role-add-rem/table-cell-confirm-role-add-rem.component.spec.ts @@ -1,10 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { OrgUserRoleNames } from '../../../../../../../../cloud-foundry/src/store/types/user.types'; import { MDAppModule } from '../../../../../../../../core/src/core/md.module'; import { BooleanIndicatorComponent, } from '../../../../../../../../core/src/shared/components/boolean-indicator/boolean-indicator.component'; +import { OrgUserRoleNames } from '../../../../../../store/types/cf-user.types'; import { TableCellConfirmRoleAddRemComponent } from './table-cell-confirm-role-add-rem.component'; describe('TableCellConfirmRoleAddRemComponent', () => { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-events/cf-events-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-events/cf-events-data-source.ts index 47d5f725e9..5a6adeb8eb 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-events/cf-events-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-events/cf-events-data-source.ts @@ -1,5 +1,5 @@ import { Store } from '@ngrx/store'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { ListDataSource, @@ -29,7 +29,7 @@ export class CfEventsDataSource extends ListDataSource { actee ); - const action = cfEntityCatalog.event.actions.getMultiple(cfGuid, paginationKey) + const action = cfEntityCatalog.event.actions.getMultiple(cfGuid, paginationKey); action.initialParams.q = CfEventsDataSource.createInitialQParams( orgGuid, diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-org-users/cf-org-users-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-org-users/cf-org-users-list-config.service.ts index 7d53361cbb..4d125e2cca 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-org-users/cf-org-users-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-org-users/cf-org-users-list-config.service.ts @@ -3,12 +3,14 @@ import { Router } from '@angular/router'; import { Store } from '@ngrx/store'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; -import { CfUser } from '../../../../../../../cloud-foundry/src/store/types/user.types'; -import { CurrentUserPermissionsService } from '../../../../../../../core/src/core/current-user-permissions.service'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; import { CloudFoundryOrganizationService, } from '../../../../../features/cloud-foundry/services/cloud-foundry-organization.service'; +import { CfUser } from '../../../../../store/types/cf-user.types'; import { CfUserService } from '../../../../data-services/cf-user.service'; import { CfUserListConfigService } from '../cf-users/cf-user-list-config.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component.spec.ts index 1df1add336..52edb4889a 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { Store } from '@ngrx/store'; -import { testSessionData } from '@stratos/store/testing'; +import { testSessionData } from '@stratosui/store/testing'; import { ConfirmationDialogService } from '../../../../../../../../core/src/shared/components/confirmation-dialog.service'; import { MetadataCardTestComponents } from '../../../../../../../../core/test-framework/core-test.helper'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component.ts index c229dd2e8d..c8275787bf 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component.ts @@ -5,9 +5,9 @@ import { map, publishReplay, refCount, switchMap, tap } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../../cloud-foundry/src/cf-app-state'; import { organizationEntityType } from '../../../../../../../../cloud-foundry/src/cf-entity-types'; -import { createUserRoleInOrg } from '../../../../../../../../cloud-foundry/src/store/types/user.types'; -import { CurrentUserPermissions } from '../../../../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../../../../core/src/core/current-user-permissions.service'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { getFavoriteFromEntity } from '../../../../../../../../core/src/core/user-favorite-helpers'; import { truthyIncludingZeroString } from '../../../../../../../../core/src/core/utils.service'; import { ConfirmationDialogConfig } from '../../../../../../../../core/src/shared/components/confirmation-dialog.config'; @@ -37,6 +37,8 @@ import { OrgQuotaHelper } from '../../../../../../features/cloud-foundry/service import { createOrgQuotaDefinition, } from '../../../../../../features/cloud-foundry/services/cloud-foundry-organization.service'; +import { createUserRoleInOrg } from '../../../../../../store/types/cf-user.types'; +import { CfCurrentUserPermissions } from '../../../../../../user-permissions/cf-user-permissions-checkers'; import { CfUserService } from '../../../../../data-services/cf-user.service'; import { CF_ENDPOINT_TYPE } from './../../../../../../cf-types'; @@ -78,12 +80,12 @@ export class CfOrgCardComponent extends CardCell> imp { label: 'Edit', action: this.edit, - can: this.currentUserPermissionsService.can(CurrentUserPermissions.ORGANIZATION_EDIT, this.cfEndpointService.cfGuid) + can: this.currentUserPermissionsService.can(CfCurrentUserPermissions.ORGANIZATION_EDIT, this.cfEndpointService.cfGuid) }, { label: 'Delete', action: this.delete, - can: this.currentUserPermissionsService.can(CurrentUserPermissions.ORGANIZATION_DELETE, this.cfEndpointService.cfGuid) + can: this.currentUserPermissionsService.can(CfCurrentUserPermissions.ORGANIZATION_DELETE, this.cfEndpointService.cfGuid) } ]; } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-orgs-data-source.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-orgs-data-source.service.ts index 9149423b08..f0c23e0d95 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-orgs-data-source.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-orgs-data-source.service.ts @@ -1,5 +1,5 @@ import { Store } from '@ngrx/store'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { organizationEntityType } from '../../../../../../../cloud-foundry/src/cf-entity-types'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-quotas/cf-quotas-data-source.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-quotas/cf-quotas-data-source.service.ts index 09c980b0d2..f2bf84af31 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-quotas/cf-quotas-data-source.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-quotas/cf-quotas-data-source.service.ts @@ -1,5 +1,5 @@ import { Store } from '@ngrx/store'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { of } from 'rxjs'; import { distinctUntilChanged, map } from 'rxjs/operators'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-quotas/cf-quotas-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-quotas/cf-quotas-list-config.service.ts index 9840ba444f..d4e001d8aa 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-quotas/cf-quotas-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-quotas/cf-quotas-list-config.service.ts @@ -9,8 +9,9 @@ import { ActiveRouteCfOrgSpace } from '../../../../../../../cloud-foundry/src/fe import { BaseCfListConfig, } from '../../../../../../../cloud-foundry/src/shared/components/list/list-types/base-cf/base-cf-list-config'; -import { CurrentUserPermissions } from '../../../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../../../core/src/core/current-user-permissions.service'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ConfirmationDialogConfig } from '../../../../../../../core/src/shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from '../../../../../../../core/src/shared/components/confirmation-dialog.service'; import { ITableColumn } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; @@ -18,6 +19,7 @@ import { IListAction, ListViewTypes } from '../../../../../../../core/src/shared import { RouterNav } from '../../../../../../../store/src/actions/router.actions'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { IQuotaDefinition } from '../../../../../cf-api.types'; +import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { CfQuotasDataSourceService } from './cf-quotas-data-source.service'; import { TableCellQuotaComponent } from './table-cell-quota/table-cell-quota.component'; @@ -39,8 +41,8 @@ export class CfQuotasListConfigService extends BaseCfListConfig>) => { return route$.pipe( switchMap(route => currentUserPermissionsService.can( - CurrentUserPermissions.APPLICATION_EDIT, + CfCurrentUserPermissions.APPLICATION_EDIT, route.entity.cfGuid, route.entity.space_guid )), diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-data-source.ts index 6d6fe29a76..42b046e9dd 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-data-source.ts @@ -1,5 +1,5 @@ import { Store } from '@ngrx/store'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { securityGroupEntityType } from '../../../../../../../cloud-foundry/src/cf-entity-types'; @@ -18,7 +18,7 @@ import { cfEntityFactory } from '../../../../../cf-entity-factory'; export class CfSecurityGroupsDataSource extends ListDataSource { constructor(store: Store, cfGuid: string, listConfig?: IListConfig) { const paginationKey = createEntityRelationPaginationKey(endpointSchemaKey, cfGuid); - const action = cfEntityCatalog.securityGroup.actions.getMultiple(cfGuid, paginationKey, {}) + const action = cfEntityCatalog.securityGroup.actions.getMultiple(cfGuid, paginationKey, {}); super({ store, action, diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-select-users/cf-select-users-data-source.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-select-users/cf-select-users-data-source.service.ts index e30ec5e1bc..757bc461b3 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-select-users/cf-select-users-data-source.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-select-users/cf-select-users-data-source.service.ts @@ -1,5 +1,5 @@ import { Store } from '@ngrx/store'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { cfUserEntityType } from '../../../../../../../cloud-foundry/src/cf-entity-types'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-select-users/cf-select-users-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-select-users/cf-select-users-list-config.service.ts index a60d8d0c6c..10ee7b1abb 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-select-users/cf-select-users-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-select-users/cf-select-users-list-config.service.ts @@ -3,7 +3,6 @@ import { combineLatest, Observable, of as observableOf } from 'rxjs'; import { distinctUntilChanged, map, publishReplay, refCount, switchMap, tap } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; -import { CfUser, CfUserMissingRoles } from '../../../../../../../cloud-foundry/src/store/types/user.types'; import { TableRowStateManager, } from '../../../../../../../core/src/shared/components/list/list-table/table-row/table-row-state-manager'; @@ -25,6 +24,7 @@ import { APIResource } from '../../../../../../../store/src/types/api.types'; import { PaginatedAction } from '../../../../../../../store/src/types/pagination.types'; import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; import { waitForCFPermissions } from '../../../../../features/cloud-foundry/cf.helpers'; +import { CfUser, CfUserMissingRoles } from '../../../../../store/types/cf-user.types'; import { CfUserService } from '../../../../data-services/cf-user.service'; import { CfSelectUsersDataSourceService } from './cf-select-users-data-source.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts index 0587430dbe..52b60a12ee 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts @@ -5,8 +5,9 @@ import { Observable } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; -import { CurrentUserPermissions } from '../../../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../../../core/src/core/current-user-permissions.service'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ListDataSource, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; @@ -20,6 +21,7 @@ import { import { ListView } from '../../../../../../../store/src/actions/list.actions'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { IServiceInstance } from '../../../../../cf-api-svc.types'; +import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { ServiceActionHelperService } from '../../../../data-services/service-action-helper.service'; import { CANCEL_ORG_ID_PARAM, CANCEL_SPACE_ID_PARAM } from '../../../add-service-instance/csi-mode.service'; import { @@ -127,7 +129,7 @@ export class CfServiceInstancesListConfigBase implements IListConfig>) => row$.pipe( switchMap( - row => this.can(this.canDeleteCache, CurrentUserPermissions.SERVICE_INSTANCE_DELETE, row.entity.cfGuid, row.entity.space_guid) + row => this.can(this.canDeleteCache, CfCurrentUserPermissions.SERVICE_INSTANCE_DELETE, row.entity.cfGuid, row.entity.space_guid) ) ) }; @@ -140,7 +142,7 @@ export class CfServiceInstancesListConfigBase implements IListConfig>) => row$.pipe( switchMap( - row => this.can(this.canDetachCache, CurrentUserPermissions.SERVICE_BINDING_EDIT, row.entity.cfGuid, row.entity.space_guid) + row => this.can(this.canDetachCache, CfCurrentUserPermissions.SERVICE_BINDING_EDIT, row.entity.cfGuid, row.entity.space_guid) ) ) }; @@ -156,12 +158,12 @@ export class CfServiceInstancesListConfigBase implements IListConfig>) => row$.pipe( switchMap( - row => this.can(this.canDetachCache, CurrentUserPermissions.SERVICE_BINDING_EDIT, row.entity.cfGuid, row.entity.space_guid) + row => this.can(this.canDetachCache, CfCurrentUserPermissions.SERVICE_BINDING_EDIT, row.entity.cfGuid, row.entity.space_guid) ) ) }; - private can(cache: CanCache, perm: CurrentUserPermissions, cfGuid: string, spaceGuid: string): Observable { + private can(cache: CanCache, perm: CfCurrentUserPermissions, cfGuid: string, spaceGuid: string): Observable { let can = cache[spaceGuid]; if (!can) { can = this.currentUserPermissionsService.can(perm, cfGuid, spaceGuid); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-data-source.ts index e0a1c8eeef..334d992dd6 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-data-source.ts @@ -1,5 +1,5 @@ import { Store } from '@ngrx/store'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { serviceEntityType } from '../../../../../../../cloud-foundry/src/cf-entity-types'; @@ -20,7 +20,7 @@ import { CF_ENDPOINT_TYPE } from '../../../../../cf-types'; export class CfServicesDataSource extends ListDataSource { constructor(store: Store, endpointGuid: string, listConfig?: IListConfig) { const paginationKey = createEntityRelationPaginationKey(endpointSchemaKey); - const getServicesAction = cfEntityCatalog.service.actions.getMultiple(endpointGuid, paginationKey, {}) + const getServicesAction = cfEntityCatalog.service.actions.getMultiple(endpointGuid, paginationKey, {}); super({ store, action: getServicesAction, diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.ts index b8c4e53ff4..90fdcad893 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.ts @@ -1,6 +1,5 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import { endpointsCfEntitiesConnectedSelector } from 'frontend/packages/store/src/selectors/endpoint.selectors'; import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; import { filter, first, map } from 'rxjs/operators'; @@ -12,7 +11,9 @@ import { ListViewTypes, } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { ListView } from '../../../../../../../store/src/actions/list.actions'; +import { connectedEndpointsOfTypesSelector } from '../../../../../../../store/src/selectors/endpoint.selectors'; import { APIResource } from '../../../../../../../store/src/types/api.types'; +import { CF_ENDPOINT_TYPE } from '../../../../../cf-types'; import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; import { haveMultiConnectedCfs } from '../../../../../features/cloud-foundry/cf.helpers'; import { CfOrgSpaceItem, createCfOrgSpaceFilterConfig } from '../../../../data-services/cf-org-space-service.service'; @@ -38,7 +39,7 @@ export class CfServicesListConfigService implements IListConfig { ) { this.dataSource = new CfServicesDataSource(this.store, activeRouteCfOrgSpace.cfGuid, this); this.cf = { - list$: this.store.select(endpointsCfEntitiesConnectedSelector).pipe( + list$: this.store.select(connectedEndpointsOfTypesSelector(CF_ENDPOINT_TYPE)).pipe( first(), map(endpoints => Object.values(endpoints)) ), diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-user-service-instances-list-config.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-user-service-instances-list-config.ts index 9c20d74796..9f653f851f 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-user-service-instances-list-config.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-user-service-instances-list-config.ts @@ -5,8 +5,9 @@ import { Observable } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; -import { CurrentUserPermissions } from '../../../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../../../core/src/core/current-user-permissions.service'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ListDataSource, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; @@ -21,6 +22,7 @@ import { ListView } from '../../../../../../../store/src/actions/list.actions'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { IUserProvidedServiceInstance } from '../../../../../cf-api-svc.types'; import { CloudFoundrySpaceService } from '../../../../../features/cloud-foundry/services/cloud-foundry-space.service'; +import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { ServiceActionHelperService } from '../../../../data-services/service-action-helper.service'; import { CANCEL_ORG_ID_PARAM, @@ -123,7 +125,7 @@ export class CfUserServiceInstancesListConfigBase implements IListConfig>) => row$.pipe( switchMap( - row => this.can(this.canDeleteCache, CurrentUserPermissions.SERVICE_INSTANCE_DELETE, row.entity.cfGuid, row.entity.space_guid) + row => this.can(this.canDeleteCache, CfCurrentUserPermissions.SERVICE_INSTANCE_DELETE, row.entity.cfGuid, row.entity.space_guid) ) ) }; @@ -137,7 +139,7 @@ export class CfUserServiceInstancesListConfigBase implements IListConfig>) => row$.pipe( switchMap( - row => this.can(this.canDetachCache, CurrentUserPermissions.SERVICE_BINDING_EDIT, row.entity.cfGuid, row.entity.space_guid) + row => this.can(this.canDetachCache, CfCurrentUserPermissions.SERVICE_BINDING_EDIT, row.entity.cfGuid, row.entity.space_guid) ) ) }; @@ -158,12 +160,12 @@ export class CfUserServiceInstancesListConfigBase implements IListConfig>) => row$.pipe( switchMap( - row => this.can(this.canDetachCache, CurrentUserPermissions.SERVICE_BINDING_EDIT, row.entity.cfGuid, row.entity.space_guid) + row => this.can(this.canDetachCache, CfCurrentUserPermissions.SERVICE_BINDING_EDIT, row.entity.cfGuid, row.entity.space_guid) ) ) }; - private can(cache: CanCache, perm: CurrentUserPermissions, cfGuid: string, spaceGuid: string): Observable { + private can(cache: CanCache, perm: CfCurrentUserPermissions, cfGuid: string, spaceGuid: string): Observable { let can = cache[spaceGuid]; if (!can) { can = this.currentUserPermissionsService.can(perm, cfGuid, spaceGuid); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-data-source.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-data-source.service.ts index 3c7cbaaffe..b858a197ed 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-data-source.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-data-source.service.ts @@ -1,5 +1,5 @@ import { Store } from '@ngrx/store'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { applicationEntityType, spaceEntityType } from '../../../../../../../cloud-foundry/src/cf-entity-types'; @@ -25,7 +25,7 @@ export class CfSpaceAppsDataSource extends ListDataSource { [], false, false - ) + ); super({ store, action, diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-quotas/cf-space-quotas-data-source.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-quotas/cf-space-quotas-data-source.service.ts index 39cc613e32..4a880b804b 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-quotas/cf-space-quotas-data-source.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-quotas/cf-space-quotas-data-source.service.ts @@ -1,5 +1,5 @@ import { Store } from '@ngrx/store'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { of } from 'rxjs'; import { distinctUntilChanged, map } from 'rxjs/operators'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-quotas/cf-space-quotas-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-quotas/cf-space-quotas-list-config.service.ts index 3cfc6e83c1..fd78c9a3fd 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-quotas/cf-space-quotas-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-quotas/cf-space-quotas-list-config.service.ts @@ -3,8 +3,9 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable, Subscription } from 'rxjs'; -import { CurrentUserPermissions } from '../../../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../../../core/src/core/current-user-permissions.service'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ConfirmationDialogConfig } from '../../../../../../../core/src/shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from '../../../../../../../core/src/shared/components/confirmation-dialog.service'; import { ITableColumn } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; @@ -15,6 +16,7 @@ import { DeleteSpaceQuotaDefinition } from '../../../../../actions/quota-definit import { IQuotaDefinition } from '../../../../../cf-api.types'; import { CFAppState } from '../../../../../cf-app-state'; import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; +import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { BaseCfListConfig } from '../base-cf/base-cf-list-config'; import { QUOTA_FROM_LIST } from '../cf-quotas/cf-quotas-list-config.service'; import { TableCellQuotaComponent } from '../cf-quotas/table-cell-quota/table-cell-quota.component'; @@ -37,8 +39,8 @@ export class CfSpaceQuotasListConfigService extends BaseCfListConfig> implemen label: 'Edit', action: this.edit, can: this.currentUserPermissionsService.can( - CurrentUserPermissions.SPACE_EDIT, + CfCurrentUserPermissions.SPACE_EDIT, this.cfEndpointService.cfGuid, this.orgGuid, this.spaceGuid @@ -97,7 +99,7 @@ export class CfSpaceCardComponent extends CardCell> implemen label: 'Delete', action: this.delete, can: this.currentUserPermissionsService.can( - CurrentUserPermissions.SPACE_DELETE, + CfCurrentUserPermissions.SPACE_DELETE, this.cfEndpointService.cfGuid, this.orgGuid ) diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces/cf-spaces-data-source.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces/cf-spaces-data-source.service.ts index 87417ff379..27c5a13c81 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces/cf-spaces-data-source.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces/cf-spaces-data-source.service.ts @@ -1,5 +1,5 @@ import { Store } from '@ngrx/store'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-stacks/cf-stacks-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-stacks/cf-stacks-data-source.ts index d4b5816219..4a6e6068ac 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-stacks/cf-stacks-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-stacks/cf-stacks-data-source.ts @@ -1,5 +1,5 @@ import { Store } from '@ngrx/store'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/cf-users-space-roles-data-source.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/cf-users-space-roles-data-source.service.ts index a922ee27b8..83128b8b1c 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/cf-users-space-roles-data-source.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/cf-users-space-roles-data-source.service.ts @@ -1,5 +1,5 @@ import { Store } from '@ngrx/store'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { GetAllOrganizationSpacesWithOrgs } from '../../../../../../../cloud-foundry/src/actions/organization.actions'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; @@ -9,7 +9,9 @@ import { spaceEntityType, } from '../../../../../../../cloud-foundry/src/cf-entity-types'; import { createEntityRelationKey } from '../../../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; -import { CurrentUserPermissionsService } from '../../../../../../../core/src/core/current-user-permissions.service'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ListDataSource, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/cf-users-space-roles-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/cf-users-space-roles-list-config.service.ts index 17f384dd8c..a65ce2cf84 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/cf-users-space-roles-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/cf-users-space-roles-list-config.service.ts @@ -3,14 +3,16 @@ import { BehaviorSubject } from 'rxjs'; import { first } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; -import { SpaceUserRoleNames } from '../../../../../../../cloud-foundry/src/store/types/user.types'; -import { CurrentUserPermissionsService } from '../../../../../../../core/src/core/current-user-permissions.service'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ITableColumn } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; import { IListConfig, ListViewTypes } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { ListView } from '../../../../../../../store/src/actions/list.actions'; -import { selectUsersRolesRoles } from '../../../../../../../store/src/selectors/users-roles.selector'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { ISpace } from '../../../../../cf-api.types'; +import { selectCfUsersRolesRoles } from '../../../../../store/selectors/cf-users-roles.selector'; +import { SpaceUserRoleNames } from '../../../../../store/types/cf-user.types'; import { CfUsersSpaceRolesDataSourceService } from './cf-users-space-roles-data-source.service'; import { TableCellRoleOrgSpaceComponent } from './table-cell-org-space-role/table-cell-org-space-role.component'; @@ -75,7 +77,7 @@ export class CfUsersSpaceRolesListConfigService implements IListConfig(false); constructor(private store: Store, cfGuid: string, spaceGuid: string, userPerms: CurrentUserPermissionsService) { - this.store.select(selectUsersRolesRoles).pipe( + this.store.select(selectCfUsersRolesRoles).pipe( first() ).subscribe(newRoles => { this.dataSource = new CfUsersSpaceRolesDataSourceService(cfGuid, newRoles.orgGuid, spaceGuid, this.store, userPerms, this); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/table-cell-select-org/table-cell-select-org.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/table-cell-select-org/table-cell-select-org.component.ts index 1f8f147977..4d11410056 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/table-cell-select-org/table-cell-select-org.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/table-cell-select-org/table-cell-select-org.component.ts @@ -6,11 +6,11 @@ import { first, map } from 'rxjs/operators'; import { UsersRolesSetOrg } from '../../../../../../../../cloud-foundry/src/actions/users-roles.actions'; import { CFAppState } from '../../../../../../../../cloud-foundry/src/cf-app-state'; import { TableCellCustom } from '../../../../../../../../core/src/shared/components/list/list.types'; -import { selectUsersRolesOrgGuid } from '../../../../../../../../store/src/selectors/users-roles.selector'; import { APIResource } from '../../../../../../../../store/src/types/api.types'; import { IOrganization } from '../../../../../../cf-api.types'; import { ActiveRouteCfOrgSpace } from '../../../../../../features/cloud-foundry/cf-page.types'; import { CfRolesService } from '../../../../../../features/cloud-foundry/users/manage-users/cf-roles.service'; +import { selectCfUsersRolesOrgGuid } from '../../../../../../store/selectors/cf-users-roles.selector'; @Component({ selector: 'app-table-cell-select-org', @@ -43,7 +43,7 @@ export class TableCellSelectOrgComponent extends TableCellCustom orgs && orgs.length === 1 ? orgs[0] : null) ); } - this.orgGuidChangedSub = this.store.select(selectUsersRolesOrgGuid).subscribe(orgGuid => { + this.orgGuidChangedSub = this.store.select(selectCfUsersRolesOrgGuid).subscribe(orgGuid => { this.selectedOrgGuid = orgGuid; }); } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-org-permission-cell/cf-org-permission-cell.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-org-permission-cell/cf-org-permission-cell.component.ts index f28255ba44..76825b3388 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-org-permission-cell/cf-org-permission-cell.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-org-permission-cell/cf-org-permission-cell.component.ts @@ -3,17 +3,13 @@ import { Store } from '@ngrx/store'; import { combineLatest } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; -import { RemoveUserRole } from '../../../../../../../../cloud-foundry/src/actions/users.actions'; +import { RemoveCfUserRole } from '../../../../../../../../cloud-foundry/src/actions/users.actions'; import { CFAppState } from '../../../../../../../../cloud-foundry/src/cf-app-state'; import { organizationEntityType } from '../../../../../../../../cloud-foundry/src/cf-entity-types'; -import { - CfUser, - IUserPermissionInOrg, - OrgUserRoleNames, -} from '../../../../../../../../cloud-foundry/src/store/types/user.types'; -import { CurrentUserPermissions } from '../../../../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../../../../core/src/core/current-user-permissions.service'; import { arrayHelper } from '../../../../../../../../core/src/core/helper-classes/array.helper'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { AppChip } from '../../../../../../../../core/src/shared/components/chips/chips.component'; import { ConfirmationDialogService } from '../../../../../../../../core/src/shared/components/confirmation-dialog.service'; import { entityCatalog } from '../../../../../../../../store/src/entity-catalog/entity-catalog'; @@ -21,6 +17,8 @@ import { APIResource } from '../../../../../../../../store/src/types/api.types'; import { IOrganization } from '../../../../../../cf-api.types'; import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; import { getOrgRoles } from '../../../../../../features/cloud-foundry/cf.helpers'; +import { CfUser, IUserPermissionInOrg, OrgUserRoleNames } from '../../../../../../store/types/cf-user.types'; +import { CfCurrentUserPermissions } from '../../../../../../user-permissions/cf-user-permissions-checkers'; import { CfUserService } from '../../../../../data-services/cf-user.service'; import { CfPermissionCell, ICellPermissionList } from '../cf-permission-cell'; @@ -60,7 +58,7 @@ export class CfOrgPermissionCellComponent extends CfPermissionCell, showName: boolean): ICellPermissionList[] { return getOrgRoles(orgPerms.permissions).map(perm => { - const updatingKey = RemoveUserRole.generateUpdatingKey( + const updatingKey = RemoveCfUserRole.generateUpdatingKey( perm.key, row.metadata.guid ); @@ -87,7 +85,7 @@ export class CfOrgPermissionCellComponent extends CfPermissionCell, updateConnectedUser: boolean) { - this.store.dispatch(new RemoveUserRole( + this.store.dispatch(new RemoveCfUserRole( this.cfUserService.activeRouteCfOrgSpace.cfGuid, cellPermission.userGuid, cellPermission.guid, @@ -98,6 +96,6 @@ export class CfOrgPermissionCellComponent extends CfPermissionCell - this.userPerms.can(CurrentUserPermissions.ORGANIZATION_CHANGE_ROLES, cfGuid, orgGuid) + this.userPerms.can(CfCurrentUserPermissions.ORGANIZATION_CHANGE_ROLES, cfGuid, orgGuid) } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-permission-cell.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-permission-cell.ts index 8708f3cb6b..7c325a8325 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-permission-cell.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-permission-cell.ts @@ -4,7 +4,6 @@ import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; import { filter, first, map, switchMap } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; -import { CfUser } from '../../../../../../../cloud-foundry/src/store/types/user.types'; import { UserRoleLabels } from '../../../../../../../cloud-foundry/src/store/types/users-roles.types'; import { AppChip } from '../../../../../../../core/src/shared/components/chips/chips.component'; import { ConfirmationDialogConfig } from '../../../../../../../core/src/shared/components/confirmation-dialog.config'; @@ -13,6 +12,7 @@ import { TableCellCustom } from '../../../../../../../core/src/shared/components import { selectSessionData } from '../../../../../../../store/src/reducers/auth.reducer'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { IUserRole } from '../../../../../features/cloud-foundry/cf.helpers'; +import { CfUser } from '../../../../../store/types/cf-user.types'; import { CfUserService } from '../../../../data-services/cf-user.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-space-permission-cell/cf-space-permission-cell.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-space-permission-cell/cf-space-permission-cell.component.ts index b03582d3f8..6aa867410f 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-space-permission-cell/cf-space-permission-cell.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-space-permission-cell/cf-space-permission-cell.component.ts @@ -3,24 +3,22 @@ import { Store } from '@ngrx/store'; import { combineLatest, Observable, of as observableOf } from 'rxjs'; import { filter, first, map, switchMap } from 'rxjs/operators'; -import { RemoveUserRole } from '../../../../../../../../cloud-foundry/src/actions/users.actions'; +import { RemoveCfUserRole } from '../../../../../../../../cloud-foundry/src/actions/users.actions'; import { CFAppState } from '../../../../../../../../cloud-foundry/src/cf-app-state'; import { organizationEntityType, spaceEntityType } from '../../../../../../../../cloud-foundry/src/cf-entity-types'; import { selectCfEntity } from '../../../../../../../../cloud-foundry/src/store/selectors/api.selectors'; -import { - CfUser, - IUserPermissionInSpace, - SpaceUserRoleNames, -} from '../../../../../../../../cloud-foundry/src/store/types/user.types'; -import { CurrentUserPermissions } from '../../../../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../../../../core/src/core/current-user-permissions.service'; import { arrayHelper } from '../../../../../../../../core/src/core/helper-classes/array.helper'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ConfirmationDialogService } from '../../../../../../../../core/src/shared/components/confirmation-dialog.service'; import { entityCatalog } from '../../../../../../../../store/src/entity-catalog/entity-catalog'; import { APIResource } from '../../../../../../../../store/src/types/api.types'; import { IOrganization, ISpace } from '../../../../../../cf-api.types'; import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; import { getSpaceRoles } from '../../../../../../features/cloud-foundry/cf.helpers'; +import { CfUser, IUserPermissionInSpace, SpaceUserRoleNames } from '../../../../../../store/types/cf-user.types'; +import { CfCurrentUserPermissions } from '../../../../../../user-permissions/cf-user-permissions-checkers'; import { CfUserService } from '../../../../../data-services/cf-user.service'; import { CfPermissionCell, ICellPermissionList } from '../cf-permission-cell'; @@ -116,7 +114,7 @@ export class CfSpacePermissionCellComponent extends CfPermissionCell, isOrgLevel = true) { return getSpaceRoles(spacePerms.permissions).map(perm => { - const updatingKey = RemoveUserRole.generateUpdatingKey( + const updatingKey = RemoveCfUserRole.generateUpdatingKey( perm.key, row.metadata.guid ); @@ -144,7 +142,7 @@ export class CfSpacePermissionCellComponent extends CfPermissionCell, updateConnectedUser: boolean) { - this.store.dispatch(new RemoveUserRole( + this.store.dispatch(new RemoveCfUserRole( this.cfUserService.activeRouteCfOrgSpace.cfGuid, cellPermission.userGuid, cellPermission.guid, @@ -156,5 +154,5 @@ export class CfSpacePermissionCellComponent extends CfPermissionCell - this.userPerms.can(CurrentUserPermissions.SPACE_CHANGE_ROLES, cfGuid, orgGuid, spaceGuid) + this.userPerms.can(CfCurrentUserPermissions.SPACE_CHANGE_ROLES, cfGuid, orgGuid, spaceGuid) } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-data-source.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-data-source.service.ts index 73462af393..05d3b80147 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-data-source.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-data-source.service.ts @@ -1,9 +1,8 @@ import { Store } from '@ngrx/store'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { cfUserEntityType } from '../../../../../../../cloud-foundry/src/cf-entity-types'; -import { CfUser } from '../../../../../../../cloud-foundry/src/store/types/user.types'; import { ListDataSource, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; @@ -11,6 +10,7 @@ import { ListConfig } from '../../../../../../../core/src/shared/components/list import { APIResource } from '../../../../../../../store/src/types/api.types'; import { PaginatedAction, PaginationEntityState } from '../../../../../../../store/src/types/pagination.types'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; +import { CfUser } from '../../../../../store/types/cf-user.types'; import { UserListUsersVisible, userListUserVisibleKey } from './cf-user-list-helpers'; function createUserVisibilityFilter(userHasRoles: (user: CfUser) => boolean): diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-config.service.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-config.service.spec.ts index 33f477573a..19f56eb01a 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-config.service.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-config.service.spec.ts @@ -3,7 +3,9 @@ import { Router } from '@angular/router'; import { Store } from '@ngrx/store'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; -import { CurrentUserPermissionsService } from '../../../../../../../core/src/core/current-user-permissions.service'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-config.service.ts index f070370166..23f7f500b9 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-config.service.ts @@ -4,11 +4,11 @@ import { BehaviorSubject, combineLatest, Observable, of as observableOf } from ' import { filter, first, map, switchMap, tap } from 'rxjs/operators'; import { UsersRolesSetUsers } from '../../../../../../../cloud-foundry/src/actions/users-roles.actions'; -import { GetAllUsersAsAdmin } from '../../../../../../../cloud-foundry/src/actions/users.actions'; +import { GetAllCfUsersAsAdmin } from '../../../../../../../cloud-foundry/src/actions/users.actions'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; -import { CfUser } from '../../../../../../../cloud-foundry/src/store/types/user.types'; -import { CurrentUserPermissionsChecker } from '../../../../../../../core/src/core/current-user-permissions.checker'; -import { CurrentUserPermissionsService } from '../../../../../../../core/src/core/current-user-permissions.service'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ITableColumn, ITableText } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; import { IListAction, @@ -33,6 +33,8 @@ import { hasSpaceRoleWithinOrg, waitForCFPermissions, } from '../../../../../features/cloud-foundry/cf.helpers'; +import { CfUser } from '../../../../../store/types/cf-user.types'; +import { CfUserPermissionsChecker } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { CfUserService } from './../../../../data-services/cf-user.service'; import { CfOrgPermissionCellComponent } from './cf-org-permission-cell/cf-org-permission-cell.component'; import { CfSpacePermissionCellComponent } from './cf-space-permission-cell/cf-space-permission-cell.component'; @@ -235,7 +237,7 @@ export class CfUserListConfigService extends ListConfig> { this.dataSource = new CfUserDataSourceService(store, action, this, userHasRoles); // Only show the filter (show users with/without roles) if the list of users can actually contain users without roles - if (GetAllUsersAsAdmin.is(action)) { + if (GetAllCfUsersAsAdmin.is(action)) { this.assignMultiConfig(); this.initialiseMultiFilter(action); } else { @@ -348,7 +350,7 @@ export class CfUserListConfigService extends ListConfig> { this.activeRouteCfOrgSpace.cfGuid, this.activeRouteCfOrgSpace.orgGuid, this.activeRouteCfOrgSpace.orgGuid && !this.activeRouteCfOrgSpace.spaceGuid ? - CurrentUserPermissionsChecker.ALL_SPACES : this.activeRouteCfOrgSpace.spaceGuid) + CfUserPermissionsChecker.ALL_SPACES : this.activeRouteCfOrgSpace.spaceGuid) private createCanUpdateOrgRoles = () => canUpdateOrgSpaceRoles( this.userPerms, diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-helpers.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-helpers.ts index 058d2df2d2..e0c964ae23 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-helpers.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-helpers.ts @@ -1,4 +1,4 @@ -import { CfUser } from '../../../../../../../cloud-foundry/src/store/types/user.types'; +import { CfUser } from '../../../../../store/types/cf-user.types'; export const userListUserVisibleKey = 'showUsers'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/detach-apps/detach-apps-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/detach-apps/detach-apps-data-source.ts index 6005c3711a..676cb211fc 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/detach-apps/detach-apps-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/detach-apps/detach-apps-data-source.ts @@ -1,5 +1,5 @@ import { Store } from '@ngrx/store'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { createEntityRelationKey, diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/service-instances/service-instances-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/service-instances/service-instances-data-source.ts index bd441ba537..b99fead8d1 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/service-instances/service-instances-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/service-instances/service-instances-data-source.ts @@ -1,5 +1,5 @@ import { Store } from '@ngrx/store'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/service-instances/service-instances-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/service-instances/service-instances-list-config.service.ts index 58637e424f..20878f7924 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/service-instances/service-instances-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/service-instances/service-instances-list-config.service.ts @@ -3,7 +3,9 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; -import { CurrentUserPermissionsService } from '../../../../../../../core/src/core/current-user-permissions.service'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ServicesService } from '../../../../../features/service-catalog/services.service'; import { ServiceActionHelperService } from '../../../../data-services/service-action-helper.service'; import { CfServiceInstancesListConfigBase } from '../cf-services/cf-service-instances-list-config.base'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/service-plans/service-plans-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/service-plans/service-plans-data-source.ts index f659096dc7..785edf555b 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/service-plans/service-plans-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/service-plans/service-plans-data-source.ts @@ -1,5 +1,5 @@ import { Store } from '@ngrx/store'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.ts index 0d82e721ec..649162074c 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.ts @@ -4,8 +4,9 @@ import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; import { CFAppState } from '../../../../../../../../cloud-foundry/src/cf-app-state'; import { serviceInstancesEntityType } from '../../../../../../../../cloud-foundry/src/cf-entity-types'; -import { CurrentUserPermissions } from '../../../../../../../../core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../../../../core/src/core/current-user-permissions.service'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { AppChip } from '../../../../../../../../core/src/shared/components/chips/chips.component'; import { MetaCardMenuItem, @@ -21,6 +22,7 @@ import { getServicePlanName, getServiceSummaryUrl, } from '../../../../../../features/service-catalog/services-helper'; +import { CfCurrentUserPermissions } from '../../../../../../user-permissions/cf-user-permissions-checkers'; import { ServiceActionHelperService } from '../../../../../data-services/service-action-helper.service'; import { CfOrgSpaceLabelService } from '../../../../../services/cf-org-space-label.service'; @@ -48,7 +50,7 @@ export class ServiceInstanceCardComponent extends CardCell endpoints && !!Object.keys(endpoints).length), publishReplay(1), diff --git a/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-user.service.ts b/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-user.service.ts index e96d52510c..30a0022d4b 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-user.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-user.service.ts @@ -7,15 +7,6 @@ import { CFAppState } from '../../../../cloud-foundry/src/cf-app-state'; import { cfUserEntityType, organizationEntityType, spaceEntityType } from '../../../../cloud-foundry/src/cf-entity-types'; import { createEntityRelationPaginationKey } from '../../../../cloud-foundry/src/entity-relations/entity-relations.types'; import { getCurrentUserCFGlobalStates } from '../../../../cloud-foundry/src/store/selectors/cf-current-user-role.selectors'; -import { - CfUser, - createUserRoleInOrg, - createUserRoleInSpace, - IUserPermissionInOrg, - IUserPermissionInSpace, - UserRoleInOrg, - UserRoleInSpace, -} from '../../../../cloud-foundry/src/store/types/user.types'; import { LocalPaginationHelpers, } from '../../../../core/src/shared/components/list/data-sources-controllers/local-list.helpers'; @@ -48,6 +39,15 @@ import { waitForCFPermissions, } from '../../features/cloud-foundry/cf.helpers'; import { selectCfPaginationState } from '../../store/selectors/pagination.selectors'; +import { + CfUser, + createUserRoleInOrg, + createUserRoleInSpace, + IUserPermissionInOrg, + IUserPermissionInSpace, + UserRoleInOrg, + UserRoleInSpace, +} from '../../store/types/cf-user.types'; @Injectable() export class CfUserService { diff --git a/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/github-scm.ts b/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/github-scm.ts index 476eded13e..5c6180fb38 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/github-scm.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/github-scm.ts @@ -78,4 +78,8 @@ export class GitHubSCM implements GitSCM { ); } + public convertCommit(projectName: string, commit: any): GitCommit { + return commit; + } + } diff --git a/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/gitlab-scm.ts b/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/gitlab-scm.ts index dacdc76916..0d6b210764 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/gitlab-scm.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/gitlab-scm.ts @@ -134,7 +134,7 @@ export class GitLabSCM implements GitSCM { }; } - private convertCommit(projectName: string, commit: any): GitCommit { + public convertCommit(projectName: string, commit: any): GitCommit { const emailMD5 = Md5.hashStr(commit.author_email); const avatarURL = `https://secure.gravatar.com/avatar/${emailMD5}?s=120&d=identicon`; diff --git a/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/scm.ts b/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/scm.ts index 27aab218dd..af4d83f7cc 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/scm.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/scm.ts @@ -18,6 +18,7 @@ export interface GitSCM { getBranch(httpClient: HttpClient, projectName: string, branchId: string): Observable; getBranches(httpClient: HttpClient, projectName: string): Observable; getCommit(httpClient: HttpClient, projectName: string, commitSha: string): Observable; + convertCommit(projectName: string, commit: any): GitCommit getCommits(httpClient: HttpClient, projectName: string, commitSha: string): Observable; getCloneURL(projectName: string): string; getCommitURL(projectName: string, commitSha: string): string; diff --git a/src/frontend/packages/cloud-foundry/src/shared/directives/app-name-unique.directive/app-name-unique.directive.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/directives/app-name-unique.directive/app-name-unique.directive.spec.ts index d19c066a77..51a8e0ee13 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/directives/app-name-unique.directive/app-name-unique.directive.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/directives/app-name-unique.directive/app-name-unique.directive.spec.ts @@ -5,7 +5,7 @@ import { inject, TestBed } from '@angular/core/testing'; import { MatDialogModule } from '@angular/material/dialog'; import { RouterTestingModule } from '@angular/router/testing'; import { Store } from '@ngrx/store'; -import { createBasicStoreModule } from '@stratos/store/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreModule } from '../../../../../core/src/core/core.module'; import { ExtensionService } from '../../../../../core/src/core/extension/extension-service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/directives/cf-user-permission/cf-user-permission.directive.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/directives/cf-user-permission/cf-user-permission.directive.spec.ts new file mode 100644 index 0000000000..f5eb80d06d --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/directives/cf-user-permission/cf-user-permission.directive.spec.ts @@ -0,0 +1,34 @@ +import { Component, TemplateRef } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BaseTestModules } from '../../../../../core/test-framework/core-test.helper'; + + +@Component({ + template: `` +}) +class TestUserPermissionComponent { +} + +class MockTemplateRef { } + +describe('CfUserPermissionDirective', () => { + let component: TestUserPermissionComponent; + let fixture: ComponentFixture; + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + ...BaseTestModules + ], + providers: [ + { provide: TemplateRef, useClass: MockTemplateRef }, + ], + declarations: [TestUserPermissionComponent] + }); + fixture = TestBed.createComponent(TestUserPermissionComponent); + component = fixture.componentInstance; + }); + it('should create an instance', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/cloud-foundry/src/shared/directives/cf-user-permission/cf-user-permission.directive.ts b/src/frontend/packages/cloud-foundry/src/shared/directives/cf-user-permission/cf-user-permission.directive.ts new file mode 100644 index 0000000000..283767d5f4 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/directives/cf-user-permission/cf-user-permission.directive.ts @@ -0,0 +1,80 @@ +import { Directive, Input, OnDestroy, OnInit, TemplateRef, ViewContainerRef } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { Observable, of as observableOf, Subscription } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; + +import { CurrentUserPermissionsService } from '../../../../../core/src/core/permissions/current-user-permissions.service'; +import { AppState } from '../../../../../store/src/app-state'; +import { waitForCFPermissions } from '../../../features/cloud-foundry/cf.helpers'; +import { CfCurrentUserPermissions } from '../../../user-permissions/cf-user-permissions-checkers'; + +@Directive({ + selector: '[appCfUserPermission]' +}) +export class CfUserPermissionDirective implements OnDestroy, OnInit { + @Input() + public appCfUserPermission: CfCurrentUserPermissions; + + @Input() + public appCfUserPermissionEndpointGuid: string; + + @Input() + private appCfUserPermissionOrganizationGuid: string; + + @Input() + private appCfUserPermissionSpaceGuid: string; + + private canSub: Subscription; + + constructor( + private store: Store, + private templateRef: TemplateRef, + private viewContainer: ViewContainerRef, + private currentUserPermissionsService: CurrentUserPermissionsService, + ) { } + + public ngOnInit() { + this.canSub = this.waitForEndpointPermissions(this.appCfUserPermissionEndpointGuid).pipe( + switchMap(() => this.currentUserPermissionsService.can( + this.appCfUserPermission, + this.appCfUserPermissionEndpointGuid, + this.getOrgOrSpaceGuid(), + this.getSpaceGuid() + )) + ).subscribe( + can => { + if (can) { + this.viewContainer.createEmbeddedView(this.templateRef); + } else { + this.viewContainer.clear(); + } + } + ); + } + + private waitForEndpointPermissions(endpointGuid: string): Observable { + return endpointGuid && endpointGuid.length > 0 ? waitForCFPermissions(this.store, endpointGuid) : observableOf(true); + } + + public ngOnDestroy() { + if (this.canSub) { + this.canSub.unsubscribe(); + } + } + + private getOrgOrSpaceGuid() { + if (this.appCfUserPermissionSpaceGuid && !this.appCfUserPermissionOrganizationGuid) { + return this.appCfUserPermissionSpaceGuid; + } + return this.appCfUserPermissionOrganizationGuid; + } + + private getSpaceGuid() { + if (this.appCfUserPermissionOrganizationGuid) { + return this.appCfUserPermissionSpaceGuid; + } + return null; + } + + +} diff --git a/src/frontend/packages/core/src/core/current-user-permissions.service.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/services/current-user-permissions.service.spec.ts similarity index 62% rename from src/frontend/packages/core/src/core/current-user-permissions.service.spec.ts rename to src/frontend/packages/cloud-foundry/src/shared/services/current-user-permissions.service.spec.ts index 3e4184df98..99dcb96deb 100644 --- a/src/frontend/packages/core/src/core/current-user-permissions.service.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/services/current-user-permissions.service.spec.ts @@ -1,33 +1,34 @@ import { TestBed } from '@angular/core/testing'; -import { createBasicStoreModule, createEntityStoreState, TestStoreEntity } from '@stratos/store/testing'; +import { createBasicStoreModule, createEntityStoreState, TestStoreEntity } from '@stratosui/store/testing'; import { first, tap } from 'rxjs/operators'; -import { CFFeatureFlagTypes, IFeatureFlag } from '../../../cloud-foundry/src/cf-api.types'; -import { cfEntityFactory } from '../../../cloud-foundry/src/cf-entity-factory'; -import { generateCFEntities } from '../../../cloud-foundry/src/cf-entity-generator'; -import { featureFlagEntityType } from '../../../cloud-foundry/src/cf-entity-types'; -import { AppState } from '../../../store/src/app-state'; -import { EntityCatalogTestModule, TEST_CATALOGUE_ENTITIES } from '../../../store/src/entity-catalog-test.module'; -import { EntityCatalogEntityConfig } from '../../../store/src/entity-catalog/entity-catalog.types'; -import { APIResource } from '../../../store/src/types/api.types'; -import { EndpointModel } from '../../../store/src/types/endpoint.types'; -import { BaseEntityValues } from '../../../store/src/types/entity.types'; -import { PaginationState } from '../../../store/src/types/pagination.types'; -import { AppTestModule } from '../../test-framework/core-test.helper'; -import { endpointEntitySchema } from '../base-entity-schemas'; -import { generateStratosEntities } from '../base-entity-types'; +import { CFFeatureFlagTypes, IFeatureFlag } from '../../../../cloud-foundry/src/cf-api.types'; +import { cfEntityFactory } from '../../../../cloud-foundry/src/cf-entity-factory'; +import { generateCFEntities } from '../../../../cloud-foundry/src/cf-entity-generator'; +import { featureFlagEntityType } from '../../../../cloud-foundry/src/cf-entity-types'; import { - CurrentUserPermissions, - PermissionConfig, - PermissionStrings, - PermissionTypes, - ScopeStrings, -} from './current-user-permissions.config'; -import { CurrentUserPermissionsService } from './current-user-permissions.service'; + CfCurrentUserPermissions, + cfCurrentUserPermissionsService, + CfPermissionTypes, + CfScopeStrings, +} from '../../../../cloud-foundry/src/user-permissions/cf-user-permissions-checkers'; +import { endpointEntitySchema } from '../../../../core/src/base-entity-schemas'; +import { generateStratosEntities } from '../../../../core/src/base-entity-types'; +import { PermissionConfig } from '../../../../core/src/core/permissions/current-user-permissions.config'; +import { CurrentUserPermissionsService } from '../../../../core/src/core/permissions/current-user-permissions.service'; +import { StratosScopeStrings } from '../../../../core/src/core/permissions/stratos-user-permissions.checker'; +import { AppTestModule } from '../../../../core/test-framework/core-test.helper'; +import { AppState } from '../../../../store/src/app-state'; +import { EntityCatalogTestModule, TEST_CATALOGUE_ENTITIES } from '../../../../store/src/entity-catalog-test.module'; +import { EntityCatalogEntityConfig } from '../../../../store/src/entity-catalog/entity-catalog.types'; +import { APIResource } from '../../../../store/src/types/api.types'; +import { EndpointModel } from '../../../../store/src/types/endpoint.types'; +import { BaseEntityValues } from '../../../../store/src/types/entity.types'; +import { PaginationState } from '../../../../store/src/types/pagination.types'; const ffSchema = cfEntityFactory(featureFlagEntityType); -describe('CurrentUserPermissionsService', () => { +describe('CurrentUserPermissionsService with CF checker', () => { let service: CurrentUserPermissionsService; @@ -58,9 +59,9 @@ describe('CurrentUserPermissionsService', () => { name: 'nathan', admin: false, scopes: [ - ScopeStrings.CF_WRITE_SCOPE, - ScopeStrings.STRATOS_CHANGE_PASSWORD, - ScopeStrings.CF_READ_SCOPE, + CfScopeStrings.CF_WRITE_SCOPE, + StratosScopeStrings.STRATOS_CHANGE_PASSWORD, + CfScopeStrings.CF_READ_SCOPE, ] }, metricsAvailable: false, @@ -92,13 +93,13 @@ describe('CurrentUserPermissionsService', () => { name: 'admin', admin: true, scopes: [ - ScopeStrings.CF_WRITE_SCOPE, - ScopeStrings.STRATOS_CHANGE_PASSWORD, - ScopeStrings.CF_READ_SCOPE, - ScopeStrings.CF_ADMIN_GROUP, - ScopeStrings.CF_READ_ONLY_ADMIN_GROUP, - ScopeStrings.CF_ADMIN_GLOBAL_AUDITOR_GROUP, - ScopeStrings.SCIM_READ + CfScopeStrings.CF_WRITE_SCOPE, + StratosScopeStrings.STRATOS_CHANGE_PASSWORD, + CfScopeStrings.CF_READ_SCOPE, + CfScopeStrings.CF_ADMIN_GROUP, + CfScopeStrings.CF_READ_ONLY_ADMIN_GROUP, + CfScopeStrings.CF_ADMIN_GLOBAL_AUDITOR_GROUP, + StratosScopeStrings.SCIM_READ ] }, metricsAvailable: false, @@ -607,291 +608,292 @@ describe('CurrentUserPermissionsService', () => { currentUserRoles: { internal: { isAdmin: false, - scopes: [ - ScopeStrings.CF_ADMIN_GROUP, - ScopeStrings.CF_READ_ONLY_ADMIN_GROUP, - ScopeStrings.CF_ADMIN_GLOBAL_AUDITOR_GROUP, - ScopeStrings.CF_WRITE_SCOPE, - ScopeStrings.CF_READ_SCOPE, - ScopeStrings.STRATOS_CHANGE_PASSWORD, - ScopeStrings.SCIM_READ + // CfScopeStrings.CF_ADMIN_GROUP, + // CfScopeStrings.CF_READ_ONLY_ADMIN_GROUP, + // CfScopeStrings.CF_ADMIN_GLOBAL_AUDITOR_GROUP, + // CfScopeStrings.CF_WRITE_SCOPE, + // CfScopeStrings.CF_READ_SCOPE, + StratosScopeStrings.STRATOS_CHANGE_PASSWORD, + StratosScopeStrings.SCIM_READ ], }, - cf: { - '0e934dc8-7ad4-40ff-b85c-53c1b61d2abb': { - global: { - isAdmin: false, - isReadOnlyAdmin: false, - isGlobalAuditor: false, - canRead: true, - canWrite: true, - scopes: [ - 'cloud_controller.read', - 'password.write', - 'cloud_controller.write', - 'openid', - 'uaa.user' - ] - }, - spaces: { - '56eb5ecc-7c96-4bb1-bdcc-0c6c3d444dc6': { - orgId: 'abc', - isManager: true, - isAuditor: false, - isDeveloper: true - } - }, - organizations: { - 'd5e50b05-497f-4b3b-9658-a396a592a8ba': { - isManager: false, - isAuditor: false, - isBillingManager: false, - isUser: true, - spaceGuids: [] + endpoints: { + cf: { + '0e934dc8-7ad4-40ff-b85c-53c1b61d2abb': { + global: { + isAdmin: false, + isReadOnlyAdmin: false, + isGlobalAuditor: false, + canRead: true, + canWrite: true, + scopes: [ + 'cloud_controller.read', + 'password.write', + 'cloud_controller.write', + 'openid', + 'uaa.user' + ] }, - 'c58e7cfd-c765-400a-a473-313fa572d5c4': { - isManager: false, - isAuditor: false, - isBillingManager: false, - isUser: true, - spaceGuids: [] - } - }, - state: { - initialised: true, - fetching: false, - error: null - } - }, - 'c80420ca-204b-4879-bf69-b6b7a202ad87': { - global: { - isAdmin: false, - isReadOnlyAdmin: false, - isGlobalAuditor: false, - canRead: true, - canWrite: true, - scopes: [ - 'openid', - 'scim.read', - 'cloud_controller.admin', - 'uaa.user', - 'routing.router_groups.read', - 'cloud_controller.read', - 'password.write', - 'cloud_controller.write', - 'doppler.firehose', - 'scim.write' - ] - }, - spaces: { - '56eb5ecc-7c96-4bb1-bdcc-0c6c3d444dc6': { - isManager: true, - isAuditor: false, - isDeveloper: true, - orgId: 'abc' + spaces: { + '56eb5ecc-7c96-4bb1-bdcc-0c6c3d444dc6': { + orgId: 'abc', + isManager: true, + isAuditor: false, + isDeveloper: true + } }, - 'c6450a21-aa1a-4643-9437-035cc818ea72': { - isManager: true, - isAuditor: false, - isDeveloper: true, - orgId: 'abc' + organizations: { + 'd5e50b05-497f-4b3b-9658-a396a592a8ba': { + isManager: false, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + 'c58e7cfd-c765-400a-a473-313fa572d5c4': { + isManager: false, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + } }, - '86577124-4b64-4ca1-9a78-d904c60505c4': { - isManager: true, - isAuditor: false, - isDeveloper: true, - orgId: 'abc' + state: { + initialised: true, + fetching: false, + error: null } }, - organizations: { - '367a49c1-b5dc-44e6-a8cf-84b1f56426a7': { - isManager: true, - isAuditor: false, - isBillingManager: false, - isUser: true, - spaceGuids: [] - }, - 'dccfedde-be2c-46a6-99cf-c1320ea8cb6d': { - isManager: true, - isAuditor: false, - isBillingManager: false, - isUser: true, - spaceGuids: [] - }, - '8a175cad-ff61-436b-8c6f-e5beb13edb5f': { - isManager: true, - isAuditor: false, - isBillingManager: false, - isUser: true, - spaceGuids: [] + 'c80420ca-204b-4879-bf69-b6b7a202ad87': { + global: { + isAdmin: false, + isReadOnlyAdmin: false, + isGlobalAuditor: false, + canRead: true, + canWrite: true, + scopes: [ + 'openid', + 'scim.read', + 'cloud_controller.admin', + 'uaa.user', + 'routing.router_groups.read', + 'cloud_controller.read', + 'password.write', + 'cloud_controller.write', + 'doppler.firehose', + 'scim.write' + ] }, - 'd5246255-867b-4f62-9040-346f113f0b7d': { - isManager: true, - isAuditor: false, - isBillingManager: false, - isUser: true, - spaceGuids: [] - } - }, - state: { - initialised: true, - fetching: false, - error: null - } - }, - READ_ONLY_ADMIN: { - global: { - isAdmin: false, - isReadOnlyAdmin: true, - isGlobalAuditor: false, - canRead: true, - canWrite: true, - scopes: [ - 'openid', - 'scim.read', - 'cloud_controller.admin', - 'uaa.user', - 'routing.router_groups.read', - 'cloud_controller.read', - 'password.write', - 'cloud_controller.write', - 'doppler.firehose', - 'scim.write' - ] - }, - spaces: { - '56eb5ecc-7c96-4bb1-bdcc-0c6c3d444dc6': { - orgId: 'abc', - isManager: true, - isAuditor: false, - isDeveloper: true + spaces: { + '56eb5ecc-7c96-4bb1-bdcc-0c6c3d444dc6': { + isManager: true, + isAuditor: false, + isDeveloper: true, + orgId: 'abc' + }, + 'c6450a21-aa1a-4643-9437-035cc818ea72': { + isManager: true, + isAuditor: false, + isDeveloper: true, + orgId: 'abc' + }, + '86577124-4b64-4ca1-9a78-d904c60505c4': { + isManager: true, + isAuditor: false, + isDeveloper: true, + orgId: 'abc' + } }, - 'c6450a21-aa1a-4643-9437-035cc818ea72': { - orgId: 'abc', - isManager: true, - isAuditor: false, - isDeveloper: true + organizations: { + '367a49c1-b5dc-44e6-a8cf-84b1f56426a7': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + 'dccfedde-be2c-46a6-99cf-c1320ea8cb6d': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + '8a175cad-ff61-436b-8c6f-e5beb13edb5f': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + 'd5246255-867b-4f62-9040-346f113f0b7d': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + } }, - '86577124-4b64-4ca1-9a78-d904c60505c4': { - orgId: 'abc', - isManager: true, - isAuditor: false, - isDeveloper: true + state: { + initialised: true, + fetching: false, + error: null } }, - organizations: { - '367a49c1-b5dc-44e6-a8cf-84b1f56426a7': { - isManager: true, - isAuditor: false, - isBillingManager: false, - isUser: true, - spaceGuids: [] + READ_ONLY_ADMIN: { + global: { + isAdmin: false, + isReadOnlyAdmin: true, + isGlobalAuditor: false, + canRead: true, + canWrite: true, + scopes: [ + 'openid', + 'scim.read', + 'cloud_controller.admin', + 'uaa.user', + 'routing.router_groups.read', + 'cloud_controller.read', + 'password.write', + 'cloud_controller.write', + 'doppler.firehose', + 'scim.write' + ] }, - 'dccfedde-be2c-46a6-99cf-c1320ea8cb6d': { - isManager: true, - isAuditor: false, - isBillingManager: false, - isUser: true, - spaceGuids: [] + spaces: { + '56eb5ecc-7c96-4bb1-bdcc-0c6c3d444dc6': { + orgId: 'abc', + isManager: true, + isAuditor: false, + isDeveloper: true + }, + 'c6450a21-aa1a-4643-9437-035cc818ea72': { + orgId: 'abc', + isManager: true, + isAuditor: false, + isDeveloper: true + }, + '86577124-4b64-4ca1-9a78-d904c60505c4': { + orgId: 'abc', + isManager: true, + isAuditor: false, + isDeveloper: true + } }, - '8a175cad-ff61-436b-8c6f-e5beb13edb5f': { - isManager: true, - isAuditor: false, - isBillingManager: false, - isUser: true, - spaceGuids: [] + organizations: { + '367a49c1-b5dc-44e6-a8cf-84b1f56426a7': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + 'dccfedde-be2c-46a6-99cf-c1320ea8cb6d': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + '8a175cad-ff61-436b-8c6f-e5beb13edb5f': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + 'd5246255-867b-4f62-9040-346f113f0b7d': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + } }, - 'd5246255-867b-4f62-9040-346f113f0b7d': { - isManager: true, - isAuditor: false, - isBillingManager: false, - isUser: true, - spaceGuids: [] + state: { + initialised: true, + fetching: false, + error: null } }, - state: { - initialised: true, - fetching: false, - error: null - } - }, - READ_ONLY_USER: { - global: { - isAdmin: false, - isReadOnlyAdmin: false, - isGlobalAuditor: false, - canRead: true, - canWrite: false, - scopes: [ - 'openid', - 'scim.read', - 'cloud_controller.admin', - 'uaa.user', - 'routing.router_groups.read', - 'cloud_controller.read', - 'password.write', - 'cloud_controller.write', - 'doppler.firehose', - 'scim.write' - ] - }, - spaces: { - '56eb5ecc-7c96-4bb1-bdcc-0c6c3d444dc6': { - isManager: true, - isAuditor: false, - isDeveloper: true, - orgId: 'abc' + READ_ONLY_USER: { + global: { + isAdmin: false, + isReadOnlyAdmin: false, + isGlobalAuditor: false, + canRead: true, + canWrite: false, + scopes: [ + 'openid', + 'scim.read', + 'cloud_controller.admin', + 'uaa.user', + 'routing.router_groups.read', + 'cloud_controller.read', + 'password.write', + 'cloud_controller.write', + 'doppler.firehose', + 'scim.write' + ] }, - 'c6450a21-aa1a-4643-9437-035cc818ea72': { - isManager: true, - isAuditor: false, - isDeveloper: true, - orgId: 'abc' - }, - '86577124-4b64-4ca1-9a78-d904c60505c4': { - isManager: true, - isAuditor: false, - isDeveloper: true, - orgId: 'abc' - } - }, - organizations: { - '367a49c1-b5dc-44e6-a8cf-84b1f56426a7': { - isManager: true, - isAuditor: false, - isBillingManager: false, - isUser: true, - spaceGuids: [] + spaces: { + '56eb5ecc-7c96-4bb1-bdcc-0c6c3d444dc6': { + isManager: true, + isAuditor: false, + isDeveloper: true, + orgId: 'abc' + }, + 'c6450a21-aa1a-4643-9437-035cc818ea72': { + isManager: true, + isAuditor: false, + isDeveloper: true, + orgId: 'abc' + }, + '86577124-4b64-4ca1-9a78-d904c60505c4': { + isManager: true, + isAuditor: false, + isDeveloper: true, + orgId: 'abc' + } }, - 'dccfedde-be2c-46a6-99cf-c1320ea8cb6d': { - isManager: true, - isAuditor: false, - isBillingManager: false, - isUser: true, - spaceGuids: [] + organizations: { + '367a49c1-b5dc-44e6-a8cf-84b1f56426a7': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + 'dccfedde-be2c-46a6-99cf-c1320ea8cb6d': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + '8a175cad-ff61-436b-8c6f-e5beb13edb5f': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + 'd5246255-867b-4f62-9040-346f113f0b7d': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + } }, - '8a175cad-ff61-436b-8c6f-e5beb13edb5f': { - isManager: true, - isAuditor: false, - isBillingManager: false, - isUser: true, - spaceGuids: [] - }, - 'd5246255-867b-4f62-9040-346f113f0b7d': { - isManager: true, - isAuditor: false, - isBillingManager: false, - isUser: true, - spaceGuids: [] + state: { + initialised: true, + fetching: false, + error: null } - }, - state: { - initialised: true, - fetching: false, - error: null } - } + }, }, state: { initialised: true, @@ -913,7 +915,7 @@ describe('CurrentUserPermissionsService', () => { beforeEach(() => { TestBed.configureTestingModule({ providers: [ - CurrentUserPermissionsService, + ...cfCurrentUserPermissionsService ], imports: [ { @@ -928,7 +930,7 @@ describe('CurrentUserPermissionsService', () => { ] }, createBasicStoreModule(createStoreState()), - AppTestModule + AppTestModule, ], }); @@ -940,7 +942,7 @@ describe('CurrentUserPermissionsService', () => { }); it('should allow create application', done => { - service.can(CurrentUserPermissions.APPLICATION_CREATE).pipe( + service.can(CfCurrentUserPermissions.APPLICATION_CREATE).pipe( tap(can => { expect(can).toBe(true); done(); @@ -950,7 +952,7 @@ describe('CurrentUserPermissionsService', () => { }); it('should allow create application for single endpoint with access', done => { - service.can(CurrentUserPermissions.APPLICATION_CREATE, '0e934dc8-7ad4-40ff-b85c-53c1b61d2abb').pipe( + service.can(CfCurrentUserPermissions.APPLICATION_CREATE, '0e934dc8-7ad4-40ff-b85c-53c1b61d2abb').pipe( tap(can => { expect(can).toBe(true); done(); @@ -961,7 +963,7 @@ describe('CurrentUserPermissionsService', () => { it('should allow create application for single endpoint with access and org/space', done => { service.can( - CurrentUserPermissions.APPLICATION_CREATE, + CfCurrentUserPermissions.APPLICATION_CREATE, 'c80420ca-204b-4879-bf69-b6b7a202ad87', '86577124-4b64-4ca1-9a78-d904c60505c4' ).pipe( @@ -975,7 +977,7 @@ describe('CurrentUserPermissionsService', () => { it('should allow if feature flag', done => { service.can( - [new PermissionConfig(PermissionTypes.FEATURE_FLAG, CFFeatureFlagTypes.private_domain_creation)] + [new PermissionConfig(CfPermissionTypes.FEATURE_FLAG, CFFeatureFlagTypes.private_domain_creation)] ).pipe( tap(can => { expect(can).toBe(true); @@ -987,7 +989,7 @@ describe('CurrentUserPermissionsService', () => { it('should allow if feature flag with cf', done => { service.can( - [new PermissionConfig(PermissionTypes.FEATURE_FLAG, CFFeatureFlagTypes.private_domain_creation)], + [new PermissionConfig(CfPermissionTypes.FEATURE_FLAG, CFFeatureFlagTypes.private_domain_creation)], 'c80420ca-204b-4879-bf69-b6b7a202ad87' ).pipe( tap(can => { @@ -1000,7 +1002,7 @@ describe('CurrentUserPermissionsService', () => { it('should not allow if no feature flag', done => { service.can( - [new PermissionConfig(PermissionTypes.FEATURE_FLAG, CFFeatureFlagTypes.user_org_creation)], + [new PermissionConfig(CfPermissionTypes.FEATURE_FLAG, CFFeatureFlagTypes.user_org_creation)], 'c80420ca-204b-4879-bf69-b6b7a202ad87' ).pipe( tap(can => { @@ -1011,36 +1013,9 @@ describe('CurrentUserPermissionsService', () => { ).subscribe(); }); - it('should allow if stratos admin', done => { - service.can(new PermissionConfig(PermissionTypes.STRATOS, PermissionStrings.STRATOS_ADMIN)).pipe( - tap(can => { - expect(can).toBe(false); - done(); - }), - first() - ).subscribe(); - }); - - it('should allow if has stratos change password scope', done => { - service.can(new PermissionConfig(PermissionTypes.STRATOS_SCOPE, ScopeStrings.STRATOS_CHANGE_PASSWORD)).pipe( - tap(can => { - expect(can).toBe(true); - done(); - }), - first() - ).subscribe(); - - service.can([new PermissionConfig(PermissionTypes.STRATOS_SCOPE, ScopeStrings.STRATOS_CHANGE_PASSWORD)]).pipe( - tap(can => { - expect(can).toBe(true); - done(); - }), - first() - ).subscribe(); - }); it('should allow if has endpoint scope', done => { - service.can(new PermissionConfig(PermissionTypes.ENDPOINT_SCOPE, ScopeStrings.SCIM_READ), 'c80420ca-204b-4879-bf69-b6b7a202ad87').pipe( + service.can(new PermissionConfig(CfPermissionTypes.ENDPOINT_SCOPE, StratosScopeStrings.SCIM_READ), 'c80420ca-204b-4879-bf69-b6b7a202ad87').pipe( tap(can => { expect(can).toBe(true); done(); @@ -1050,7 +1025,7 @@ describe('CurrentUserPermissionsService', () => { }); it('should not allow if has endpoint scope', done => { - service.can(new PermissionConfig(PermissionTypes.ENDPOINT_SCOPE, ScopeStrings.SCIM_READ), '0e934dc8-7ad4-40ff-b85c-53c1b61d2abb').pipe( + service.can(new PermissionConfig(CfPermissionTypes.ENDPOINT_SCOPE, StratosScopeStrings.SCIM_READ), '0e934dc8-7ad4-40ff-b85c-53c1b61d2abb').pipe( tap(can => { expect(can).toBe(false); done(); @@ -1061,7 +1036,7 @@ describe('CurrentUserPermissionsService', () => { it('should not allow if read only admin', done => { service.can( - CurrentUserPermissions.APPLICATION_CREATE, + CfCurrentUserPermissions.APPLICATION_CREATE, 'READ_ONLY_ADMIN', 'c6450a21-aa1a-4643-9437-035cc818ea72' ).pipe( @@ -1075,7 +1050,7 @@ describe('CurrentUserPermissionsService', () => { it('should not allow if read only user', done => { service.can( - CurrentUserPermissions.APPLICATION_CREATE, + CfCurrentUserPermissions.APPLICATION_CREATE, 'READ_ONLY_USER', 'c6450a21-aa1a-4643-9437-035cc818ea72' ).pipe( diff --git a/src/frontend/packages/cloud-foundry/src/store/cloud-foundry.reducers.module.ts b/src/frontend/packages/cloud-foundry/src/store/cloud-foundry.reducers.module.ts index 6d13912909..71da36d84f 100644 --- a/src/frontend/packages/cloud-foundry/src/store/cloud-foundry.reducers.module.ts +++ b/src/frontend/packages/cloud-foundry/src/store/cloud-foundry.reducers.module.ts @@ -1,15 +1,17 @@ import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; + +import { cfUsersRolesReducer } from './reducers/cf-users-roles.reducer'; import { createAppReducer } from './reducers/create-application.reducer'; -import { deployAppReducer } from './reducers/deploy-app.reducer'; import { createServiceInstanceReducer } from './reducers/create-service-instance.reducer'; -import { UsersRolesReducer } from './reducers/users-roles.reducer'; +import { deployAppReducer } from './reducers/deploy-app.reducer'; + @NgModule({ imports: [ StoreModule.forFeature('createApplication', createAppReducer), StoreModule.forFeature('deployApplication', deployAppReducer), StoreModule.forFeature('createServiceInstance', createServiceInstanceReducer), - StoreModule.forFeature('manageUsersRoles', UsersRolesReducer), + StoreModule.forFeature('manageUsersRoles', cfUsersRolesReducer), ] }) export class CloudFoundryReducersModule { } diff --git a/src/frontend/packages/cloud-foundry/src/store/cloud-foundry.store.module.ts b/src/frontend/packages/cloud-foundry/src/store/cloud-foundry.store.module.ts index cf172535cb..f2c9b707bd 100644 --- a/src/frontend/packages/cloud-foundry/src/store/cloud-foundry.store.module.ts +++ b/src/frontend/packages/cloud-foundry/src/store/cloud-foundry.store.module.ts @@ -8,11 +8,11 @@ import { CloudFoundryEffects } from './effects/cloud-foundry.effects'; import { CreateAppPageEffects } from './effects/create-app-effects'; import { DeployAppEffects } from './effects/deploy-app.effects'; import { GithubEffects } from './effects/github.effects'; -import { PermissionEffects, PermissionsEffects } from './effects/permissions.effect'; import { CfValidateEffects } from './effects/request.effects'; import { RouteEffect } from './effects/route.effects'; import { ServiceInstanceEffects } from './effects/service-instance.effects'; import { UpdateAppEffects } from './effects/update-app-effects'; +import { UsersRolesEffects } from './effects/users-roles.effects'; @NgModule({ imports: [ @@ -24,12 +24,11 @@ import { UpdateAppEffects } from './effects/update-app-effects'; GithubEffects, CloudFoundryEffects, RouteEffect, - PermissionsEffects, - PermissionEffects, ServiceInstanceEffects, AppEffects, UpdateAppEffects, - CfValidateEffects + CfValidateEffects, + UsersRolesEffects ]) ] }) diff --git a/src/frontend/packages/cloud-foundry/src/store/effects/app.effects.ts b/src/frontend/packages/cloud-foundry/src/store/effects/app.effects.ts index 60657b138d..76926a21da 100644 --- a/src/frontend/packages/cloud-foundry/src/store/effects/app.effects.ts +++ b/src/frontend/packages/cloud-foundry/src/store/effects/app.effects.ts @@ -7,7 +7,7 @@ import { endpointHasMetrics } from '../../../../core/src/features/endpoints/endp import { EndpointOnlyAppState } from '../../../../store/src/app-state'; import { APISuccessOrFailedAction } from '../../../../store/src/types/request.types'; import { ASSIGN_ROUTE_SUCCESS } from '../../actions/application-service-routes.actions'; -import { UPDATE_SUCCESS, UpdateExistingApplication } from '../../actions/application.actions'; +import { CF_APP_UPDATE_SUCCESS, UpdateExistingApplication } from '../../actions/application.actions'; import { cfEntityCatalog } from '../../cf-entity-catalog'; import { createAppInstancesMetricAction, @@ -29,7 +29,7 @@ export class AppEffects { ); @Effect({ dispatch: false }) clearCellMetrics$ = this.actions$.pipe( - ofType(UPDATE_SUCCESS), + ofType(CF_APP_UPDATE_SUCCESS), map(action => { // User's can scale down instances and previous instance data is kept in store, when the user scales up again this stale data can // be incorrectly shown straight away. In order to work around this fetch the latest metrics again when scaling up diff --git a/src/frontend/packages/cloud-foundry/src/store/effects/permissions.effect.ts b/src/frontend/packages/cloud-foundry/src/store/effects/permissions.effect.ts deleted file mode 100644 index 8c713def7c..0000000000 --- a/src/frontend/packages/cloud-foundry/src/store/effects/permissions.effect.ts +++ /dev/null @@ -1,274 +0,0 @@ -import { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; -import { Action, Store } from '@ngrx/store'; -import { endpointsCfEntitiesConnectedSelector } from 'frontend/packages/store/src/selectors/endpoint.selectors'; -import { combineLatest, Observable, of as observableOf } from 'rxjs'; -import { - catchError, - first, - map, - mergeMap, - pairwise, - share, - skipWhile, - switchMap, - tap, - withLatestFrom, -} from 'rxjs/operators'; - -import { LoggerService } from '../../../../core/src/core/logger.service'; -import { CONNECT_ENDPOINTS_SUCCESS, EndpointActionComplete } from '../../../../store/src/actions/endpoint.actions'; -import { entityCatalog } from '../../../../store/src/entity-catalog/entity-catalog'; -import { - BaseHttpClientFetcher, - flattenPagination, - PaginationFlattener, -} from '../../../../store/src/helpers/paginated-request-helpers'; -import { ActionState } from '../../../../store/src/reducers/api-request-reducer/types'; -import { selectPaginationState } from '../../../../store/src/selectors/pagination.selectors'; -import { EndpointModel, INewlyConnectedEndpointInfo } from '../../../../store/src/types/endpoint.types'; -import { BasePaginatedAction, PaginationEntityState } from '../../../../store/src/types/pagination.types'; -import { - GET_CURRENT_USER_CF_RELATIONS, - GET_CURRENT_USER_CF_RELATIONS_FAILED, - GET_CURRENT_USER_CF_RELATIONS_SUCCESS, - GET_CURRENT_USER_RELATION, - GET_CURRENT_USER_RELATIONS, - GET_CURRENT_USER_RELATIONS_FAILED, - GET_CURRENT_USER_RELATIONS_SUCCESS, - GetCurrentUserRelationsComplete, - GetCurrentUsersRelations, - GetUserCfRelations, - GetUserRelations, - UserRelationTypes, -} from '../../actions/permissions.actions'; -import { CFAppState } from '../../cf-app-state'; -import { cfEntityCatalog } from '../../cf-entity-catalog'; -import { CFResponse } from '../types/cf-api.types'; - -class PermissionFlattener extends BaseHttpClientFetcher implements PaginationFlattener { - - constructor(httpClient: HttpClient, public url, public requestOptions: { [key: string]: any }) { - super(httpClient, url, requestOptions, 'page'); - } - public getTotalPages = (res: CFResponse) => res.total_pages; - - public mergePages = (res: CFResponse[]) => { - const firstRes = res.shift(); - const final = res.reduce((finalRes, currentRes) => { - finalRes.resources = [ - ...finalRes.resources, - ]; - return finalRes; - }, firstRes); - return final; - } - public getTotalResults = (res: CFResponse): number => res.total_results; - public clearResults = (res: CFResponse) => observableOf(res); -} - -interface CfsRequestState { - [cfGuid: string]: Observable[]; -} - -interface IEndpointConnectionInfo { - guid: string; - userGuid: string; -} - -const successAction: Action = { type: GET_CURRENT_USER_RELATIONS_SUCCESS }; -const failedAction: Action = { type: GET_CURRENT_USER_RELATIONS_FAILED }; - -function fetchCfUserRole(store: Store, action: GetUserRelations, httpClient: HttpClient): Observable { - const url = `pp/v1/proxy/v2/users/${action.guid}/${action.relationType}`; - const params = { - headers: { - 'x-cap-cnsi-list': action.endpointGuid, - 'x-cap-passthrough': 'true' - }, - params: { - 'results-per-page': '100' - } - }; - const get$ = httpClient.get( - url, - params - ); - return flattenPagination( - (flatAction: Action) => this.store.dispatch(flatAction), - get$, - new PermissionFlattener(httpClient, url, params) - ).pipe( - map(data => { - store.dispatch(new GetCurrentUserRelationsComplete(action.relationType, action.endpointGuid, data.resources)); - return true; - }), - first(), - catchError(err => observableOf(false)), - share() - ); -} - -const fetchPaginationStateFromAction = (store: Store, action: BasePaginatedAction) => { - const entityKey = entityCatalog.getEntityKey(action); - return store.select(selectPaginationState(entityKey, action.paginationKey)); -}; - -/** - * Using the given action wait until the associated pagination section changes from busy to not busy - */ -const createPaginationCompleteWatcher = (store: Store, action: BasePaginatedAction): Observable => - fetchPaginationStateFromAction(store, action).pipe( - map((paginationState: PaginationEntityState) => { - const pageRequest: ActionState = - paginationState && paginationState.pageRequests && paginationState.pageRequests[paginationState.currentPage]; - return pageRequest ? pageRequest.busy : true; - }), - pairwise(), - map(([oldFetching, newFetching]) => { - return oldFetching === true && newFetching === false; - }), - skipWhile(completed => !completed), - first(), - ); - -@Injectable() -export class PermissionsEffects { - constructor( - private httpClient: HttpClient, - private actions$: Actions, - private store: Store, - private logService: LoggerService - ) { } - - @Effect() getCurrentUsersPermissions$ = this.actions$.pipe( - ofType(GET_CURRENT_USER_RELATIONS), - withLatestFrom(this.store.select(endpointsCfEntitiesConnectedSelector)), - switchMap(([action, endpoints]) => { - const endpointsArray = Object.values(endpoints); - const isAllAdmins = endpointsArray.every(endpoint => !!endpoint.user.admin); - - // If all endpoints are connected as admin, there's no permissions to fetch. So only update the permission state to initialised - if (isAllAdmins) { - return [ - successAction, - ...endpointsArray.map(endpoint => new GetUserCfRelations(endpoint.guid, GET_CURRENT_USER_CF_RELATIONS_SUCCESS)) - ]; - } - - // If some endpoints are not connected as admin, go out and fetch the current user's specific roles - const flagsAndRoleRequests = this.dispatchRoleRequests(endpointsArray); - const allRequestsCompleted = this.handleCfRequests(flagsAndRoleRequests); - return combineLatest(allRequestsCompleted).pipe( - switchMap(succeeds => succeeds.every(succeeded => !!succeeded) ? [successAction] : [failedAction]) - ); - }), - catchError(err => { - this.logService.warn('Failed to fetch current user permissions: ', err); - return observableOf([failedAction]); - }) - ); - - - @Effect() getPermissionForNewlyConnectedEndpoint$ = this.actions$.pipe( - ofType(CONNECT_ENDPOINTS_SUCCESS), - switchMap(action => { - const endpoint = action.endpoint as INewlyConnectedEndpointInfo; - if (endpoint.user.admin || action.endpointType !== 'cf') { - return endpoint.user.admin ? [new GetUserCfRelations(action.guid, GET_CURRENT_USER_CF_RELATIONS_SUCCESS)] : []; - } - - // START fetching cf roles for current user - this.store.dispatch(new GetUserCfRelations(action.guid, GET_CURRENT_USER_CF_RELATIONS)); - - return combineLatest(this.fetchCfUserRoles({ guid: action.guid, userGuid: endpoint.user.guid })).pipe( - // FINISH fetching cf roles for current user - mergeMap(succeeds => [new GetUserCfRelations( - action.guid, - succeeds.every(succeeded => !!succeeded) ? GET_CURRENT_USER_CF_RELATIONS_SUCCESS : GET_CURRENT_USER_CF_RELATIONS_FAILED - )]), - catchError(err => { - this.logService.warn('Failed to fetch current user permissions for a cf: ', err); - return [new GetUserCfRelations(action.guid, GET_CURRENT_USER_CF_RELATIONS_FAILED)]; - }) - ); - }) - ); - - - private dispatchRoleRequests(endpoints: EndpointModel[]): CfsRequestState { - const requests: CfsRequestState = {}; - - // Per endpoint fetch feature flags and user roles (unless admin, where we don't need to), then mark endpoint as initialised - endpoints.forEach(endpoint => { - if (endpoint.user.admin) { - // We don't need permissions for admin users (they can do everything) - requests[endpoint.guid] = [observableOf(true)]; - this.store.dispatch(new GetUserCfRelations(endpoint.guid, GET_CURRENT_USER_CF_RELATIONS_SUCCESS)); - } else { - // START fetching cf roles for current user - this.store.dispatch(new GetUserCfRelations(endpoint.guid, GET_CURRENT_USER_CF_RELATIONS)); - - // Dispatch feature flags fetch actions - const ffAction = cfEntityCatalog.featureFlag.actions.getMultiple(endpoint.guid) - requests[endpoint.guid] = [createPaginationCompleteWatcher(this.store, ffAction)]; - this.store.dispatch(ffAction); - - // Dispatch requests to fetch roles per role type for current user - requests[endpoint.guid].push(...this.fetchCfUserRoles({ guid: endpoint.guid, userGuid: endpoint.user.guid })); - - // FINISH fetching cf roles for current user - combineLatest(requests[endpoint.guid]).pipe( - first(), - tap(succeeds => { - this.store.dispatch(new GetUserCfRelations( - endpoint.guid, - succeeds.every(succeeded => !!succeeded) ? GET_CURRENT_USER_CF_RELATIONS_SUCCESS : GET_CURRENT_USER_CF_RELATIONS_FAILED) - ); - }), - catchError(err => { - this.logService.warn('Failed to fetch current user permissions for a cf: ', err); - this.store.dispatch(new GetUserCfRelations(endpoint.guid, GET_CURRENT_USER_CF_RELATIONS_FAILED)); - return observableOf(err); - }) - ).subscribe(); - } - }); - return requests; - } - - private handleCfRequests(requests: CfsRequestState): Observable[] { - const allCompleted: Observable[] = []; - Object.keys(requests).forEach(cfGuid => { - const successes = requests[cfGuid]; - allCompleted.push(...successes); - }); - return allCompleted; - } - - fetchCfUserRoles(endpoint: IEndpointConnectionInfo): Observable[] { - return Object.values(UserRelationTypes).map((type: UserRelationTypes) => { - const relAction = new GetUserRelations(endpoint.userGuid, type, endpoint.guid); - return fetchCfUserRole(this.store, relAction, this.httpClient); - }); - } -} - -@Injectable() -export class PermissionEffects { - constructor( - private httpClient: HttpClient, - private actions$: Actions, - private store: Store - ) { } - - @Effect() getCurrentUsersPermissions$ = this.actions$.pipe( - ofType(GET_CURRENT_USER_RELATION), - map(action => { - return fetchCfUserRole(this.store, action, this.httpClient).pipe( - map((success) => ({ type: action.actions[1] })) - ); - }) - ); -} diff --git a/src/frontend/packages/cloud-foundry/src/store/effects/update-app-effects.ts b/src/frontend/packages/cloud-foundry/src/store/effects/update-app-effects.ts index a29f72b026..7c080b69e1 100644 --- a/src/frontend/packages/cloud-foundry/src/store/effects/update-app-effects.ts +++ b/src/frontend/packages/cloud-foundry/src/store/effects/update-app-effects.ts @@ -4,7 +4,7 @@ import { mergeMap } from 'rxjs/operators'; import { WrapperRequestActionSuccess } from '../../../../store/src/types/request.types'; import { AppMetadataTypes } from '../../actions/app-metadata.actions'; -import { UPDATE_SUCCESS, UpdateExistingApplication } from '../../actions/application.actions'; +import { CF_APP_UPDATE_SUCCESS, UpdateExistingApplication } from '../../actions/application.actions'; import { cfEntityCatalog } from '../../cf-entity-catalog'; @Injectable() @@ -16,7 +16,7 @@ export class UpdateAppEffects { } @Effect() UpdateAppInStore$ = this.actions$.pipe( - ofType(UPDATE_SUCCESS), + ofType(CF_APP_UPDATE_SUCCESS), mergeMap((action: WrapperRequestActionSuccess) => { const updateAction = action.apiAction as UpdateExistingApplication; const updateEntities = updateAction.updateEntities || [AppMetadataTypes.ENV_VARS, AppMetadataTypes.STATS, AppMetadataTypes.SUMMARY]; diff --git a/src/frontend/packages/store/src/effects/users-roles.effects.ts b/src/frontend/packages/cloud-foundry/src/store/effects/users-roles.effects.ts similarity index 78% rename from src/frontend/packages/store/src/effects/users-roles.effects.ts rename to src/frontend/packages/cloud-foundry/src/store/effects/users-roles.effects.ts index 640bebb891..7ed0d48c09 100644 --- a/src/frontend/packages/store/src/effects/users-roles.effects.ts +++ b/src/frontend/packages/cloud-foundry/src/store/effects/users-roles.effects.ts @@ -1,3 +1,4 @@ +import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Actions, Effect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; @@ -7,37 +8,46 @@ import { import { combineLatest as observableCombineLatest, combineLatest, Observable, of as observableOf, of } from 'rxjs'; import { catchError, filter, first, map, mergeMap, pairwise, switchMap, tap, withLatestFrom } from 'rxjs/operators'; -import { - UsersRolesActions, - UsersRolesClearUpdateState, - UsersRolesExecuteChanges, -} from '../../../cloud-foundry/src/actions/users-roles.actions'; -import { AddUserRole, ChangeUserRole, RemoveUserRole } from '../../../cloud-foundry/src/actions/users.actions'; -import { CFAppState } from '../../../cloud-foundry/src/cf-app-state'; -import { organizationEntityType, spaceEntityType } from '../../../cloud-foundry/src/cf-entity-types'; -import { CF_ENDPOINT_TYPE } from '../../../cloud-foundry/src/cf-types'; -import { CfUserService } from '../../../cloud-foundry/src/shared/data-services/cf-user.service'; -import { OrgUserRoleNames } from '../../../cloud-foundry/src/store/types/user.types'; -import { CfRoleChange, UsersRolesState } from '../../../cloud-foundry/src/store/types/users-roles.types'; -import { ResetPagination } from '../actions/pagination.actions'; -import { entityCatalog } from '../entity-catalog/entity-catalog'; -import { ActionState } from '../reducers/api-request-reducer/types'; -import { selectSessionData } from '../reducers/auth.reducer'; -import { selectUsersRoles } from '../selectors/users-roles.selector'; -import { SessionDataEndpoint } from '../types/auth.types'; -import { PaginatedAction } from '../types/pagination.types'; -import { ICFAction, UpdateCfAction } from '../types/request.types'; +import { ResetPagination } from '../../../../store/src/actions/pagination.actions'; +import { entityCatalog } from '../../../../store/src/entity-catalog/entity-catalog'; +import { ActionState } from '../../../../store/src/reducers/api-request-reducer/types'; +import { selectSessionData } from '../../../../store/src/reducers/auth.reducer'; +import { SessionDataEndpoint } from '../../../../store/src/types/auth.types'; +import { PaginatedAction } from '../../../../store/src/types/pagination.types'; +import { ICFAction, UpdateCfAction } from '../../../../store/src/types/request.types'; +import { GET_CURRENT_CF_USER_RELATION, GetCurrentCfUserRelations } from '../../actions/permissions.actions'; +import { UsersRolesActions, UsersRolesClearUpdateState, UsersRolesExecuteChanges } from '../../actions/users-roles.actions'; +import { AddCfUserRole, ChangeCfUserRole, RemoveCfUserRole } from '../../actions/users.actions'; +import { CFAppState } from '../../cf-app-state'; +import { organizationEntityType, spaceEntityType } from '../../cf-entity-types'; +import { CF_ENDPOINT_TYPE } from '../../cf-types'; +import { CfUserService } from '../../shared/data-services/cf-user.service'; +import { fetchCfUserRole } from '../../user-permissions/cf-user-roles-fetch'; +import { selectCfUsersRoles } from '../selectors/cf-users-roles.selector'; +import { OrgUserRoleNames } from '../types/cf-user.types'; +import { CfRoleChange, UsersRolesState } from '../types/users-roles.types'; @Injectable() export class UsersRolesEffects { constructor( + private httpClient: HttpClient, private actions$: Actions, private store: Store, private cfUserService: CfUserService, ) { } + @Effect() getCurrentUsersPermissions$ = this.actions$.pipe( + ofType(GET_CURRENT_CF_USER_RELATION), + map(action => { + return fetchCfUserRole(this.store, action, this.httpClient).pipe( + map(() => ({ type: action.actions[1] })), + catchError(() => [{ type: action.actions[2] }]) + ); + }) + ); + @Effect() clearEntityUpdates$ = this.actions$.pipe( ofType(UsersRolesActions.ClearUpdateState), mergeMap(action => { @@ -47,7 +57,7 @@ export class UsersRolesEffects { guid: change.spaceGuid ? change.spaceGuid : change.orgGuid, endpointType: CF_ENDPOINT_TYPE, entityType: change.spaceGuid ? spaceEntityType : organizationEntityType, - updatingKey: ChangeUserRole.generateUpdatingKey(change.role, change.userGuid), + updatingKey: ChangeCfUserRole.generateUpdatingKey(change.role, change.userGuid), options: null, actions: [], type: '' @@ -61,7 +71,7 @@ export class UsersRolesEffects { @Effect() executeUsersRolesChange$ = this.actions$.pipe( ofType(UsersRolesActions.ExecuteChanges), withLatestFrom( - this.store.select(selectUsersRoles), + this.store.select(selectCfUsersRoles), this.store.select(selectSessionData()) ), mergeMap(([action, usersRoles, sessionData]) => { @@ -200,11 +210,11 @@ export class UsersRolesEffects { change: CfRoleChange, username: string, usernameOrigin: string - ): ChangeUserRole { + ): ChangeCfUserRole { const isSpace = !!change.spaceGuid; const entityGuid = isSpace ? change.spaceGuid : change.orgGuid; return change.add ? - new AddUserRole( + new AddCfUserRole( cfGuid, change.userGuid, entityGuid, @@ -215,7 +225,7 @@ export class UsersRolesEffects { username, usernameOrigin ) : - new RemoveUserRole( + new RemoveCfUserRole( cfGuid, change.userGuid, entityGuid, @@ -228,7 +238,7 @@ export class UsersRolesEffects { ); } - private createActionObs(action: ChangeUserRole): Observable { + private createActionObs(action: ChangeCfUserRole): Observable { return entityCatalog.getEntity(action) .store .getEntityMonitor(action.guid) diff --git a/src/frontend/packages/cloud-foundry/src/store/reducers/users-roles.reducer.ts b/src/frontend/packages/cloud-foundry/src/store/reducers/cf-users-roles.reducer.ts similarity index 97% rename from src/frontend/packages/cloud-foundry/src/store/reducers/users-roles.reducer.ts rename to src/frontend/packages/cloud-foundry/src/store/reducers/cf-users-roles.reducer.ts index bcb4b0173d..01f1eb770c 100644 --- a/src/frontend/packages/cloud-foundry/src/store/reducers/users-roles.reducer.ts +++ b/src/frontend/packages/cloud-foundry/src/store/reducers/cf-users-roles.reducer.ts @@ -16,7 +16,7 @@ import { IUserPermissionInOrg, IUserPermissionInSpace, OrgUserRoleNames, -} from '../types/user.types'; +} from '../types/cf-user.types'; import { UsersRolesState } from '../types/users-roles.types'; export function createDefaultOrgRoles(orgGuid: string, orgName: string): IUserPermissionInOrg { @@ -53,7 +53,7 @@ const defaultState: UsersRolesState = { changedRoles: [] }; -export function UsersRolesReducer(state: UsersRolesState = defaultState, action: Action): UsersRolesState { +export function cfUsersRolesReducer(state: UsersRolesState = defaultState, action: Action): UsersRolesState { switch (action.type) { case UsersRolesActions.SetUsers: const setUsersAction = action as UsersRolesSetUsers; diff --git a/src/frontend/packages/cloud-foundry/src/store/reducers/users.reducer.ts b/src/frontend/packages/cloud-foundry/src/store/reducers/cf-users.reducer.ts similarity index 90% rename from src/frontend/packages/cloud-foundry/src/store/reducers/users.reducer.ts rename to src/frontend/packages/cloud-foundry/src/store/reducers/cf-users.reducer.ts index 42376488c6..71079d216e 100644 --- a/src/frontend/packages/cloud-foundry/src/store/reducers/users.reducer.ts +++ b/src/frontend/packages/cloud-foundry/src/store/reducers/cf-users.reducer.ts @@ -5,10 +5,10 @@ import { APIResource, NormalizedResponse } from '../../../../store/src/types/api import { APISuccessOrFailedAction } from '../../../../store/src/types/request.types'; import { GET_ORGANIZATION_USERS_SUCCESS, GetAllOrgUsers } from '../../actions/organization.actions'; import { - ADD_ROLE_SUCCESS, - ChangeUserRole, - createDefaultUserRelations, - REMOVE_ROLE_SUCCESS, + ADD_CF_ROLE_SUCCESS, + ChangeCfUserRole, + createDefaultCfUserRelations, + REMOVE_CF_ROLE_SUCCESS, } from '../../actions/users.actions'; import { IOrganization, ISpace } from '../../cf-api.types'; import { cfUserEntityType } from '../../cf-entity-types'; @@ -20,7 +20,7 @@ import { getDefaultCfUserMissingRoles, OrgUserRoleNames, SpaceUserRoleNames, -} from '../types/user.types'; +} from '../types/cf-user.types'; const properties = { org: { @@ -36,12 +36,12 @@ const properties = { } }; -export function userReducer(state: IRequestEntityTypeState>, action: APISuccessOrFailedAction) { +export function cfUserReducer(state: IRequestEntityTypeState>, action: APISuccessOrFailedAction) { switch (action.type) { - case ADD_ROLE_SUCCESS: - case REMOVE_ROLE_SUCCESS: + case ADD_CF_ROLE_SUCCESS: + case REMOVE_CF_ROLE_SUCCESS: // Ensure that a user's roles collections are updated when we call add/remove - const permAction = action.apiAction as ChangeUserRole; + const permAction = action.apiAction as ChangeCfUserRole; if (permAction.username) { return state; } @@ -50,7 +50,7 @@ export function userReducer(state: IRequestEntityTypeState>, ...state, [userGuid]: { ...state[userGuid], - entity: updatePermission(state[userGuid].entity, entityGuid, isSpace, permissionTypeKey, action.type === ADD_ROLE_SUCCESS), + entity: updatePermission(state[userGuid].entity, entityGuid, isSpace, permissionTypeKey, action.type === ADD_CF_ROLE_SUCCESS), } }; case GET_ORGANIZATION_USERS_SUCCESS: @@ -99,18 +99,18 @@ type StateEntities = IRequestEntityTypeState>; export function userSpaceOrgReducer(isSpace: boolean) { return (state: StateEntities, action: APISuccessOrFailedAction) => { switch (action.type) { - case ADD_ROLE_SUCCESS: - case REMOVE_ROLE_SUCCESS: + case ADD_CF_ROLE_SUCCESS: + case REMOVE_CF_ROLE_SUCCESS: // Ensure that an org or space's roles lists are updated when we call add/remove - const permAction = action.apiAction as ChangeUserRole; - const isAdd = action.type === ADD_ROLE_SUCCESS ? true : false; + const permAction = action.apiAction as ChangeCfUserRole; + const isAdd = action.type === ADD_CF_ROLE_SUCCESS ? true : false; return (isSpace && !!permAction.isSpace) || (!isSpace && !permAction.isSpace) ? newEntityState(state, permAction, isAdd) : state; } return state; }; } -function newEntityState(state: StateEntities, action: ChangeUserRole, add: boolean): StateEntities { +function newEntityState(state: StateEntities, action: ChangeCfUserRole, add: boolean): StateEntities { const apiResource: APIResource = state[action.guid]; if (!apiResource) { return state; @@ -165,7 +165,7 @@ function updateUserMissingRoles(users: IRequestEntityTypeState { if (getOrgUsersAction.includeRelations.find(includedRel => includedRel === rel)) { reqRels.push(rel); diff --git a/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-base-cf-role.reducer.ts b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-base-cf-role.reducer.ts new file mode 100644 index 0000000000..64377c46ee --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-base-cf-role.reducer.ts @@ -0,0 +1,95 @@ +import { APIResource } from '../../../../../store/src/types/api.types'; +import { getDefaultRolesRequestState } from '../../../../../store/src/types/current-user-roles.types'; +import { CfUserRelationTypes, GetCurrentCfUserRelationsComplete } from '../../../actions/permissions.actions'; +import { ISpace } from '../../../cf-api.types'; +import { IAllCfRolesState, ICfRolesState, IOrgsRoleState } from '../../types/cf-current-user-roles.types'; +import { createCfOrgRoleStateState } from './current-cf-user-roles-org.reducer'; +import { currentCfUserOrgRolesReducer } from './current-cf-user-roles-orgs.reducer'; +import { currentCfUserSpaceRolesReducer } from './current-cf-user-roles-spaces.reducer'; + +export function getDefaultCfEndpointRoles(): ICfRolesState { + return { + global: { + isAdmin: false, + isReadOnlyAdmin: false, + isGlobalAuditor: false, + canRead: false, + canWrite: false, + scopes: [] + }, + spaces: { + + }, + organizations: { + + }, + state: getDefaultRolesRequestState() + }; +} + +export function currentUserBaseCFRolesReducer(state: IAllCfRolesState = {}, action: GetCurrentCfUserRelationsComplete): IAllCfRolesState { + if (!state[action.endpointGuid]) { + state = { + ...state, + [action.endpointGuid]: getDefaultCfEndpointRoles() + }; + } + return { + ...state, + [action.endpointGuid]: currentUserCFRolesReducer(state[action.endpointGuid], action) + }; +} + +function currentUserCFRolesReducer( + state: ICfRolesState = getDefaultCfEndpointRoles(), + action: GetCurrentCfUserRelationsComplete): ICfRolesState { + if (isOrgRelation(action.relationType)) { + return { + ...state, + organizations: currentCfUserOrgRolesReducer(state.organizations, action) + }; + } + if (isSpaceRelation(action.relationType)) { + return { + ...state, + spaces: currentCfUserSpaceRolesReducer(state.spaces, action), + organizations: assignSpaceToOrg(state.organizations, action.data) + }; + } + return state; +} + +function assignSpaceToOrg(organizations: IOrgsRoleState = {}, spaces: APIResource[]): IOrgsRoleState { + return spaces.reduce((newOrganizations: IOrgsRoleState, space) => { + const orgGuid = space.entity.organization_guid; + const org = newOrganizations[orgGuid] || createCfOrgRoleStateState(); + const spaceGuids = org.spaceGuids || []; + if (spaceGuids.includes(space.metadata.guid)) { + return newOrganizations; + } + return { + ...newOrganizations, + [orgGuid]: { + ...org, + spaceGuids: [ + ...spaceGuids, + space.metadata.guid + ] + } + }; + }, organizations); +} + + +function isOrgRelation(relationType: CfUserRelationTypes) { + return relationType === CfUserRelationTypes.AUDITED_ORGANIZATIONS || + relationType === CfUserRelationTypes.BILLING_MANAGED_ORGANIZATION || + relationType === CfUserRelationTypes.MANAGED_ORGANIZATION || + relationType === CfUserRelationTypes.ORGANIZATIONS; +} + +function isSpaceRelation(relationType: CfUserRelationTypes) { + return relationType === CfUserRelationTypes.AUDITED_SPACES || + relationType === CfUserRelationTypes.MANAGED_SPACES || + relationType === CfUserRelationTypes.SPACES; +} diff --git a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-reducer.helpers.ts b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-reducer.helpers.ts similarity index 65% rename from src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-reducer.helpers.ts rename to src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-reducer.helpers.ts index 61459783c7..0cf24c8845 100644 --- a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-reducer.helpers.ts +++ b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-reducer.helpers.ts @@ -1,23 +1,20 @@ -import { - GetCurrentUserRelationsComplete, - UserRelationTypes, -} from '../../../../cloud-foundry/src/actions/permissions.actions'; -import { APIResource } from '../../types/api.types'; +import { APIResource } from '../../../../../store/src/types/api.types'; +import { CfUserRelationTypes, GetCurrentCfUserRelationsComplete } from '../../../actions/permissions.actions'; -export interface IKeyedByIDObject { +interface IKeyedByIDObject { [id: string]: T; } -export type roleFinalReducer = ( +type roleFinalReducer = ( state: T, - relationType: UserRelationTypes, + relationType: CfUserRelationTypes, userHasRelation: boolean, data?: APIResource ) => T; -export function addNewRoles( +export function addNewCfRoles( state: IKeyedByIDObject, - action: GetCurrentUserRelationsComplete, + action: GetCurrentCfUserRelationsComplete, reducer: roleFinalReducer ) { return action.data.reduce((config, data) => { @@ -32,9 +29,9 @@ export function addNewRoles( }, { newState: { ...state }, addedIds: [] }); } -export function removeOldRoles( +export function removeOldCfRoles( state: IKeyedByIDObject, - action: GetCurrentUserRelationsComplete, + action: GetCurrentCfUserRelationsComplete, newIds: string[], reducer: roleFinalReducer ) { diff --git a/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-role-session.reducer.ts b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-role-session.reducer.ts new file mode 100644 index 0000000000..5778477b89 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-role-session.reducer.ts @@ -0,0 +1,89 @@ +import { VerifiedSession } from '../../../../../store/src/actions/auth.actions'; +import { EndpointActionComplete } from '../../../../../store/src/actions/endpoint.actions'; +import { SessionUser } from '../../../../../store/src/types/auth.types'; +import { EndpointUser, INewlyConnectedEndpointInfo } from '../../../../../store/src/types/endpoint.types'; +import { CF_ENDPOINT_TYPE } from '../../../cf-types'; +import { CfScopeStrings } from '../../../user-permissions/cf-user-permissions-checkers'; +import { IAllCfRolesState, ICfRolesState, IGlobalRolesState } from '../../types/cf-current-user-roles.types'; +import { getDefaultCfEndpointRoles } from './current-cf-user-base-cf-role.reducer'; + +interface PartialEndpoint { + user: EndpointUser | SessionUser; + guid: string; +} + +export function cfRoleInfoFromSessionReducer( + state: IAllCfRolesState, + action: VerifiedSession +): IAllCfRolesState { + const { endpoints } = action.sessionData; + return propagateEndpointsAdminPermissions(state, Object.values(endpoints.cf)); +} + +export function updateNewlyConnectedCfEndpoint( + state: IAllCfRolesState, + action: EndpointActionComplete +): IAllCfRolesState { + if (action.endpointType !== CF_ENDPOINT_TYPE) { + return state; + } + const endpoint = action.endpoint as INewlyConnectedEndpointInfo; + const cfRoles = propagateEndpointsAdminPermissions(state, [{ + user: endpoint.user, + guid: action.guid + }]); + return { + ...cfRoles, + }; +} + + +function propagateEndpointsAdminPermissions( + cfState: IAllCfRolesState, + endpoints: PartialEndpoint[] +): IAllCfRolesState { + if (!endpoints || !endpoints.length) { + return cfState; + } + return Object.values(endpoints).reduce((state, endpoint) => { + return { + ...state, + [endpoint.guid]: propagateEndpointAdminPermissions(state[endpoint.guid], endpoint) + }; + }, { ...cfState }); +} + +function propagateEndpointAdminPermissions(state: ICfRolesState = getDefaultCfEndpointRoles(), endpoint: PartialEndpoint) { + const scopes = endpoint.user ? endpoint.user.scopes : []; + const global = getEndpointRoles(scopes, state.global); + return { + ...state, + global + }; +} + +function getEndpointRoles(scopes: string[], globalEndpointState: IGlobalRolesState) { + const newEndpointState = { + ...globalEndpointState, + scopes + }; + return scopes.reduce((roles, scope) => { + if (scope === CfScopeStrings.CF_ADMIN_GROUP) { + roles.isAdmin = true; + } + if (scope === CfScopeStrings.CF_READ_ONLY_ADMIN_GROUP) { + roles.isReadOnlyAdmin = true; + } + if (scope === CfScopeStrings.CF_ADMIN_GLOBAL_AUDITOR_GROUP) { + roles.isGlobalAuditor = true; + } + if (scope === CfScopeStrings.CF_READ_SCOPE) { + roles.canRead = true; + } + if (scope === CfScopeStrings.CF_WRITE_SCOPE) { + roles.canWrite = true; + } + return roles; + }, newEndpointState); +} + diff --git a/src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-roles-changed.reducers.ts b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-changed.reducers.ts similarity index 69% rename from src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-roles-changed.reducers.ts rename to src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-changed.reducers.ts index f67b52a86f..578f857b0a 100644 --- a/src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-roles-changed.reducers.ts +++ b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-changed.reducers.ts @@ -1,17 +1,16 @@ -import { PermissionStrings } from '../../../../../core/src/core/current-user-permissions.config'; -import { ICurrentUserRolesState } from '../../../../../store/src/types/current-user-roles.types'; import { APISuccessOrFailedAction } from '../../../../../store/src/types/request.types'; -import { ChangeUserRole } from '../../../actions/users.actions'; -import { ICfRolesState, IOrgRoleState, ISpaceRoleState } from '../../types/cf-current-user-roles.types'; -import { OrgUserRoleNames, SpaceUserRoleNames } from '../../types/user.types'; -import { defaultUserOrgRoleState } from './current-user-roles-org.reducer'; -import { defaultUserSpaceRoleState } from './current-user-roles-space.reducer'; +import { ChangeCfUserRole } from '../../../actions/users.actions'; +import { CfPermissionStrings } from '../../../user-permissions/cf-user-permissions-checkers'; +import { IAllCfRolesState, ICfRolesState, IOrgRoleState, ISpaceRoleState } from '../../types/cf-current-user-roles.types'; +import { OrgUserRoleNames, SpaceUserRoleNames } from '../../types/cf-user.types'; +import { defaultCfUserOrgRoleState } from './current-cf-user-roles-org.reducer'; +import { defaultCfUserSpaceRoleState } from './current-cf-user-roles-space.reducer'; -export function updateAfterRoleChange( - state: ICurrentUserRolesState, +export function updateAfterCfRoleChange( + state: IAllCfRolesState, isAdd: boolean, - action: APISuccessOrFailedAction): ICurrentUserRolesState { - const changePerm = action.apiAction as ChangeUserRole; + action: APISuccessOrFailedAction): IAllCfRolesState { + const changePerm = action.apiAction as ChangeCfUserRole; if (!changePerm.updateConnectedUser) { // We haven't changed the user connected to this cf or the connected user is an admin. No need to update the permission roles return state; @@ -19,7 +18,7 @@ export function updateAfterRoleChange( const entityType = changePerm.isSpace ? 'spaces' : 'organizations'; - const cf = state.cf[changePerm.endpointGuid]; + const cf = state[changePerm.endpointGuid]; const entity = cf[entityType][changePerm.entityGuid] || createEmptyState(changePerm.isSpace, changePerm.orgGuid); const permissionType = userRoleNameToPermissionName(changePerm.permissionTypeKey); @@ -46,34 +45,34 @@ export function updateAfterRoleChange( function createEmptyState(isSpace: boolean, orgId?: string): ISpaceRoleState | IOrgRoleState { return isSpace ? { - ...defaultUserSpaceRoleState, + ...defaultCfUserSpaceRoleState, orgId } : { - ...defaultUserOrgRoleState, + ...defaultCfUserOrgRoleState, }; } -function userRoleNameToPermissionName(roleName: OrgUserRoleNames | SpaceUserRoleNames): PermissionStrings { +function userRoleNameToPermissionName(roleName: OrgUserRoleNames | SpaceUserRoleNames): CfPermissionStrings { switch (roleName) { case OrgUserRoleNames.AUDITOR: - return PermissionStrings.ORG_AUDITOR; + return CfPermissionStrings.ORG_AUDITOR; case OrgUserRoleNames.BILLING_MANAGERS: - return PermissionStrings.ORG_BILLING_MANAGER; + return CfPermissionStrings.ORG_BILLING_MANAGER; case OrgUserRoleNames.MANAGER: - return PermissionStrings.ORG_MANAGER; + return CfPermissionStrings.ORG_MANAGER; case OrgUserRoleNames.USER: - return PermissionStrings.ORG_USER; + return CfPermissionStrings.ORG_USER; case SpaceUserRoleNames.AUDITOR: - return PermissionStrings.SPACE_AUDITOR; + return CfPermissionStrings.SPACE_AUDITOR; case SpaceUserRoleNames.DEVELOPER: - return PermissionStrings.SPACE_DEVELOPER; + return CfPermissionStrings.SPACE_DEVELOPER; case SpaceUserRoleNames.MANAGER: - return PermissionStrings.SPACE_MANAGER; + return CfPermissionStrings.SPACE_MANAGER; } } function handleOrgRoleChange( - state: ICurrentUserRolesState, + state: IAllCfRolesState, endpointGuid: string, cf: ICfRolesState, orgGuid: string, @@ -95,7 +94,7 @@ function handleOrgRoleChange( * Update the space role AND org space guids list */ function handleSpaceRoleChange( - state: ICurrentUserRolesState, + state: IAllCfRolesState, endpointGuid: string, cf: ICfRolesState, orgGuid: string, @@ -135,14 +134,11 @@ function handleSpaceRoleChange( }); } -function spreadState(state: ICurrentUserRolesState, cfGuid: string, cf: ICfRolesState): ICurrentUserRolesState { +function spreadState(state: IAllCfRolesState, cfGuid: string, cf: ICfRolesState): IAllCfRolesState { return { ...state, - cf: { - ...state.cf, - [cfGuid]: { - ...cf - } + [cfGuid]: { + ...cf } }; } diff --git a/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-clear.reducers.ts b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-clear.reducers.ts new file mode 100644 index 0000000000..ade79d48f4 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-clear.reducers.ts @@ -0,0 +1,111 @@ +import { EndpointActionComplete } from '../../../../../store/src/actions/endpoint.actions'; +import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; +import { APISuccessOrFailedAction } from '../../../../../store/src/types/request.types'; +import { CF_ENDPOINT_TYPE } from '../../../cf-types'; +import { IAllCfRolesState } from '../../types/cf-current-user-roles.types'; +import { getDefaultCfEndpointRoles } from './current-cf-user-base-cf-role.reducer'; + +export function removeEndpointCfRoles(state: IAllCfRolesState, action: EndpointActionComplete) { + if (!state[action.guid]) { + return state; + } + const cfState = { + ...state + }; + delete cfState[action.guid]; + return { + ...cfState, + }; +} + +export function addCfEndpoint(state: IAllCfRolesState, action: EndpointActionComplete) { + if (action.endpointType !== CF_ENDPOINT_TYPE) { + return state; + } + const endpoint = action.endpoint as EndpointModel; + const guid = endpoint.guid; + if (state[guid]) { + return state; + } + const cfState = { + ...state + }; + + cfState[guid] = getDefaultCfEndpointRoles(); + return cfState; +} + +export function removeCfSpaceRoles(state: IAllCfRolesState, action: APISuccessOrFailedAction) { + const { endpointGuid, guid } = action.apiAction; + const removedOrgOrSpaceState = removeOrgOrSpaceRoles(state, endpointGuid as string, guid, 'spaces'); + return removeSpaceIdFromOrg(removedOrgOrSpaceState, endpointGuid as string, guid); +} + +function removeSpaceIdFromOrg(state: IAllCfRolesState, endpointGuid: string, spaceGuid: string) { + const space = state[endpointGuid].spaces[spaceGuid]; + if (!space) { + return state; + } + const { orgId } = space; + return { + ...state, + [endpointGuid]: { + ...state[endpointGuid], + organizations: { + ...state[endpointGuid].organizations, + [orgId]: { + ...state[endpointGuid].organizations[orgId], + spaceGuids: state[endpointGuid].organizations[orgId].spaceGuids.filter(id => id !== spaceGuid) + } + } + } + }; +} + +export function removeCfOrgRoles(state: IAllCfRolesState, action: APISuccessOrFailedAction) { + const { endpointGuid, guid } = action.apiAction; + if (!state[endpointGuid as string].organizations[guid]) { + return state; + } + // const spaceIds = state.cf[endpointGuid].organizations[guid].spaceIds; + const spaceIds = []; + const newState = removeOrgOrSpaceRoles(state, endpointGuid, guid, 'organizations'); + return cleanUpOrgSpaces(newState, spaceIds, endpointGuid); +} + +function removeOrgOrSpaceRoles( + state: IAllCfRolesState, + endpointGuid: string, + orgOrSpaceId: string, + type: 'organizations' | 'spaces' +) { + if (!state[endpointGuid][type][orgOrSpaceId]) { + return state; + } + // Remove orgOrSpaceId + const { + [orgOrSpaceId]: omit, + ...newTypeState + } = state[endpointGuid][type]; + + const newState = { + ...state, + [endpointGuid]: { + ...state[endpointGuid], + [type]: newTypeState + } + }; + return newState; +} + +function cleanUpOrgSpaces(state: IAllCfRolesState, spaceIds: string[], endpointGuid: string) { + if (!spaceIds || spaceIds.length === 0 || !state[endpointGuid]) { + return state; + } + return spaceIds.reduce((newState, spaceId) => { + if (newState[endpointGuid].spaces[spaceId]) { + delete newState[endpointGuid].spaces[spaceId]; + } + return newState; + }, { ...state }); +} diff --git a/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-org.reducer.ts b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-org.reducer.ts new file mode 100644 index 0000000000..ecac70b373 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-org.reducer.ts @@ -0,0 +1,47 @@ +import { CfUserRelationTypes } from '../../../actions/permissions.actions'; +import { IOrgRoleState } from '../../types/cf-current-user-roles.types'; + +export const defaultCfUserOrgRoleState: IOrgRoleState = { + isManager: false, + isAuditor: false, + isBillingManager: false, + isUser: false, + spaceGuids: [] +}; + +export const createCfOrgRoleStateState = () => ({ + ...defaultCfUserOrgRoleState, + spaceGuids: [ + ...defaultCfUserOrgRoleState.spaceGuids + ] +}); + +export function currentCfUserOrgRoleReducer( + state: IOrgRoleState = createCfOrgRoleStateState(), + relationType: CfUserRelationTypes, + userHasRelation: boolean +) { + switch (relationType) { + case CfUserRelationTypes.AUDITED_ORGANIZATIONS: + return { + ...state, + isAuditor: userHasRelation + }; + case CfUserRelationTypes.BILLING_MANAGED_ORGANIZATION: + return { + ...state, + isBillingManager: userHasRelation + }; + case CfUserRelationTypes.MANAGED_ORGANIZATION: + return { + ...state, + isManager: userHasRelation + }; + case CfUserRelationTypes.ORGANIZATIONS: + return { + ...state, + isUser: userHasRelation + }; + } + return state; +} diff --git a/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-orgs.reducer.ts b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-orgs.reducer.ts new file mode 100644 index 0000000000..325d21faa0 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-orgs.reducer.ts @@ -0,0 +1,9 @@ +import { GetCurrentCfUserRelationsComplete } from '../../../actions/permissions.actions'; +import { IOrgsRoleState } from '../../types/cf-current-user-roles.types'; +import { addNewCfRoles, removeOldCfRoles } from './current-cf-user-reducer.helpers'; +import { currentCfUserOrgRoleReducer } from './current-cf-user-roles-org.reducer'; + +export function currentCfUserOrgRolesReducer(state: IOrgsRoleState = {}, action: GetCurrentCfUserRelationsComplete) { + const { newState, addedIds } = addNewCfRoles(state, action, currentCfUserOrgRoleReducer); + return removeOldCfRoles(newState, action, addedIds, currentCfUserOrgRoleReducer); +} diff --git a/src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-roles-space.reducer.ts b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-space.reducer.ts similarity index 67% rename from src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-roles-space.reducer.ts rename to src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-space.reducer.ts index fda7c35762..944aee675e 100644 --- a/src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-roles-space.reducer.ts +++ b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-space.reducer.ts @@ -1,18 +1,18 @@ import { APIResource } from '../../../../../store/src/types/api.types'; -import { UserRelationTypes } from '../../../actions/permissions.actions'; +import { CfUserRelationTypes } from '../../../actions/permissions.actions'; import { ISpace } from '../../../cf-api.types'; import { ISpaceRoleState } from '../../types/cf-current-user-roles.types'; -export const defaultUserSpaceRoleState: ISpaceRoleState = { +export const defaultCfUserSpaceRoleState: ISpaceRoleState = { orgId: null, isManager: false, isAuditor: false, isDeveloper: false, }; -export function currentUserSpaceRoleReducer( - state: ISpaceRoleState = defaultUserSpaceRoleState, - relationType: UserRelationTypes, +export function currentCfUserSpaceRoleReducer( + state: ISpaceRoleState = defaultCfUserSpaceRoleState, + relationType: CfUserRelationTypes, userHasRelation: boolean, space: APIResource ): ISpaceRoleState { @@ -28,7 +28,7 @@ export function currentUserSpaceRoleReducer( } function addId( - state: ISpaceRoleState = defaultUserSpaceRoleState, + state: ISpaceRoleState = defaultCfUserSpaceRoleState, space: APIResource ) { if (!state.orgId) { @@ -42,21 +42,21 @@ function addId( function applyRoles( state: ISpaceRoleState, - relationType: UserRelationTypes, + relationType: CfUserRelationTypes, userHasRelation: boolean ) { switch (relationType) { - case UserRelationTypes.AUDITED_SPACES: + case CfUserRelationTypes.AUDITED_SPACES: return { ...state, isAuditor: userHasRelation }; - case UserRelationTypes.MANAGED_SPACES: + case CfUserRelationTypes.MANAGED_SPACES: return { ...state, isManager: userHasRelation }; - case UserRelationTypes.SPACES: + case CfUserRelationTypes.SPACES: return { ...state, isDeveloper: userHasRelation diff --git a/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-spaces.reducer.ts b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-spaces.reducer.ts new file mode 100644 index 0000000000..f66284401f --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles-spaces.reducer.ts @@ -0,0 +1,9 @@ +import { GetCurrentCfUserRelationsComplete } from '../../../actions/permissions.actions'; +import { ISpacesRoleState } from '../../types/cf-current-user-roles.types'; +import { addNewCfRoles, removeOldCfRoles } from './current-cf-user-reducer.helpers'; +import { currentCfUserSpaceRoleReducer } from './current-cf-user-roles-space.reducer'; + +export function currentCfUserSpaceRolesReducer(state: ISpacesRoleState = {}, action: GetCurrentCfUserRelationsComplete) { + const { newState, addedIds } = addNewCfRoles(state, action, currentCfUserSpaceRoleReducer); + return removeOldCfRoles(newState, action, addedIds, currentCfUserSpaceRoleReducer); +} diff --git a/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles.reducer.spec.ts b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles.reducer.spec.ts new file mode 100644 index 0000000000..eb698f376c --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles.reducer.spec.ts @@ -0,0 +1,249 @@ +import { CfUserRelationTypes, GetCurrentCfUserRelationsComplete } from '../../../actions/permissions.actions'; +import { + IAllCfRolesState, + ICfRolesState, + IOrgRoleState, + ISpaceRoleState, + RoleEntities, +} from '../../types/cf-current-user-roles.types'; +import { getDefaultCfEndpointRoles } from './current-cf-user-base-cf-role.reducer'; +import { createCfOrgRoleStateState } from './current-cf-user-roles-org.reducer'; +import { currentCfUserRolesReducer } from './current-cf-user-roles.reducer'; + +const testOrgGuid = 'org-1'; +const testSpaceGuid = 'space-1'; +const generalGuid = 'guid-123'; +const testCFEndpointGuid = 'cf-1'; + +function getSpaceAction(type: CfUserRelationTypes, orgGuid: string = testOrgGuid, spaceGuid: string = testSpaceGuid) { + return new GetCurrentCfUserRelationsComplete( + type, + testCFEndpointGuid, + [{ metadata: { guid: spaceGuid, created_at: '1', updated_at: '1', url: '1' }, entity: { organization_guid: orgGuid } }] + ); +} + +function getOrgAction(type: CfUserRelationTypes, orgGuid: string = testOrgGuid) { + return new GetCurrentCfUserRelationsComplete( + type, + testCFEndpointGuid, + [{ metadata: { guid: orgGuid, created_at: '1', updated_at: '1', url: '1' }, entity: {} }] + ); +} + +function getState( + orgOrSpace: RoleEntities, + allRoles: { guid: string, roles: ISpaceRoleState | IOrgRoleState }[] = [], + roles?: ISpaceRoleState | IOrgRoleState +): ICfRolesState { + const baseState = getDefaultCfEndpointRoles(); + if (!allRoles.length) { + let guid = testSpaceGuid; + if (orgOrSpace === RoleEntities.ORGS) { + guid = testOrgGuid; + } + allRoles.push({ guid, roles }); + } + const orgSpaceRoles = { + [orgOrSpace]: {} + }; + if (orgOrSpace === RoleEntities.SPACES) { + orgSpaceRoles.organizations = { + [testOrgGuid]: createCfOrgRoleStateState() + }; + } + allRoles.forEach(role => { + orgSpaceRoles[orgOrSpace][role.guid] = role.roles; + if (orgOrSpace === RoleEntities.SPACES) { + orgSpaceRoles.organizations[testOrgGuid].spaceGuids.push(role.guid); + } + }); + return { + ...baseState, + ...orgSpaceRoles + }; +} + +describe('currentCfUserRolesReducer', () => { + it('set defaults', () => { + const state = currentCfUserRolesReducer(undefined, { type: 'FAKE_ACTION' }); + const expectedState: IAllCfRolesState = null; + expect(state).toEqual(expectedState); + }); + it('should add org manager role to org', () => { + const state = currentCfUserRolesReducer(undefined, getOrgAction(CfUserRelationTypes.MANAGED_ORGANIZATION)); + const cfPermissions = state[testCFEndpointGuid]; + expect(cfPermissions).toEqual(getState(RoleEntities.ORGS, [], { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: false, + spaceGuids: [] + })); + }); + it('should add org auditor role to org', () => { + const state = currentCfUserRolesReducer(undefined, getOrgAction(CfUserRelationTypes.AUDITED_ORGANIZATIONS)); + const cfPermissions = state[testCFEndpointGuid]; + expect(cfPermissions).toEqual(getState(RoleEntities.ORGS, [], { + isManager: false, + isAuditor: true, + isBillingManager: false, + isUser: false, + spaceGuids: [] + })); + }); + it('should add org billing manager role to org', () => { + const state = currentCfUserRolesReducer(undefined, getOrgAction(CfUserRelationTypes.BILLING_MANAGED_ORGANIZATION)); + const cfPermissions = state[testCFEndpointGuid]; + expect(cfPermissions).toEqual(getState(RoleEntities.ORGS, [], { + isManager: false, + isAuditor: false, + isBillingManager: true, + isUser: false, + spaceGuids: [] + })); + }); + it('should add org user role to org', () => { + const state = currentCfUserRolesReducer(undefined, getOrgAction(CfUserRelationTypes.ORGANIZATIONS)); + const cfPermissions = state[testCFEndpointGuid]; + expect(cfPermissions).toEqual(getState(RoleEntities.ORGS, [], { + isManager: false, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + })); + }); + + it('should retain other org roles', () => { + let state = currentCfUserRolesReducer(undefined, getOrgAction(CfUserRelationTypes.ORGANIZATIONS)); + state = currentCfUserRolesReducer(state, getOrgAction(CfUserRelationTypes.AUDITED_ORGANIZATIONS, generalGuid)); + const cfPermissions = state[testCFEndpointGuid]; + const toEqual = getState(RoleEntities.ORGS, [{ + guid: testOrgGuid, + roles: { + isManager: false, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + }, + { + guid: generalGuid, + roles: { + isManager: false, + isAuditor: true, + isBillingManager: false, + isUser: false, + spaceGuids: [] + } + }]); + expect(cfPermissions).toEqual(toEqual); + }); + + it('should retain other space roles', () => { + let state = currentCfUserRolesReducer(undefined, getSpaceAction(CfUserRelationTypes.SPACES)); + state = currentCfUserRolesReducer(state, getSpaceAction(CfUserRelationTypes.MANAGED_SPACES, generalGuid, generalGuid)); + const cfPermissions = state[testCFEndpointGuid]; + const toEqual = getState(RoleEntities.SPACES, [{ + guid: testSpaceGuid, + roles: { + orgId: testOrgGuid, + isManager: false, + isAuditor: false, + isDeveloper: true + } + }, + { + guid: generalGuid, + roles: { + orgId: generalGuid, + isManager: true, + isAuditor: false, + isDeveloper: false + } + }]); + const orgState = getState(RoleEntities.ORGS, [{ + guid: testOrgGuid, + roles: { + isManager: false, + isAuditor: false, + isBillingManager: false, + isUser: false, + spaceGuids: [ + testSpaceGuid + ] + } + }, + { + guid: generalGuid, + roles: { + isManager: false, + isAuditor: false, + isBillingManager: false, + isUser: false, + spaceGuids: [ + generalGuid + ] + } + }]); + toEqual.organizations = orgState.organizations; + expect(cfPermissions).toEqual(toEqual); + }); + + it('should retain other space and org roles', () => { + let state = currentCfUserRolesReducer(undefined, getSpaceAction(CfUserRelationTypes.SPACES)); + state = currentCfUserRolesReducer(state, getSpaceAction(CfUserRelationTypes.MANAGED_SPACES, generalGuid, generalGuid)); + state = currentCfUserRolesReducer(state, getOrgAction(CfUserRelationTypes.ORGANIZATIONS)); + state = currentCfUserRolesReducer(state, getOrgAction(CfUserRelationTypes.AUDITED_ORGANIZATIONS, generalGuid)); + const cfPermissions = state[testCFEndpointGuid]; + + + const spaceState = getState(RoleEntities.SPACES, [{ + guid: testSpaceGuid, + roles: { + orgId: testOrgGuid, + isManager: false, + isAuditor: false, + isDeveloper: true + } + }, + { + guid: generalGuid, + roles: { + orgId: generalGuid, + isManager: true, + isAuditor: false, + isDeveloper: false + } + }]); + + const orgState = getState(RoleEntities.ORGS, [{ + guid: testOrgGuid, + roles: { + isManager: false, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [ + testSpaceGuid + ] + } + }, + { + guid: generalGuid, + roles: { + isManager: false, + isAuditor: true, + isBillingManager: false, + isUser: false, + spaceGuids: [ + generalGuid + ] + } + }]); + spaceState.organizations = orgState.organizations; + expect(cfPermissions).toEqual(spaceState); + }); +}); diff --git a/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles.reducer.ts b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles.reducer.ts new file mode 100644 index 0000000000..89a24cc8e5 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/store/reducers/current-cf-user-roles-reducer/current-cf-user-roles.reducer.ts @@ -0,0 +1,90 @@ +import { Action } from '@ngrx/store'; + +import { SESSION_VERIFIED, VerifiedSession } from '../../../../../store/src/actions/auth.actions'; +import { + CONNECT_ENDPOINTS_SUCCESS, + DISCONNECT_ENDPOINTS_SUCCESS, + EndpointActionComplete, + REGISTER_ENDPOINTS_SUCCESS, + UNREGISTER_ENDPOINTS_SUCCESS, +} from '../../../../../store/src/actions/endpoint.actions'; +import { EntityUserRolesReducer } from '../../../../../store/src/entity-request-pipeline/entity-request-pipeline.types'; +import { + currentUserRolesRequestStateReducer, + RolesRequestStateStage, +} from '../../../../../store/src/reducers/current-user-roles-reducer/current-user-roles.reducer'; +import { APISuccessOrFailedAction } from '../../../../../store/src/types/request.types'; +import { DELETE_ORGANIZATION_SUCCESS } from '../../../actions/organization.actions'; +import { + GET_CURRENT_CF_USER_RELATION_SUCCESS, + GET_CURRENT_CF_USER_RELATIONS, + GET_CURRENT_CF_USER_RELATIONS_FAILED, + GET_CURRENT_CF_USER_RELATIONS_SUCCESS, + GetCfUserRelations, + GetCurrentCfUserRelationsComplete, +} from '../../../actions/permissions.actions'; +import { DELETE_SPACE_SUCCESS } from '../../../actions/space.actions'; +import { ADD_CF_ROLE_SUCCESS, REMOVE_CF_ROLE_SUCCESS } from '../../../actions/users.actions'; +import { IAllCfRolesState } from '../../types/cf-current-user-roles.types'; +import { currentUserBaseCFRolesReducer } from './current-cf-user-base-cf-role.reducer'; +import { cfRoleInfoFromSessionReducer, updateNewlyConnectedCfEndpoint } from './current-cf-user-role-session.reducer'; +import { updateAfterCfRoleChange } from './current-cf-user-roles-changed.reducers'; +import { + addCfEndpoint, + removeCfOrgRoles, + removeCfSpaceRoles, + removeEndpointCfRoles, +} from './current-cf-user-roles-clear.reducers'; + +export const currentCfUserRolesReducer: EntityUserRolesReducer = ( + state: IAllCfRolesState = {}, + action: Action +): IAllCfRolesState => { + switch (action.type) { + case GET_CURRENT_CF_USER_RELATION_SUCCESS: + const gcursAction = action as GetCurrentCfUserRelationsComplete + return currentUserBaseCFRolesReducer(state, gcursAction); + case SESSION_VERIFIED: + return cfRoleInfoFromSessionReducer(state, action as VerifiedSession); + case REGISTER_ENDPOINTS_SUCCESS: + return addCfEndpoint(state, action as EndpointActionComplete); + case CONNECT_ENDPOINTS_SUCCESS: + return updateNewlyConnectedCfEndpoint(state, action as EndpointActionComplete); + case DISCONNECT_ENDPOINTS_SUCCESS: + case UNREGISTER_ENDPOINTS_SUCCESS: + return removeEndpointCfRoles(state, action as EndpointActionComplete); + case DELETE_ORGANIZATION_SUCCESS: + return removeCfOrgRoles(state, action as APISuccessOrFailedAction); + case DELETE_SPACE_SUCCESS: + return removeCfSpaceRoles(state, action as APISuccessOrFailedAction); + case ADD_CF_ROLE_SUCCESS: + return updateAfterCfRoleChange(state, true, action as APISuccessOrFailedAction); + case REMOVE_CF_ROLE_SUCCESS: + return updateAfterCfRoleChange(state, false, action as APISuccessOrFailedAction); + case GET_CURRENT_CF_USER_RELATIONS: + case GET_CURRENT_CF_USER_RELATIONS_SUCCESS: + case GET_CURRENT_CF_USER_RELATIONS_FAILED: + return currentUserCfRolesRequestStateReducer(state, action as GetCfUserRelations); + } + return null; +} + +export function currentUserCfRolesRequestStateReducer(cf: IAllCfRolesState = {}, action: GetCfUserRelations) { + const cfGuid = (action as GetCfUserRelations).cfGuid; + + const type = action.type === GET_CURRENT_CF_USER_RELATIONS ? + RolesRequestStateStage.START : + action.type === GET_CURRENT_CF_USER_RELATIONS_SUCCESS ? RolesRequestStateStage.SUCCESS : + action.type === GET_CURRENT_CF_USER_RELATIONS_FAILED ? RolesRequestStateStage.FAILURE : + RolesRequestStateStage.OTHER + return { + ...cf, + [cfGuid]: { + ...cf[cfGuid], + state: { + ...cf[cfGuid].state, + ...currentUserRolesRequestStateReducer(cf[cfGuid].state, type) + } + } + }; +} diff --git a/src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-base-cf-role.reducer.ts b/src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-base-cf-role.reducer.ts deleted file mode 100644 index 0e9832beb1..0000000000 --- a/src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-base-cf-role.reducer.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { getDefaultEndpointRoles } from '../../../../../store/src/types/current-user-roles.types'; -import { GetCurrentUserRelationsComplete } from '../../../actions/permissions.actions'; -import { IAllCfRolesState } from '../../types/cf-current-user-roles.types'; -import { currentUserCFRolesReducer } from './current-user-cf-roles.reducer'; - -export function currentUserBaseCFRolesReducer(state: IAllCfRolesState = {}, action: GetCurrentUserRelationsComplete): IAllCfRolesState { - if (!state[action.endpointGuid]) { - state = { - ...state, - [action.endpointGuid]: getDefaultEndpointRoles() - }; - } - return { - ...state, - [action.endpointGuid]: currentUserCFRolesReducer(state[action.endpointGuid], action) - }; -} diff --git a/src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-cf-roles.reducer.ts b/src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-cf-roles.reducer.ts deleted file mode 100644 index 3cb9fbf328..0000000000 --- a/src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-cf-roles.reducer.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { APIResource } from '../../../../../store/src/types/api.types'; -import { getDefaultEndpointRoles } from '../../../../../store/src/types/current-user-roles.types'; -import { GetCurrentUserRelationsComplete, UserRelationTypes } from '../../../actions/permissions.actions'; -import { ISpace } from '../../../cf-api.types'; -import { ICfRolesState, IOrgsRoleState } from '../../types/cf-current-user-roles.types'; -import { createOrgRoleStateState } from './current-user-roles-org.reducer'; -import { currentUserOrgRolesReducer } from './current-user-roles-orgs.reducer'; -import { currentUserSpaceRolesReducer } from './current-user-roles-spaces.reducer'; - -export function currentUserCFRolesReducer( - state: ICfRolesState = getDefaultEndpointRoles(), - action: GetCurrentUserRelationsComplete): ICfRolesState { - if (isOrgRelation(action.relationType)) { - return { - ...state, - organizations: currentUserOrgRolesReducer(state.organizations, action) - }; - } - if (isSpaceRelation(action.relationType)) { - return { - ...state, - spaces: currentUserSpaceRolesReducer(state.spaces, action), - organizations: assignSpaceToOrg(state.organizations, action.data) - }; - } - return state; -} - -function assignSpaceToOrg(organizations: IOrgsRoleState = {}, spaces: APIResource[]): IOrgsRoleState { - return spaces.reduce((newOrganizations: IOrgsRoleState, space) => { - const orgGuid = space.entity.organization_guid; - const org = newOrganizations[orgGuid] || createOrgRoleStateState(); - const spaceGuids = org.spaceGuids || []; - if (spaceGuids.includes(space.metadata.guid)) { - return newOrganizations; - } - return { - ...newOrganizations, - [orgGuid]: { - ...org, - spaceGuids: [ - ...spaceGuids, - space.metadata.guid - ] - } - }; - }, organizations); -} - - -function isOrgRelation(relationType: UserRelationTypes) { - return relationType === UserRelationTypes.AUDITED_ORGANIZATIONS || - relationType === UserRelationTypes.BILLING_MANAGED_ORGANIZATION || - relationType === UserRelationTypes.MANAGED_ORGANIZATION || - relationType === UserRelationTypes.ORGANIZATIONS; -} - -function isSpaceRelation(relationType: UserRelationTypes) { - return relationType === UserRelationTypes.AUDITED_SPACES || - relationType === UserRelationTypes.MANAGED_SPACES || - relationType === UserRelationTypes.SPACES; -} diff --git a/src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-roles-org.reducer.ts b/src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-roles-org.reducer.ts deleted file mode 100644 index 8f3053d305..0000000000 --- a/src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-roles-org.reducer.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { UserRelationTypes } from '../../../actions/permissions.actions'; -import { IOrgRoleState } from '../../types/cf-current-user-roles.types'; - -export const defaultUserOrgRoleState: IOrgRoleState = { - isManager: false, - isAuditor: false, - isBillingManager: false, - isUser: false, - spaceGuids: [] -}; - -export const createOrgRoleStateState = () => ({ - ...defaultUserOrgRoleState, - spaceGuids: [ - ...defaultUserOrgRoleState.spaceGuids - ] -}); - -export function currentUserOrgRoleReducer( - state: IOrgRoleState = createOrgRoleStateState(), - relationType: UserRelationTypes, - userHasRelation: boolean -) { - switch (relationType) { - case UserRelationTypes.AUDITED_ORGANIZATIONS: - return { - ...state, - isAuditor: userHasRelation - }; - case UserRelationTypes.BILLING_MANAGED_ORGANIZATION: - return { - ...state, - isBillingManager: userHasRelation - }; - case UserRelationTypes.MANAGED_ORGANIZATION: - return { - ...state, - isManager: userHasRelation - }; - case UserRelationTypes.ORGANIZATIONS: - return { - ...state, - isUser: userHasRelation - }; - } - return state; -} diff --git a/src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-roles-orgs.reducer.ts b/src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-roles-orgs.reducer.ts deleted file mode 100644 index b7d39d94bb..0000000000 --- a/src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-roles-orgs.reducer.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { - addNewRoles, - removeOldRoles, -} from '../../../../../store/src/reducers/current-user-roles-reducer/current-user-reducer.helpers'; -import { GetCurrentUserRelationsComplete } from '../../../actions/permissions.actions'; -import { IOrgsRoleState } from '../../types/cf-current-user-roles.types'; -import { currentUserOrgRoleReducer } from './current-user-roles-org.reducer'; - -export function currentUserOrgRolesReducer(state: IOrgsRoleState = {}, action: GetCurrentUserRelationsComplete) { - const { newState, addedIds } = addNewRoles(state, action, currentUserOrgRoleReducer); - return removeOldRoles(newState, action, addedIds, currentUserOrgRoleReducer); -} diff --git a/src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-roles-spaces.reducer.ts b/src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-roles-spaces.reducer.ts deleted file mode 100644 index 5660bca4ef..0000000000 --- a/src/frontend/packages/cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-roles-spaces.reducer.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { - addNewRoles, - removeOldRoles, -} from '../../../../../store/src/reducers/current-user-roles-reducer/current-user-reducer.helpers'; -import { GetCurrentUserRelationsComplete } from '../../../actions/permissions.actions'; -import { ISpacesRoleState } from '../../types/cf-current-user-roles.types'; -import { currentUserSpaceRoleReducer } from './current-user-roles-space.reducer'; - -export function currentUserSpaceRolesReducer(state: ISpacesRoleState = {}, action: GetCurrentUserRelationsComplete) { - const { newState, addedIds } = addNewRoles(state, action, currentUserSpaceRoleReducer); - return removeOldRoles(newState, action, addedIds, currentUserSpaceRoleReducer); -} diff --git a/src/frontend/packages/cloud-foundry/src/store/selectors/cf-current-user-role.selectors.ts b/src/frontend/packages/cloud-foundry/src/store/selectors/cf-current-user-role.selectors.ts index f7bd9b3e62..c3df05ca37 100644 --- a/src/frontend/packages/cloud-foundry/src/store/selectors/cf-current-user-role.selectors.ts +++ b/src/frontend/packages/cloud-foundry/src/store/selectors/cf-current-user-role.selectors.ts @@ -1,16 +1,13 @@ import { compose } from '@ngrx/store'; +import { PermissionValues } from '../../../../core/src/core/permissions/current-user-permissions.config'; import { - PermissionStrings, - PermissionValues, - ScopeStrings, -} from '../../../../core/src/core/current-user-permissions.config'; -import { - selectCurrentUserCFGlobalHasScopes, - selectCurrentUserRequestState, + selectCurrentUserGlobalHasScopes, selectCurrentUserRolesState, } from '../../../../store/src/selectors/current-user-role.selectors'; import { ICurrentUserRolesState } from '../../../../store/src/types/current-user-roles.types'; +import { CF_ENDPOINT_TYPE } from '../../cf-types'; +import { CfPermissionStrings, CfScopeStrings } from '../../user-permissions/cf-user-permissions-checkers'; import { IAllCfRolesState, ICfRolesState, @@ -19,7 +16,10 @@ import { ISpacesRoleState, } from '../types/cf-current-user-roles.types'; -const selectSpaceWithRoleFromOrg = (role: PermissionStrings, orgId: string) => (state: ICfRolesState) => { + +export const selectCurrentUserRequestState = (state: ICurrentUserRolesState | ICfRolesState) => state.state; + +const selectSpaceWithRoleFromOrg = (role: CfPermissionStrings, orgId: string) => (state: ICfRolesState) => { if (!state) { return 'all'; } @@ -38,7 +38,7 @@ const selectSpaceWithRoleFromOrg = (role: PermissionStrings, orgId: string) => ( }, []); }; -export const selectCurrentUserCFRolesState = (state: ICurrentUserRolesState) => state.cf; +export const selectCurrentUserCFRolesState = (state: ICurrentUserRolesState) => state.endpoints[CF_ENDPOINT_TYPE]; export const selectCurrentUserCFEndpointRolesState = (endpointGuid: string) => (state: IAllCfRolesState) => state ? state[endpointGuid] : null; @@ -63,7 +63,7 @@ export const getCurrentUserCFRolesState = compose( // ============================ export const getCurrentUserCFEndpointRolesState = (endpointGuid: string) => compose( selectCurrentUserCFEndpointRolesState(endpointGuid), - getCurrentUserCFRolesState + getCurrentUserCFRolesState, ); // ============================ @@ -101,8 +101,8 @@ export const getCurrentUserCFEndpointScopesState = (endpointGuid: string) => com // Has endpoint scopes // ============================ -export const getCurrentUserCFEndpointHasScope = (endpointGuid: string, scope: ScopeStrings) => compose( - selectCurrentUserCFGlobalHasScopes(scope), +export const getCurrentUserCFEndpointHasScope = (endpointGuid: string, scope: CfScopeStrings) => compose( + selectCurrentUserGlobalHasScopes(scope), getCurrentUserCFEndpointScopesState(endpointGuid) ); // ============================ @@ -142,7 +142,7 @@ export const getCurrentUserCFOrgRolesState = (endpointGuid: string, orgId: strin // Get an array of space guid that have a particular role // anf from a particular org // ============================ -export const getSpacesFromOrgWithRole = (endpointGuid: string, orgId: string, role: PermissionStrings) => compose( +export const getSpacesFromOrgWithRole = (endpointGuid: string, orgId: string, role: CfPermissionStrings) => compose( selectSpaceWithRoleFromOrg(role, orgId), getCurrentUserCFEndpointRolesState(endpointGuid) ); diff --git a/src/frontend/packages/cloud-foundry/src/store/selectors/cf-users-roles.selector.ts b/src/frontend/packages/cloud-foundry/src/store/selectors/cf-users-roles.selector.ts new file mode 100644 index 0000000000..2d5707711f --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/store/selectors/cf-users-roles.selector.ts @@ -0,0 +1,50 @@ +import { compose } from '@ngrx/store'; + +import { CFAppState } from '../../cf-app-state'; +import { IUserPermissionInOrg } from '../types/cf-user.types'; +import { UsersRolesState } from '../types/users-roles.types'; + +export const selectCfUsersRoles = (state: CFAppState): UsersRolesState => state.manageUsersRoles; + +const selectUsers = (usersRoles: UsersRolesState) => usersRoles.users; +export const selectCfUsersRolesPicked = compose( + selectUsers, + selectCfUsersRoles +); + +const selectNewRoles = (usersRoles: UsersRolesState) => usersRoles.newRoles; +export const selectCfUsersRolesRoles = compose( + selectNewRoles, + selectCfUsersRoles +); + +const selectCfGuid = (usersRoles: UsersRolesState) => usersRoles.cfGuid; +export const selectCfUsersRolesCf = compose( + selectCfGuid, + selectCfUsersRoles +); + +const selectChanged = (usersRoles: UsersRolesState) => usersRoles.changedRoles; +export const selectCfUsersRolesChangedRoles = compose( + selectChanged, + selectCfUsersRoles +); + +const selectNewRoleOrgGuid = (newRoles: IUserPermissionInOrg) => newRoles.orgGuid; +export const selectCfUsersRolesOrgGuid = compose( + selectNewRoleOrgGuid, + selectNewRoles, + selectCfUsersRoles +); + +const isRemove = (usersRoles: UsersRolesState) => usersRoles.isRemove; +export const selectCfUsersIsRemove = compose( + isRemove, + selectCfUsersRoles +); + +const isSetByUsername = (usersRoles: UsersRolesState) => usersRoles.isSetByUsername; +export const selectCfUsersIsSetByUsername = compose( + isSetByUsername, + selectCfUsersRoles +); diff --git a/src/frontend/packages/cloud-foundry/src/store/selectors/cloud-foundry.selector.ts b/src/frontend/packages/cloud-foundry/src/store/selectors/cloud-foundry.selector.ts deleted file mode 100644 index 61e11d3b7c..0000000000 --- a/src/frontend/packages/cloud-foundry/src/store/selectors/cloud-foundry.selector.ts +++ /dev/null @@ -1 +0,0 @@ -// export const getInfoGuid = compose(getMetadataGuid, getAPIResourceMetadata); diff --git a/src/frontend/packages/cloud-foundry/src/store/types/cf-current-user-roles.types.ts b/src/frontend/packages/cloud-foundry/src/store/types/cf-current-user-roles.types.ts index f535579799..83640a6cf8 100644 --- a/src/frontend/packages/cloud-foundry/src/store/types/cf-current-user-roles.types.ts +++ b/src/frontend/packages/cloud-foundry/src/store/types/cf-current-user-roles.types.ts @@ -1,6 +1,5 @@ import { RolesRequestState } from '../../../../store/src/types/current-user-roles.types'; - export enum RoleEntities { ORGS = 'organizations', SPACES = 'spaces' @@ -40,5 +39,5 @@ export interface ICfRolesState { } export interface IAllCfRolesState { - [guid: string]: ICfRolesState; + [guid: string]: ICfRolesState } diff --git a/src/frontend/packages/cloud-foundry/src/store/types/user.types.ts b/src/frontend/packages/cloud-foundry/src/store/types/cf-user.types.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/store/types/user.types.ts rename to src/frontend/packages/cloud-foundry/src/store/types/cf-user.types.ts diff --git a/src/frontend/packages/cloud-foundry/src/store/types/users-roles.types.ts b/src/frontend/packages/cloud-foundry/src/store/types/users-roles.types.ts index 5e9b41a9da..e6c7dcb045 100644 --- a/src/frontend/packages/cloud-foundry/src/store/types/users-roles.types.ts +++ b/src/frontend/packages/cloud-foundry/src/store/types/users-roles.types.ts @@ -1,4 +1,4 @@ -import { CfUser, CfUserRoleParams, IUserPermissionInOrg, OrgUserRoleNames, SpaceUserRoleNames } from './user.types'; +import { CfUser, CfUserRoleParams, IUserPermissionInOrg, OrgUserRoleNames, SpaceUserRoleNames } from './cf-user.types'; export interface UsersRolesState { cfGuid: string; diff --git a/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-permissions-checkers.ts b/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-permissions-checkers.ts new file mode 100644 index 0000000000..b38816caf9 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-permissions-checkers.ts @@ -0,0 +1,492 @@ +import { Store } from '@ngrx/store'; +import { combineLatest, Observable, of } from 'rxjs'; +import { distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators'; + +import { + IPermissionConfigs, + PermissionConfig, + PermissionConfigLink, + PermissionTypes, + PermissionValues, +} from '../../../core/src/core/permissions/current-user-permissions.config'; +import { + CurrentUserPermissionsService, + CUSTOM_USER_PERMISSION_CHECKERS, +} from '../../../core/src/core/permissions/current-user-permissions.service'; +import { + BaseCurrentUserPermissionsChecker, + IConfigGroup, + IConfigGroups, + ICurrentUserPermissionsChecker, + IPermissionCheckCombiner, +} from '../../../core/src/core/permissions/current-user-permissions.types'; +import { GeneralEntityAppState } from '../../../store/src/app-state'; +import { connectedEndpointsSelector } from '../../../store/src/selectors/endpoint.selectors'; +import { CFFeatureFlagTypes, IFeatureFlag } from '../cf-api.types'; +import { cfEntityCatalog } from '../cf-entity-catalog'; +import { CF_ENDPOINT_TYPE } from '../cf-types'; +import { + getCurrentUserCFEndpointHasScope, + getCurrentUserCFEndpointRolesState, + getCurrentUserCFGlobalState, +} from '../store/selectors/cf-current-user-role.selectors'; +import { IOrgRoleState, ISpaceRoleState, ISpacesRoleState } from '../store/types/cf-current-user-roles.types'; + +export const cfCurrentUserPermissionsService = [ + { + provide: CUSTOM_USER_PERMISSION_CHECKERS, + useFactory: (store: Store) => [new CfUserPermissionsChecker(store)], + deps: [Store] + }, + CurrentUserPermissionsService, +] + +export enum CfCurrentUserPermissions { + APPLICATION_VIEW = 'view.application', + APPLICATION_EDIT = 'edit.application', + APPLICATION_CREATE = 'create.application', + APPLICATION_MANAGE = 'manage.application', + APPLICATION_VIEW_ENV_VARS = 'env-vars.view.application', + SPACE_VIEW = 'view.space', + SPACE_CREATE = 'create.space', + SPACE_DELETE = 'delete.space', + SPACE_EDIT = 'edit.space', + SPACE_CHANGE_ROLES = 'change-roles.space', + ROUTE_CREATE = 'create.route', + // ROUTE_BINDING_CREATE = 'create.binding.route', + QUOTA_CREATE = 'create.quota', + QUOTA_EDIT = 'edit.quota', + QUOTA_DELETE = 'delete.quota', + SPACE_QUOTA_CREATE = 'create.space-quota', + SPACE_QUOTA_EDIT = 'edit.space-quota', + SPACE_QUOTA_DELETE = 'delete.space-quota', + ORGANIZATION_CREATE = 'create.org', + ORGANIZATION_DELETE = 'delete.org', + ORGANIZATION_EDIT = 'edit.org', + ORGANIZATION_SUSPEND = 'suspend.org', + ORGANIZATION_CHANGE_ROLES = 'change-roles.org', + SERVICE_INSTANCE_DELETE = 'delete.service-instance', + SERVICE_INSTANCE_CREATE = 'create.service-instance', + SERVICE_BINDING_EDIT = 'edit.service-binding', + FIREHOSE_VIEW = 'view-firehose', + SERVICE_INSTANCE_EDIT = 'edit.service-instance' +} + +export enum CfPermissionStrings { + _GLOBAL_ = 'global', + SPACE_MANAGER = 'isManager', + SPACE_AUDITOR = 'isAuditor', + SPACE_DEVELOPER = 'isDeveloper', + ORG_MANAGER = 'isManager', + ORG_AUDITOR = 'isAuditor', + ORG_BILLING_MANAGER = 'isBillingManager', + ORG_USER = 'isUser', +} + +export enum CfScopeStrings { + CF_ADMIN_GROUP = 'cloud_controller.admin', + CF_READ_ONLY_ADMIN_GROUP = 'cloud_controller.admin_read_only', + CF_ADMIN_GLOBAL_AUDITOR_GROUP = 'cloud_controller.global_auditor', + CF_WRITE_SCOPE = 'cloud_controller.write', + CF_READ_SCOPE = 'cloud_controller.write', +} + +export enum CfPermissionTypes { + SPACE = 'spaces', + ORGANIZATION = 'organizations', + ENDPOINT = 'endpoint', + ENDPOINT_SCOPE = 'endpoint-scope', + FEATURE_FLAG = 'feature-flag', +} + +enum CHECKER_GROUPS { + CF_GROUP = '__CF_TYPE__' +} + +// For each set permissions are checked by permission types of ENDPOINT, ENDPOINT_SCOPE, STRATOS_SCOPE, FEATURE_FLAG or a random bag. +// Every group result must be true in order for the permission to be true. A group result is true if all or some of it's permissions are +// true (see `getCheckFromConfig`). +export const cfPermissionConfigs: IPermissionConfigs = { + [CfCurrentUserPermissions.APPLICATION_VIEW]: [ + // See #2186 + new PermissionConfig(CfPermissionTypes.ENDPOINT_SCOPE, CfScopeStrings.CF_READ_ONLY_ADMIN_GROUP), + new PermissionConfig(CfPermissionTypes.ENDPOINT_SCOPE, CfScopeStrings.CF_ADMIN_GLOBAL_AUDITOR_GROUP), + new PermissionConfig(CfPermissionTypes.ORGANIZATION, CfPermissionStrings.ORG_MANAGER), + new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_MANAGER), + new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_AUDITOR), + new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_DEVELOPER) + ], + [CfCurrentUserPermissions.APPLICATION_CREATE]: new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_DEVELOPER), + [CfCurrentUserPermissions.APPLICATION_MANAGE]: new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_DEVELOPER), + [CfCurrentUserPermissions.APPLICATION_EDIT]: new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_DEVELOPER), + [CfCurrentUserPermissions.APPLICATION_VIEW_ENV_VARS]: new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_DEVELOPER), + [CfCurrentUserPermissions.SPACE_VIEW]: [ + // See #2186 + new PermissionConfig(CfPermissionTypes.ENDPOINT_SCOPE, CfScopeStrings.CF_READ_ONLY_ADMIN_GROUP), + new PermissionConfig(CfPermissionTypes.ENDPOINT_SCOPE, CfScopeStrings.CF_ADMIN_GLOBAL_AUDITOR_GROUP), + new PermissionConfig(CfPermissionTypes.ORGANIZATION, CfPermissionStrings.ORG_MANAGER), + new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_MANAGER), + new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_AUDITOR), + new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_DEVELOPER) + ], + [CfCurrentUserPermissions.SPACE_CREATE]: new PermissionConfig(CfPermissionTypes.ORGANIZATION, CfPermissionStrings.ORG_MANAGER), + [CfCurrentUserPermissions.SPACE_DELETE]: new PermissionConfig(CfPermissionTypes.ORGANIZATION, CfPermissionStrings.ORG_MANAGER), + [CfCurrentUserPermissions.SPACE_EDIT]: [ + new PermissionConfig(CfPermissionTypes.ORGANIZATION, CfPermissionStrings.ORG_MANAGER), + new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_MANAGER), + ], + [CfCurrentUserPermissions.SPACE_CHANGE_ROLES]: [ + new PermissionConfig(CfPermissionTypes.ORGANIZATION, CfPermissionStrings.ORG_MANAGER), + new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_MANAGER) + ], + // TODO: See #4189. Wire in. Can be org manager? + [CfCurrentUserPermissions.ROUTE_CREATE]: [ + new PermissionConfig(CfPermissionTypes.FEATURE_FLAG, CFFeatureFlagTypes.route_creation), + new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_DEVELOPER) + ], + [CfCurrentUserPermissions.QUOTA_CREATE]: new PermissionConfig(CfPermissionTypes.ENDPOINT_SCOPE, CfScopeStrings.CF_ADMIN_GROUP), + [CfCurrentUserPermissions.QUOTA_EDIT]: new PermissionConfig(CfPermissionTypes.ENDPOINT_SCOPE, CfScopeStrings.CF_ADMIN_GROUP), + [CfCurrentUserPermissions.QUOTA_DELETE]: new PermissionConfig(CfPermissionTypes.ENDPOINT_SCOPE, CfScopeStrings.CF_ADMIN_GROUP), + [CfCurrentUserPermissions.SPACE_QUOTA_CREATE]: new PermissionConfig(CfPermissionTypes.ORGANIZATION, CfPermissionStrings.ORG_MANAGER), + [CfCurrentUserPermissions.SPACE_QUOTA_EDIT]: new PermissionConfig(CfPermissionTypes.ORGANIZATION, CfPermissionStrings.ORG_MANAGER), + [CfCurrentUserPermissions.SPACE_QUOTA_DELETE]: new PermissionConfig(CfPermissionTypes.ORGANIZATION, CfPermissionStrings.ORG_MANAGER), + [CfCurrentUserPermissions.ORGANIZATION_CREATE]: [ + // is admin (checked for everything) or FF is on and user has a role + new PermissionConfig(CfPermissionTypes.FEATURE_FLAG, CFFeatureFlagTypes.user_org_creation), + new PermissionConfig(CfPermissionTypes.ORGANIZATION, CfPermissionStrings.ORG_MANAGER), + new PermissionConfig(CfPermissionTypes.ORGANIZATION, CfPermissionStrings.ORG_AUDITOR), + new PermissionConfig(CfPermissionTypes.ORGANIZATION, CfPermissionStrings.ORG_BILLING_MANAGER), + new PermissionConfig(CfPermissionTypes.ORGANIZATION, CfPermissionStrings.ORG_USER), + new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_MANAGER), + new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_AUDITOR), + new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_DEVELOPER) + ], + [CfCurrentUserPermissions.ORGANIZATION_DELETE]: new PermissionConfig(CfPermissionTypes.ENDPOINT_SCOPE, CfScopeStrings.CF_ADMIN_GROUP), + [CfCurrentUserPermissions.ORGANIZATION_EDIT]: new PermissionConfigLink(CfCurrentUserPermissions.ORGANIZATION_DELETE), + [CfCurrentUserPermissions.ORGANIZATION_SUSPEND]: new PermissionConfig(CfPermissionTypes.ENDPOINT_SCOPE, CfScopeStrings.CF_ADMIN_GROUP), + [CfCurrentUserPermissions.ORGANIZATION_CHANGE_ROLES]: new PermissionConfig(CfPermissionTypes.ORGANIZATION, CfPermissionStrings.ORG_MANAGER), + [CfCurrentUserPermissions.SERVICE_INSTANCE_DELETE]: new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_DEVELOPER), + [CfCurrentUserPermissions.SERVICE_INSTANCE_CREATE]: new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_DEVELOPER), + [CfCurrentUserPermissions.SERVICE_INSTANCE_EDIT]: new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_DEVELOPER), + [CfCurrentUserPermissions.SERVICE_BINDING_EDIT]: new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_DEVELOPER), + [CfCurrentUserPermissions.FIREHOSE_VIEW]: [ + new PermissionConfig(CfPermissionTypes.ENDPOINT_SCOPE, CfScopeStrings.CF_READ_ONLY_ADMIN_GROUP) + ], +}; + +export class CfUserPermissionsChecker extends BaseCurrentUserPermissionsChecker implements ICurrentUserPermissionsChecker { + static readonly ALL_SPACES = 'PERMISSIONS__ALL_SPACES_PLEASE'; + + constructor(private store: Store) { + super(); + } + + getPermissionConfig(action: string) { + return cfPermissionConfigs[action]; + } + + private check( + type: PermissionTypes, + permission: PermissionValues, + endpointGuid?: string, + orgOrSpaceGuid?: string, + allSpacesWithinOrg = false + ) { + if (type === CfPermissionTypes.ENDPOINT_SCOPE) { + if (!endpointGuid) { + return of(false); + } + return this.store.select(getCurrentUserCFEndpointHasScope(endpointGuid, permission as CfScopeStrings)); + } + + if (type === CfPermissionTypes.ENDPOINT) { + return this.store.select(getCurrentUserCFGlobalState(endpointGuid, permission)); + } + return this.getCfEndpointState(endpointGuid).pipe( + filter(state => !!state), + map(state => { + const permissionString = permission as CfPermissionStrings; + if (allSpacesWithinOrg) { + const spaceState = state[CfPermissionTypes.SPACE]; + return this.checkAllSpacesInOrg(state[CfPermissionTypes.ORGANIZATION][orgOrSpaceGuid], spaceState, permissionString); + } + return this.selectPermission(state[type][orgOrSpaceGuid], permissionString); + }), + distinctUntilChanged(), + ); + }; + + /** + * @param permissionConfig Single permission to be checked + */ + public getSimpleCheck(permissionConfig: PermissionConfig, endpointGuid?: string, orgOrSpaceGuid?: string, spaceGuid?: string) { + const check$ = this.getBaseSimpleCheck(permissionConfig, endpointGuid, orgOrSpaceGuid, spaceGuid); + if (permissionConfig.type === CfPermissionTypes.ORGANIZATION || permissionConfig.type === CfPermissionTypes.SPACE) { + return this.applyAdminCheck(check$, endpointGuid); + } + return check$; + } + + private getBaseSimpleCheck(permissionConfig: PermissionConfig, endpointGuid?: string, orgOrSpaceGuid?: string, spaceGuid?: string) { + switch (permissionConfig.type) { + case (CfPermissionTypes.FEATURE_FLAG): + return this.getFeatureFlagCheck(permissionConfig, endpointGuid); + case (CfPermissionTypes.ORGANIZATION): + case (CfPermissionTypes.SPACE): + case (CfPermissionTypes.ENDPOINT): + return this.getCfCheck(permissionConfig, endpointGuid, orgOrSpaceGuid, spaceGuid); + case (CfPermissionTypes.ENDPOINT_SCOPE): + return this.getEndpointScopesCheck(permissionConfig.permission as CfScopeStrings, endpointGuid); + } + } + + private getEndpointScopesCheck(permission: CfScopeStrings, endpointGuid?: string) { + const endpointGuids$ = this.getEndpointGuidObservable(endpointGuid); + return endpointGuids$.pipe( + switchMap(guids => combineLatest(guids.map(guid => this.check(CfPermissionTypes.ENDPOINT_SCOPE, permission, endpointGuid)))), + map(checks => checks.some(check => check)), + distinctUntilChanged() + ); + } + + private getEndpointScopesChecks( + configs: PermissionConfig[], + endpoint?: string + ) { + return configs.map(config => { + const { permission } = config; + return this.getEndpointScopesCheck(permission as CfScopeStrings, endpoint); + }); + } + + private getCfChecks( + configs: PermissionConfig[], + endpointGuid?: string, + orgOrSpaceGuid?: string, + spaceGuid?: string + ): Observable[] { + return configs.map(config => this.getCfCheck(config, endpointGuid, orgOrSpaceGuid, spaceGuid)); + } + + private getCfCheck(config: PermissionConfig, endpointGuid?: string, orgOrSpaceGuid?: string, spaceGuid?: string): Observable { + const { type, permission } = config; + const checkAllSpaces = spaceGuid === CfUserPermissionsChecker.ALL_SPACES; + const actualGuid = type === CfPermissionTypes.SPACE && spaceGuid && !checkAllSpaces ? spaceGuid : orgOrSpaceGuid; + const cfPermissions = permission as CfPermissionStrings; + if (type === CfPermissionTypes.ENDPOINT || (endpointGuid && actualGuid)) { + return this.check(type, cfPermissions, endpointGuid, actualGuid, checkAllSpaces); + } else if (!actualGuid) { + const endpointGuids$ = this.getEndpointGuidObservable(endpointGuid); + return endpointGuids$.pipe( + switchMap(guids => combineLatest(guids.map(guid => this.checkAllOfType(guid, type as CfPermissionTypes, cfPermissions)))), + map(checks => checks.some(check => check)), + distinctUntilChanged() + ); + } + return of(false); + } + + private getFeatureFlagChecks(configs: PermissionConfig[], endpointGuid?: string): Observable[] { + return configs.map(config => { + return this.getFeatureFlagCheck(config, endpointGuid); + }); + } + + private getFeatureFlagCheck(config: PermissionConfig, endpointGuid?: string): Observable { + const permission = config.permission as CFFeatureFlagTypes; + const endpointGuids$ = this.getEndpointGuidObservable(endpointGuid); + return endpointGuids$.pipe( + switchMap(guids => { + const createFFObs = guid => + // For admins we don't have the ff list which is usually fetched right at the start, + // so this can't be a pagination monitor on its own (which doesn't fetch if list is missing) + cfEntityCatalog.featureFlag.store.getPaginationService(guid).entities$; + return combineLatest(guids.map(createFFObs)); + }), + map(endpointFeatureFlags => endpointFeatureFlags.some(featureFlags => this.checkFeatureFlag(featureFlags, permission))), + // startWith(false), // Don't start with anything, this ensures first value out can be trusted. Should never get to the point where + // nothing is returned + distinctUntilChanged(), + ); + } + + private checkFeatureFlag(featureFlags: IFeatureFlag[], permission: CFFeatureFlagTypes) { + const flag = featureFlags.find(ff => ff.name === permission.toString()); + if (!flag) { + return false; + } + return flag.enabled; + } + + private getAdminChecks(endpointGuid?: string) { + const endpointGuids$ = this.getEndpointGuidObservable(endpointGuid); + return endpointGuids$.pipe( + map(guids => guids.map(guid => this.getCfAdminCheck(guid))), + switchMap(checks => BaseCurrentUserPermissionsChecker.reduceChecks(checks)), + ); + } + + /** + * Includes read only admins, global auditors and users that don't have the cloud_controller.write scope + */ + private getReadOnlyCheck(endpointGuid: string) { + return this.getCfEndpointState(endpointGuid).pipe( + map( + cfPermissions => ( + cfPermissions && ( + cfPermissions.global.isGlobalAuditor || + cfPermissions.global.isReadOnlyAdmin || + !cfPermissions.global.canWrite + ) + ) + ), + distinctUntilChanged() + ); + } + + private applyAdminCheck(check$: Observable, endpointGuid?: string): Observable { + const adminCheck$ = this.getAdminChecks(endpointGuid); + const readOnlyCheck$ = this.getReadOnlyChecks(endpointGuid); + return combineLatest( + adminCheck$, + readOnlyCheck$ + ).pipe( + distinctUntilChanged(), + switchMap(([isAdmin, isReadOnly]) => { + if (isAdmin) { + return of(true); + } + if (isReadOnly) { + return of(false); + } + return check$; + }) + ); + } + + /** + * If no endpoint is passed, check them all + */ + private getReadOnlyChecks(endpointGuid?: string) { + const endpointGuids$ = this.getEndpointGuidObservable(endpointGuid); + return endpointGuids$.pipe( + map(guids => guids.map(guid => this.getReadOnlyCheck(guid))), + switchMap(checks => BaseCurrentUserPermissionsChecker.reduceChecks(checks, '&&')) + ); + } + + private getCfAdminCheck(endpointGuid: string) { + return this.getCfEndpointState(endpointGuid).pipe( + filter(cfPermissions => !!cfPermissions), + map(cfPermissions => cfPermissions.global.isAdmin) + ); + } + + private checkAllOfType(endpointGuid: string, type: CfPermissionTypes, permission: CfPermissionStrings, orgGuid?: string) { + return this.getCfEndpointState(endpointGuid).pipe( + map(state => { + if (!state || !state[type]) { + return false; + } + return Object.keys(state[type]).some(guid => { + return this.selectPermission(state[type][guid], permission); + }); + }) + ); + } + + private getAllEndpointGuids() { + return this.store.select(connectedEndpointsSelector).pipe( + map(endpoints => Object.values(endpoints).filter(e => e.cnsi_type === CF_ENDPOINT_TYPE).map(endpoint => endpoint.guid)) + ); + } + + private getEndpointGuidObservable(endpointGuid: string) { + return !endpointGuid ? this.getAllEndpointGuids() : of([endpointGuid]); + } + + private selectPermission(state: IOrgRoleState | ISpaceRoleState, permission: CfPermissionStrings): boolean { + return state ? state[permission] || false : false; + } + + private checkAllSpacesInOrg(orgState: IOrgRoleState, endpointSpaces: ISpacesRoleState, permission: CfPermissionStrings) { + const spaceGuids = !!orgState && orgState.spaceGuids ? orgState.spaceGuids : []; + return spaceGuids.map(spaceGuid => { + const space = endpointSpaces[spaceGuid]; + return space ? space[permission] || false : false; + }).some(check => check); + + } + + private getCfEndpointState(endpointGuid: string) { + return this.store.select(getCurrentUserCFEndpointRolesState(endpointGuid)); + } + + public getComplexCheck( + permissionConfigs: PermissionConfig[], + endpointGuid?: string, + orgOrSpaceGuid?: string, + spaceGuid?: string + ): IPermissionCheckCombiner[] { + const groupedChecks = this.groupConfigs(permissionConfigs); + return Object.keys(groupedChecks).map((permission: PermissionTypes) => { + const configGroup = groupedChecks[permission]; + const checkCombiner = this.getBaseCheckFromConfig(configGroup, permission, endpointGuid, orgOrSpaceGuid, spaceGuid) + if (checkCombiner) { + checkCombiner.checks = checkCombiner.checks.map(check$ => this.applyAdminCheck(check$, endpointGuid)) + } + return checkCombiner; + }); + } + + + private groupConfigs(configs: PermissionConfig[]): IConfigGroups { + return configs.reduce((grouped, config) => { + const type = this.getGroupType(config); + return { + ...grouped, + [type]: [ + ...(grouped[type] || []), + config + ] + }; + }, {}); + } + + private getGroupType(config: PermissionConfig) { + if (config.type === CfPermissionTypes.ORGANIZATION || config.type === CfPermissionTypes.SPACE) { + return CHECKER_GROUPS.CF_GROUP; + } + return config.type; + } + + + private getBaseCheckFromConfig( + configGroup: IConfigGroup, + permission: CfPermissionTypes | CHECKER_GROUPS | string, + endpointGuid?: string, + orgOrSpaceGuid?: string, + spaceGuid?: string + ): IPermissionCheckCombiner { + switch (permission) { + case CfPermissionTypes.ENDPOINT_SCOPE: + return { + checks: this.getEndpointScopesChecks(configGroup, endpointGuid), + }; + case CfPermissionTypes.FEATURE_FLAG: + return { + checks: this.getFeatureFlagChecks(configGroup, endpointGuid), + combineType: '&&' + }; + case CHECKER_GROUPS.CF_GROUP: + return { + checks: this.getCfChecks(configGroup, endpointGuid, orgOrSpaceGuid, spaceGuid) + }; + } + } + + public getFallbackCheck(endpointGuid: string, endpointType: string) { + return endpointType === CF_ENDPOINT_TYPE ? this.getCfAdminCheck(endpointGuid) : null; + }; + +} \ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-roles-fetch.ts b/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-roles-fetch.ts new file mode 100644 index 0000000000..ec6687ced1 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-roles-fetch.ts @@ -0,0 +1,218 @@ +import { HttpClient } from '@angular/common/http'; +import { Action, Store } from '@ngrx/store'; +import { combineLatest, Observable, of } from 'rxjs'; +import { catchError, first, map, pairwise, share, skipWhile, switchMap, tap } from 'rxjs/operators'; + +import { LoggerService } from '../../../core/src/core/logger.service'; +import { AppState } from '../../../store/src/app-state'; +import { entityCatalog } from '../../../store/src/entity-catalog/entity-catalog'; +import { + EntityUserRolesEndpoint, + EntityUserRolesFetch, +} from '../../../store/src/entity-request-pipeline/entity-request-pipeline.types'; +import { + BaseHttpClientFetcher, + flattenPagination, + PaginationFlattener, +} from '../../../store/src/helpers/paginated-request-helpers'; +import { ActionState } from '../../../store/src/reducers/api-request-reducer/types'; +import { connectedEndpointsOfTypesSelector } from '../../../store/src/selectors/endpoint.selectors'; +import { selectPaginationState } from '../../../store/src/selectors/pagination.selectors'; +import { BasePaginatedAction, PaginationEntityState } from '../../../store/src/types/pagination.types'; +import { + CfUserRelationTypes, + GET_CURRENT_CF_USER_RELATIONS, + GET_CURRENT_CF_USER_RELATIONS_FAILED, + GET_CURRENT_CF_USER_RELATIONS_SUCCESS, + GetCfUserRelations, + GetCurrentCfUserRelations, + GetCurrentCfUserRelationsComplete, +} from '../actions/permissions.actions'; +import { cfEntityCatalog } from '../cf-entity-catalog'; +import { CF_ENDPOINT_TYPE } from '../cf-types'; +import { CFResponse } from '../store/types/cf-api.types'; + +const createEndpointArray = (store: Store, endpoints: string[] | EntityUserRolesEndpoint[]): Observable => { + // If there's no endpoints get all from store. Alternatively fetch specific endpoint id's from store + if (!endpoints || !endpoints.length || typeof (endpoints[0]) === 'string') { + const endpointIds = endpoints as string[]; + return store.select(connectedEndpointsOfTypesSelector(CF_ENDPOINT_TYPE)).pipe( + first(), + map(cfEndpoints => endpointIds.length === 0 ? + Object.values(cfEndpoints) : + Object.values(cfEndpoints).filter(cfEndpoint => endpointIds.find(endpointId => endpointId === cfEndpoint.guid)) + ), + ); + } + return of(endpoints as EntityUserRolesEndpoint[]); +} + +export const cfUserRolesFetch: EntityUserRolesFetch = ( + endpoints: string[] | EntityUserRolesEndpoint[], + store: Store, + logService: LoggerService, + httpClient: HttpClient +) => { + return createEndpointArray(store, endpoints).pipe( + switchMap((cfEndpoints: EntityUserRolesEndpoint[]) => { + const isAllAdmins = cfEndpoints.every(endpoint => !!endpoint.user.admin); + // If all endpoints are connected as admin, there's no permissions to fetch. So only update the permission state to initialised + if (isAllAdmins) { + cfEndpoints.forEach(endpoint => store.dispatch(new GetCfUserRelations(endpoint.guid, GET_CURRENT_CF_USER_RELATIONS_SUCCESS))) + } else { + // If some endpoints are not connected as admin, go out and fetch the current user's specific roles + const flagsAndRoleRequests = dispatchRoleRequests(cfEndpoints, store, logService, httpClient); + const allRequestsCompleted = handleCfRequests(flagsAndRoleRequests); + return combineLatest(allRequestsCompleted).pipe( + map(succeeds => succeeds.every(succeeded => !!succeeded)), + ); + } + return of(true); + }) + ) +} + +interface CfsRequestState { + [cfGuid: string]: Observable[]; +} + +interface IEndpointConnectionInfo { + guid: string; + userGuid: string; +} + +function dispatchRoleRequests( + endpoints: EntityUserRolesEndpoint[], + store: Store, + logService: LoggerService, + httpClient: HttpClient +): CfsRequestState { + const requests: CfsRequestState = {}; + + // Per endpoint fetch feature flags and user roles (unless admin, where we don't need to), then mark endpoint as initialised + endpoints.forEach(endpoint => { + if (endpoint.user.admin) { + // We don't need permissions for admin users (they can do everything) + requests[endpoint.guid] = [of(true)]; + store.dispatch(new GetCfUserRelations(endpoint.guid, GET_CURRENT_CF_USER_RELATIONS_SUCCESS)); + } else { + // START fetching cf roles for current user + store.dispatch(new GetCfUserRelations(endpoint.guid, GET_CURRENT_CF_USER_RELATIONS)); + + // Dispatch feature flags fetch actions + const ffAction = cfEntityCatalog.featureFlag.actions.getMultiple(endpoint.guid) + requests[endpoint.guid] = [createPaginationCompleteWatcher(store, ffAction)]; + store.dispatch(ffAction); + + // Dispatch requests to fetch roles per role type for current user + requests[endpoint.guid].push(...fetchCfUserRoles({ guid: endpoint.guid, userGuid: endpoint.user.guid }, store, httpClient)); + + // FINISH fetching cf roles for current user + combineLatest(requests[endpoint.guid]).pipe( + first(), + tap(succeeds => { + store.dispatch(new GetCfUserRelations( + endpoint.guid, + succeeds.every(succeeded => !!succeeded) ? GET_CURRENT_CF_USER_RELATIONS_SUCCESS : GET_CURRENT_CF_USER_RELATIONS_FAILED) + ); + }), + catchError(err => { + logService.warn('Failed to fetch current user permissions for a cf: ', err); + store.dispatch(new GetCfUserRelations(endpoint.guid, GET_CURRENT_CF_USER_RELATIONS_FAILED)); + return of(err); + }) + ).subscribe(); + } + }); + return requests; +} + +function handleCfRequests(requests: CfsRequestState): Observable[] { + const allCompleted: Observable[] = []; + Object.keys(requests).forEach(cfGuid => { + const successes = requests[cfGuid]; + allCompleted.push(...successes); + }); + return allCompleted; +} + +function fetchCfUserRoles(endpoint: IEndpointConnectionInfo, store: Store, httpClient: HttpClient): Observable[] { + return Object.values(CfUserRelationTypes).map((type: CfUserRelationTypes) => { + const relAction = new GetCurrentCfUserRelations(endpoint.userGuid, type, endpoint.guid); + return fetchCfUserRole(store, relAction, httpClient); + }); +} + +class PermissionFlattener extends BaseHttpClientFetcher implements PaginationFlattener { + + constructor(httpClient: HttpClient, public url, public requestOptions: { [key: string]: any }) { + super(httpClient, url, requestOptions, 'page'); + } + public getTotalPages = (res: CFResponse) => res.total_pages; + + public mergePages = (res: CFResponse[]) => { + const firstRes = res.shift(); + const final = res.reduce((finalRes, currentRes) => { + finalRes.resources = [ + ...finalRes.resources, + ]; + return finalRes; + }, firstRes); + return final; + } + public getTotalResults = (res: CFResponse): number => res.total_results; + public clearResults = (res: CFResponse) => of(res); +} + +export function fetchCfUserRole(store: Store, action: GetCurrentCfUserRelations, httpClient: HttpClient): Observable { + const url = `pp/v1/proxy/v2/users/${action.guid}/${action.relationType}`; + const params = { + headers: { + 'x-cap-cnsi-list': action.endpointGuid, + 'x-cap-passthrough': 'true' + }, + params: { + 'results-per-page': '100' + } + }; + const get$ = httpClient.get( + url, + params + ); + return flattenPagination( + (flatAction: Action) => store.dispatch(flatAction), + get$, + new PermissionFlattener(httpClient, url, params) + ).pipe( + map(data => { + store.dispatch(new GetCurrentCfUserRelationsComplete(action.relationType, action.endpointGuid, data.resources)); + return true; + }), + first(), + catchError(err => of(false)), + share() + ); +} + +const fetchPaginationStateFromAction = (store: Store, action: BasePaginatedAction) => { + const entityKey = entityCatalog.getEntityKey(action); + return store.select(selectPaginationState(entityKey, action.paginationKey)); +}; + +/** + * Using the given action wait until the associated pagination section changes from busy to not busy + */ +const createPaginationCompleteWatcher = (store: Store, action: BasePaginatedAction): Observable => + fetchPaginationStateFromAction(store, action).pipe( + map((paginationState: PaginationEntityState) => { + const pageRequest: ActionState = + paginationState && paginationState.pageRequests && paginationState.pageRequests[paginationState.currentPage]; + return pageRequest ? pageRequest.busy : true; + }), + pairwise(), + map(([oldFetching, newFetching]) => { + return oldFetching === true && newFetching === false; + }), + skipWhile(completed => !completed), + first(), + ); \ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/test-framework/cloud-foundry-endpoint-service.helper.ts b/src/frontend/packages/cloud-foundry/test-framework/cloud-foundry-endpoint-service.helper.ts index dfb1e5297e..2174658903 100644 --- a/src/frontend/packages/cloud-foundry/test-framework/cloud-foundry-endpoint-service.helper.ts +++ b/src/frontend/packages/cloud-foundry/test-framework/cloud-foundry-endpoint-service.helper.ts @@ -3,7 +3,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { Store, StoreModule } from '@ngrx/store'; -import { testSCFEndpointGuid } from '@stratos/store/testing'; +import { testSCFEndpointGuid } from '@stratosui/store/testing'; import { CoreModule } from '../../core/src/core/core.module'; import { SharedModule } from '../../core/src/shared/shared.module'; @@ -23,7 +23,7 @@ import { import { CfOrgSpaceDataService } from '../src/shared/data-services/cf-org-space-service.service'; import { CfUserService } from '../src/shared/data-services/cf-user.service'; import { CloudFoundryService } from '../src/shared/data-services/cloud-foundry.service'; -import { createUserRoleInOrg } from '../src/store/types/user.types'; +import { createUserRoleInOrg } from '../src/store/types/cf-user.types'; import { CfUserServiceTestProvider } from './user-service-helper'; export const cfEndpointServiceProviderDeps = [ diff --git a/src/frontend/packages/core/src/app.component.spec.ts b/src/frontend/packages/core/src/app.component.spec.ts index ef7f9b6321..1894f848e5 100644 --- a/src/frontend/packages/core/src/app.component.spec.ts +++ b/src/frontend/packages/core/src/app.component.spec.ts @@ -1,8 +1,8 @@ import { async, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { AppComponent } from './app.component'; import { LoggedInService } from './logged-in.service'; import { SharedModule } from './shared/shared.module'; diff --git a/src/frontend/packages/core/src/app.module.ts b/src/frontend/packages/core/src/app.module.ts index 1cb71ceaa0..7ba9fb46a1 100644 --- a/src/frontend/packages/core/src/app.module.ts +++ b/src/frontend/packages/core/src/app.module.ts @@ -1,13 +1,13 @@ -import { NgModule, Injectable } from '@angular/core'; +import { Injectable, NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { Params, RouteReuseStrategy, RouterStateSnapshot } from '@angular/router'; -import { RouterStateSerializer, StoreRouterConnectingModule, DefaultRouterStateSerializer } from '@ngrx/router-store'; +import { DefaultRouterStateSerializer, RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store'; import { Store } from '@ngrx/store'; import { debounceTime, filter, withLatestFrom } from 'rxjs/operators'; import { CfAutoscalerModule } from '../../cf-autoscaler/src/cf-autoscaler.module'; -import { CloudFoundryPackageModule } from '../../cloud-foundry/src/cloud-foundry.module'; +import { CloudFoundryPackageModule } from '../../cloud-foundry/src/cloud-foundry-package.module'; import { SetRecentlyVisitedEntityAction } from '../../store/src/actions/recently-visited.actions'; import { UpdateUserFavoriteMetadataAction, @@ -35,6 +35,7 @@ import { CustomizationService } from './core/customizations.types'; import { DynamicExtensionRoutes } from './core/extension/dynamic-extension-routes'; import { ExtensionService } from './core/extension/extension-service'; import { getGitHubAPIURL, GITHUB_API_URL } from './core/github.helpers'; +import { CurrentUserPermissionsService } from './core/permissions/current-user-permissions.service'; import { UserFavoriteManager } from './core/user-favorite-manager'; import { CustomImportModule } from './custom-import.module'; import { AboutModule } from './features/about/about.module'; @@ -116,7 +117,8 @@ export class CustomRouterStateSerializer SidePanelService, { provide: GITHUB_API_URL, useFactory: getGitHubAPIURL }, { provide: RouterStateSerializer, useClass: CustomRouterStateSerializer }, // Create action for router navigation - { provide: RouteReuseStrategy, useClass: CustomReuseStrategy } + { provide: RouteReuseStrategy, useClass: CustomReuseStrategy }, + CurrentUserPermissionsService ], bootstrap: [AppComponent] }) diff --git a/src/frontend/packages/core/src/core/core.module.ts b/src/frontend/packages/core/src/core/core.module.ts index e3ee91c394..ff8b4eb280 100644 --- a/src/frontend/packages/core/src/core/core.module.ts +++ b/src/frontend/packages/core/src/core/core.module.ts @@ -17,7 +17,6 @@ import { ButtonBlurOnClickDirective } from './button-blur-on-click.directive'; import { BytesToHumanSize, MegaBytesToHumanSize } from './byte-formatters.pipe'; import { ClickStopPropagationDirective } from './click-stop-propagation.directive'; import { APP_TITLE, appTitleFactory } from './core.types'; -import { CurrentUserPermissionsService } from './current-user-permissions.service'; import { DisableRouterLinkDirective } from './disable-router-link.directive'; import { DotContentComponent } from './dot-content/dot-content.component'; import { EndpointsService } from './endpoints.service'; @@ -85,7 +84,6 @@ import { WindowRef } from './window-ref/window-ref.service'; PaginationMonitorFactory, UserProfileService, EntityServiceFactory, - CurrentUserPermissionsService, { provide: APP_TITLE, useFactory: appTitleFactory, diff --git a/src/frontend/packages/core/src/core/current-user-permissions.checker.ts b/src/frontend/packages/core/src/core/current-user-permissions.checker.ts deleted file mode 100644 index 1d50f368f4..0000000000 --- a/src/frontend/packages/core/src/core/current-user-permissions.checker.ts +++ /dev/null @@ -1,317 +0,0 @@ -import { Store } from '@ngrx/store'; -import { combineLatest, Observable, of as observableOf } from 'rxjs'; -import { distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators'; - -import { CFFeatureFlagTypes, IFeatureFlag } from '../../../cloud-foundry/src/cf-api.types'; -import { cfEntityCatalog } from '../../../cloud-foundry/src/cf-entity-catalog'; -import { - getCurrentUserCFEndpointHasScope, - getCurrentUserCFEndpointRolesState, - getCurrentUserCFGlobalState, -} from '../../../cloud-foundry/src/store/selectors/cf-current-user-role.selectors'; -import { - IOrgRoleState, - ISpaceRoleState, - ISpacesRoleState, -} from '../../../cloud-foundry/src/store/types/cf-current-user-roles.types'; -import { GeneralEntityAppState } from '../../../store/src/app-state'; -import { - getCurrentUserStratosHasScope, - getCurrentUserStratosRole, -} from '../../../store/src/selectors/current-user-role.selectors'; -import { connectedEndpointsSelector } from '../../../store/src/selectors/endpoint.selectors'; -import { - PermissionConfig, - PermissionStrings, - PermissionTypes, - PermissionValues, - ScopeStrings, -} from './current-user-permissions.config'; - - -export interface IConfigGroups { - [permissionType: string]: IConfigGroup; -} -export enum CHECKER_GROUPS { - CF_GROUP = '__CF_TYPE__' -} - -export type IConfigGroup = PermissionConfig[]; -export class CurrentUserPermissionsChecker { - static readonly ALL_SPACES = 'PERMISSIONS__ALL_SPACES_PLEASE'; - constructor(private store: Store, ) { } - public check( - type: PermissionTypes, - permission: PermissionValues, - endpointGuid?: string, - orgOrSpaceGuid?: string, - allSpacesWithinOrg = false - ) { - if (type === PermissionTypes.STRATOS) { - return this.store.select(getCurrentUserStratosRole(permission)); - } - - if (type === PermissionTypes.STRATOS_SCOPE) { - return this.store.select(getCurrentUserStratosHasScope(permission as ScopeStrings)); - } - - if (type === PermissionTypes.ENDPOINT_SCOPE) { - if (!endpointGuid) { - return observableOf(false); - } - return this.store.select(getCurrentUserCFEndpointHasScope(endpointGuid, permission as ScopeStrings)); - } - - if (type === PermissionTypes.ENDPOINT) { - return this.store.select(getCurrentUserCFGlobalState(endpointGuid, permission)); - } - return this.getEndpointState(endpointGuid).pipe( - filter(state => !!state), - map(state => { - const permissionString = permission as PermissionStrings; - if (allSpacesWithinOrg) { - const spaceState = state[PermissionTypes.SPACE]; - return this.checkAllSpacesInOrg(state[PermissionTypes.ORGANIZATION][orgOrSpaceGuid], spaceState, permissionString); - } - return this.selectPermission(state[type][orgOrSpaceGuid], permissionString); - }), - distinctUntilChanged(), - ); - } - /** - * @param permissionConfig Single permission to be checked - */ - public getSimpleCheck(permissionConfig: PermissionConfig, endpointGuid?: string, orgOrSpaceGuid?: string, spaceGuid?: string) { - switch (permissionConfig.type) { - case (PermissionTypes.FEATURE_FLAG): - return this.getFeatureFlagCheck(permissionConfig, endpointGuid); - case (PermissionTypes.ORGANIZATION): - case (PermissionTypes.SPACE): - case (PermissionTypes.ENDPOINT): - return this.getCfCheck(permissionConfig, endpointGuid, orgOrSpaceGuid, spaceGuid); - case (PermissionTypes.STRATOS): - return this.getInternalCheck(permissionConfig.permission as PermissionStrings); - case (PermissionTypes.STRATOS_SCOPE): - return this.getInternalScopesCheck(permissionConfig.permission as ScopeStrings); - case (PermissionTypes.ENDPOINT_SCOPE): - return this.getEndpointScopesCheck(permissionConfig.permission as ScopeStrings, endpointGuid); - } - } - - private checkAllSpacesInOrg(orgState: IOrgRoleState, endpointSpaces: ISpacesRoleState, permission: PermissionStrings) { - const spaceGuids = !!orgState && orgState.spaceGuids ? orgState.spaceGuids : []; - return spaceGuids.map(spaceGuid => { - const space = endpointSpaces[spaceGuid]; - return space ? space[permission] || false : false; - }).some(check => check); - } - - public getInternalChecks( - configs: PermissionConfig[] - ) { - return configs.map(config => { - const { permission } = config; - return this.getInternalCheck(permission as PermissionStrings); - }); - } - - public getInternalCheck(permission: PermissionStrings) { - return this.check(PermissionTypes.STRATOS, permission); - } - - public getInternalScopesChecks( - configs: PermissionConfig[] - ) { - return configs.map(config => { - const { permission } = config; - return this.getInternalScopesCheck(permission as ScopeStrings); - }); - } - - public getEndpointScopesCheck(permission: ScopeStrings, endpointGuid?: string) { - const endpointGuids$ = this.getEndpointGuidObservable(endpointGuid); - return endpointGuids$.pipe( - switchMap(guids => combineLatest(guids.map(guid => this.check(PermissionTypes.ENDPOINT_SCOPE, permission, endpointGuid)))), - map(checks => checks.some(check => check)), - distinctUntilChanged() - ); - } - - public getEndpointScopesChecks( - configs: PermissionConfig[], - endpoint?: string - ) { - return configs.map(config => { - const { permission } = config; - return this.getEndpointScopesCheck(permission as ScopeStrings, endpoint); - }); - } - - public getInternalScopesCheck(permission: ScopeStrings) { - return this.check(PermissionTypes.STRATOS_SCOPE, permission); - } - - public getCfChecks( - configs: PermissionConfig[], - endpointGuid?: string, - orgOrSpaceGuid?: string, - spaceGuid?: string - ): Observable[] { - return configs.map(config => this.getCfCheck(config, endpointGuid, orgOrSpaceGuid, spaceGuid)); - } - - public getCfCheck(config: PermissionConfig, endpointGuid?: string, orgOrSpaceGuid?: string, spaceGuid?: string): Observable { - const { type, permission } = config; - const checkAllSpaces = spaceGuid === CurrentUserPermissionsChecker.ALL_SPACES; - const actualGuid = type === PermissionTypes.SPACE && spaceGuid && !checkAllSpaces ? spaceGuid : orgOrSpaceGuid; - const cfPermissions = permission as PermissionStrings; - if (type === PermissionTypes.ENDPOINT || (endpointGuid && actualGuid)) { - return this.check(type, cfPermissions, endpointGuid, actualGuid, checkAllSpaces); - } else if (!actualGuid) { - const endpointGuids$ = this.getEndpointGuidObservable(endpointGuid); - return endpointGuids$.pipe( - switchMap(guids => combineLatest(guids.map(guid => this.checkAllOfType(guid, type, cfPermissions)))), - map(checks => checks.some(check => check)), - distinctUntilChanged() - ); - } - return observableOf(false); - } - - public getFeatureFlagChecks(configs: PermissionConfig[], endpointGuid?: string): Observable[] { - return configs.map(config => { - return this.getFeatureFlagCheck(config, endpointGuid); - }); - } - - public getFeatureFlagCheck(config: PermissionConfig, endpointGuid?: string): Observable { - const permission = config.permission as CFFeatureFlagTypes; - const endpointGuids$ = this.getEndpointGuidObservable(endpointGuid); - return endpointGuids$.pipe( - switchMap(guids => { - const createFFObs = guid => - // For admins we don't have the ff list which is usually fetched right at the start, - // so this can't be a pagination monitor on its own (which doesn't fetch if list is missing) - cfEntityCatalog.featureFlag.store.getPaginationService(guid).entities$; - return combineLatest(guids.map(createFFObs)); - }), - map(endpointFeatureFlags => endpointFeatureFlags.some(featureFlags => this.checkFeatureFlag(featureFlags, permission))), - // startWith(false), // Don't start with anything, this ensures first value out can be trusted. Should never get to the point where - // nothing is returned - distinctUntilChanged(), - ); - } - - public checkFeatureFlag(featureFlags: IFeatureFlag[], permission: CFFeatureFlagTypes) { - const flag = featureFlags.find(ff => ff.name === permission.toString()); - if (!flag) { - return false; - } - return flag.enabled; - } - - public getAdminCheck(endpointGuid: string) { - return this.getEndpointState(endpointGuid).pipe( - filter(cfPermissions => !!cfPermissions), - map(cfPermissions => cfPermissions.global.isAdmin) - ); - } - - public getAdminChecks(endpointGuid?: string) { - const endpointGuids$ = this.getEndpointGuidObservable(endpointGuid); - return endpointGuids$.pipe( - map(guids => guids.map(guid => this.getAdminCheck(guid))), - switchMap(checks => this.reduceChecks(checks)) - ); - } - - /** - * Includes read only admins, global auditors and users that don't have the cloud_controller.write scope - */ - public getReadOnlyCheck(endpointGuid: string) { - return this.getEndpointState(endpointGuid).pipe( - map( - cfPermissions => ( - cfPermissions && ( - cfPermissions.global.isGlobalAuditor || - cfPermissions.global.isReadOnlyAdmin || - !cfPermissions.global.canWrite - ) - ) - ), - distinctUntilChanged() - ); - } - /** - * If no endpoint is passed, check them all - */ - public getReadOnlyChecks(endpointGuid?: string) { - const endpointGuids$ = this.getEndpointGuidObservable(endpointGuid); - return endpointGuids$.pipe( - map(guids => guids.map(guid => this.getReadOnlyCheck(guid))), - switchMap(checks => this.reduceChecks(checks, '&&')) - ); - } - - public reduceChecks(checks: Observable[], type: '||' | '&&' = '||') { - const func = type === '||' ? 'some' : 'every'; - if (!checks || !checks.length) { - return observableOf(true); - } - return combineLatest(checks).pipe( - map(flags => flags[func](flag => flag)), - distinctUntilChanged() - ); - } - - public groupConfigs(configs: PermissionConfig[]): IConfigGroups { - return configs.reduce((grouped, config) => { - const type = this.getGroupType(config); - return { - ...grouped, - [type]: [ - ...(grouped[type] || []), - config - ] - }; - }, {}); - } - - private getGroupType(config: PermissionConfig) { - if (config.type === PermissionTypes.ORGANIZATION || config.type === PermissionTypes.SPACE) { - return CHECKER_GROUPS.CF_GROUP; - } - return config.type; - } - - private checkAllOfType(endpointGuid: string, type: PermissionTypes, permission: PermissionStrings, orgGuid?: string) { - return this.getEndpointState(endpointGuid).pipe( - map(state => { - if (!state || !state[type]) { - return false; - } - return Object.keys(state[type]).some(guid => { - return this.selectPermission(state[type][guid], permission); - }); - }) - ); - } - - private getAllEndpointGuids() { - return this.store.select(connectedEndpointsSelector).pipe( - map(endpoints => Object.values(endpoints).filter(e => e.cnsi_type === 'cf').map(endpoint => endpoint.guid)) - ); - } - - private getEndpointGuidObservable(endpointGuid: string) { - return !endpointGuid ? this.getAllEndpointGuids() : observableOf([endpointGuid]); - } - - private selectPermission(state: IOrgRoleState | ISpaceRoleState, permission: PermissionStrings): boolean { - return state ? state[permission] || false : false; - } - - private getEndpointState(endpointGuid: string) { - return this.store.select(getCurrentUserCFEndpointRolesState(endpointGuid)); - } -} diff --git a/src/frontend/packages/core/src/core/current-user-permissions.config.ts b/src/frontend/packages/core/src/core/current-user-permissions.config.ts deleted file mode 100644 index 3781f1ca24..0000000000 --- a/src/frontend/packages/core/src/core/current-user-permissions.config.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { CFFeatureFlagTypes } from '../../../cloud-foundry/src/cf-api.types'; - -export enum CurrentUserPermissions { - APPLICATION_VIEW = 'view.application', - APPLICATION_EDIT = 'edit.application', - APPLICATION_CREATE = 'create.application', - APPLICATION_MANAGE = 'manage.application', - APPLICATION_VIEW_ENV_VARS = 'env-vars.view.application', - SPACE_VIEW = 'view.space', - SPACE_CREATE = 'create.space', - SPACE_DELETE = 'delete.space', - SPACE_EDIT = 'edit.space', - SPACE_CHANGE_ROLES = 'change-roles.space', - ROUTE_CREATE = 'create.route', - // ROUTE_BINDING_CREATE = 'create.binding.route', - QUOTA_CREATE = 'create.quota', - QUOTA_EDIT = 'edit.quota', - QUOTA_DELETE = 'delete.quota', - SPACE_QUOTA_CREATE = 'create.space-quota', - SPACE_QUOTA_EDIT = 'edit.space-quota', - SPACE_QUOTA_DELETE = 'delete.space-quota', - ORGANIZATION_CREATE = 'create.org', - ORGANIZATION_DELETE = 'delete.org', - ORGANIZATION_EDIT = 'edit.org', - ORGANIZATION_SUSPEND = 'suspend.org', - ORGANIZATION_CHANGE_ROLES = 'change-roles.org', - SERVICE_INSTANCE_DELETE = 'delete.service-instance', - SERVICE_INSTANCE_CREATE = 'create.service-instance', - SERVICE_BINDING_EDIT = 'edit.service-binding', - FIREHOSE_VIEW = 'view-firehose', - ENDPOINT_REGISTER = 'register.endpoint', - PASSWORD_CHANGE = 'change-password', - SERVICE_INSTANCE_EDIT = 'edit.service-instance' -} -export type PermissionConfigType = PermissionConfig[] | PermissionConfig | PermissionConfigLink; -export interface IPermissionConfigs { - [permissionString: string]: PermissionConfigType; -} - -export enum PermissionStrings { - _GLOBAL_ = 'global', - SPACE_MANAGER = 'isManager', - SPACE_AUDITOR = 'isAuditor', - SPACE_DEVELOPER = 'isDeveloper', - ORG_MANAGER = 'isManager', - ORG_AUDITOR = 'isAuditor', - ORG_BILLING_MANAGER = 'isBillingManager', - ORG_USER = 'isUser', - STRATOS_ADMIN = 'isAdmin' -} - -export enum ScopeStrings { - CF_ADMIN_GROUP = 'cloud_controller.admin', - CF_READ_ONLY_ADMIN_GROUP = 'cloud_controller.admin_read_only', - CF_ADMIN_GLOBAL_AUDITOR_GROUP = 'cloud_controller.global_auditor', - CF_WRITE_SCOPE = 'cloud_controller.write', - CF_READ_SCOPE = 'cloud_controller.write', - STRATOS_CHANGE_PASSWORD = 'password.write', - SCIM_READ = 'scim.read' -} - -export enum PermissionTypes { - SPACE = 'spaces', - ORGANIZATION = 'organizations', - ENDPOINT = 'endpoint', - ENDPOINT_SCOPE = 'endpoint-scope', - FEATURE_FLAG = 'feature-flag', - STRATOS = 'internal', - STRATOS_SCOPE = 'internal-scope' -} - -export enum StratosPermissionTypes { - ADMIN = 'isAdmin' -} - -export type PermissionValues = StratosPermissionTypes | ScopeStrings | CFFeatureFlagTypes | PermissionStrings; -export class PermissionConfig { - constructor( - public type: PermissionTypes, - public permission: PermissionValues = PermissionStrings._GLOBAL_ - ) { } -} -export class PermissionConfigLink { - constructor( - public link: CurrentUserPermissions - ) { } -} - -// For each set permissions are checked by permission types of ENDPOINT, ENDPOINT_SCOPE, STRATOS_SCOPE, FEATURE_FLAG or a random bag. -// Every group result must be true in order for the permission to be true. A group result is true if all or some of it's permissions are -// true (see `getCheckFromConfig`). -export const permissionConfigs: IPermissionConfigs = { - [CurrentUserPermissions.APPLICATION_VIEW]: [ - // See #2186 - new PermissionConfig(PermissionTypes.ENDPOINT_SCOPE, ScopeStrings.CF_READ_ONLY_ADMIN_GROUP), - new PermissionConfig(PermissionTypes.ENDPOINT_SCOPE, ScopeStrings.CF_ADMIN_GLOBAL_AUDITOR_GROUP), - new PermissionConfig(PermissionTypes.ORGANIZATION, PermissionStrings.ORG_MANAGER), - new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_MANAGER), - new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_AUDITOR), - new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_DEVELOPER) - ], - [CurrentUserPermissions.APPLICATION_CREATE]: new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_DEVELOPER), - [CurrentUserPermissions.APPLICATION_MANAGE]: new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_DEVELOPER), - [CurrentUserPermissions.APPLICATION_EDIT]: new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_DEVELOPER), - [CurrentUserPermissions.APPLICATION_VIEW_ENV_VARS]: new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_DEVELOPER), - [CurrentUserPermissions.SPACE_VIEW]: [ - // See #2186 - new PermissionConfig(PermissionTypes.ENDPOINT_SCOPE, ScopeStrings.CF_READ_ONLY_ADMIN_GROUP), - new PermissionConfig(PermissionTypes.ENDPOINT_SCOPE, ScopeStrings.CF_ADMIN_GLOBAL_AUDITOR_GROUP), - new PermissionConfig(PermissionTypes.ORGANIZATION, PermissionStrings.ORG_MANAGER), - new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_MANAGER), - new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_AUDITOR), - new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_DEVELOPER) - ], - [CurrentUserPermissions.SPACE_CREATE]: new PermissionConfig(PermissionTypes.ORGANIZATION, PermissionStrings.ORG_MANAGER), - [CurrentUserPermissions.SPACE_DELETE]: new PermissionConfig(PermissionTypes.ORGANIZATION, PermissionStrings.ORG_MANAGER), - [CurrentUserPermissions.SPACE_EDIT]: [ - new PermissionConfig(PermissionTypes.ORGANIZATION, PermissionStrings.ORG_MANAGER), - new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_MANAGER), - ], - [CurrentUserPermissions.SPACE_CHANGE_ROLES]: [ - new PermissionConfig(PermissionTypes.ORGANIZATION, PermissionStrings.ORG_MANAGER), - new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_MANAGER) - ], - // TODO: See #4189. Wire in. Can be org manager? - [CurrentUserPermissions.ROUTE_CREATE]: [ - new PermissionConfig(PermissionTypes.FEATURE_FLAG, CFFeatureFlagTypes.route_creation), - new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_DEVELOPER) - ], - [CurrentUserPermissions.QUOTA_CREATE]: new PermissionConfig(PermissionTypes.ENDPOINT_SCOPE, ScopeStrings.CF_ADMIN_GROUP), - [CurrentUserPermissions.QUOTA_EDIT]: new PermissionConfig(PermissionTypes.ENDPOINT_SCOPE, ScopeStrings.CF_ADMIN_GROUP), - [CurrentUserPermissions.QUOTA_DELETE]: new PermissionConfig(PermissionTypes.ENDPOINT_SCOPE, ScopeStrings.CF_ADMIN_GROUP), - [CurrentUserPermissions.SPACE_QUOTA_CREATE]: new PermissionConfig(PermissionTypes.ORGANIZATION, PermissionStrings.ORG_MANAGER), - [CurrentUserPermissions.SPACE_QUOTA_EDIT]: new PermissionConfig(PermissionTypes.ORGANIZATION, PermissionStrings.ORG_MANAGER), - [CurrentUserPermissions.SPACE_QUOTA_DELETE]: new PermissionConfig(PermissionTypes.ORGANIZATION, PermissionStrings.ORG_MANAGER), - [CurrentUserPermissions.ORGANIZATION_CREATE]: [ - new PermissionConfig(PermissionTypes.FEATURE_FLAG, CFFeatureFlagTypes.user_org_creation), - new PermissionConfig(PermissionTypes.ORGANIZATION, PermissionStrings.ORG_MANAGER), - new PermissionConfig(PermissionTypes.ORGANIZATION, PermissionStrings.ORG_AUDITOR), - new PermissionConfig(PermissionTypes.ORGANIZATION, PermissionStrings.ORG_BILLING_MANAGER), - new PermissionConfig(PermissionTypes.ORGANIZATION, PermissionStrings.ORG_USER), - new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_MANAGER), - new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_AUDITOR), - new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_DEVELOPER) - ], - [CurrentUserPermissions.ORGANIZATION_DELETE]: new PermissionConfig(PermissionTypes.ENDPOINT_SCOPE, ScopeStrings.CF_ADMIN_GROUP), - [CurrentUserPermissions.ORGANIZATION_EDIT]: new PermissionConfigLink(CurrentUserPermissions.ORGANIZATION_DELETE), - [CurrentUserPermissions.ORGANIZATION_SUSPEND]: new PermissionConfig(PermissionTypes.ENDPOINT_SCOPE, ScopeStrings.CF_ADMIN_GROUP), - [CurrentUserPermissions.ORGANIZATION_CHANGE_ROLES]: new PermissionConfig(PermissionTypes.ORGANIZATION, PermissionStrings.ORG_MANAGER), - [CurrentUserPermissions.SERVICE_INSTANCE_DELETE]: new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_DEVELOPER), - [CurrentUserPermissions.SERVICE_INSTANCE_CREATE]: new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_DEVELOPER), - [CurrentUserPermissions.SERVICE_INSTANCE_EDIT]: new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_DEVELOPER), - [CurrentUserPermissions.SERVICE_BINDING_EDIT]: new PermissionConfig(PermissionTypes.SPACE, PermissionStrings.SPACE_DEVELOPER), - [CurrentUserPermissions.FIREHOSE_VIEW]: [ - new PermissionConfig(PermissionTypes.ENDPOINT_SCOPE, ScopeStrings.CF_READ_ONLY_ADMIN_GROUP) - ], - [CurrentUserPermissions.ENDPOINT_REGISTER]: new PermissionConfig(PermissionTypes.STRATOS, PermissionStrings.STRATOS_ADMIN), - [CurrentUserPermissions.PASSWORD_CHANGE]: new PermissionConfig(PermissionTypes.STRATOS_SCOPE, ScopeStrings.STRATOS_CHANGE_PASSWORD), -}; diff --git a/src/frontend/packages/core/src/core/current-user-permissions.service.ts b/src/frontend/packages/core/src/core/current-user-permissions.service.ts deleted file mode 100644 index be56d540db..0000000000 --- a/src/frontend/packages/core/src/core/current-user-permissions.service.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Store } from '@ngrx/store'; -import { combineLatest, Observable, of as observableOf } from 'rxjs'; -import { distinctUntilChanged, map, switchMap } from 'rxjs/operators'; - -import { InternalAppState } from '../../../store/src/app-state'; -import { - CHECKER_GROUPS, - CurrentUserPermissionsChecker, - IConfigGroup, - IConfigGroups, -} from './current-user-permissions.checker'; -import { - CurrentUserPermissions, - PermissionConfig, - PermissionConfigLink, - permissionConfigs, - PermissionConfigType, - PermissionTypes, -} from './current-user-permissions.config'; - -interface ICheckCombiner { - checks: Observable[]; - combineType?: '&&'; -} - -@Injectable() -export class CurrentUserPermissionsService { - private checker: CurrentUserPermissionsChecker; - constructor( - store: Store - ) { - this.checker = new CurrentUserPermissionsChecker(store); - } - /** - * @param action The action we're going to check the user's access to. - * @param endpointGuid If endpointGuid is provided without a orgOrSpaceGuid the checks will be done across all orgs and - * spaces within the cf. - * If no endpoint guid is provided we will do the check over all of the endpoint and all orgs/spaces. - * @param orgOrSpaceGuid If this is the only param then it will be used as the id to for all permission checks. - * @param spaceGuid If this is provided then the orgOrSpaceGuid will be used for org related permission checks and this will be - * used for space related permission checks. - */ - public can( - action: CurrentUserPermissions | PermissionConfigType, - endpointGuid?: string, - orgOrSpaceGuid?: string, - spaceGuid?: string - ): Observable { - const actionConfig = this.getConfig(typeof action === 'string' ? permissionConfigs[action] : action); - const obs$ = this.getCanObservable(actionConfig, endpointGuid, orgOrSpaceGuid, spaceGuid); - return obs$ ? obs$.pipe( - distinctUntilChanged(), - ) : observableOf(false); - } - - private getCanObservable( - actionConfig: PermissionConfig[] | PermissionConfig, - endpointGuid: string, - orgOrSpaceGuid?: string, - spaceGuid?: string): Observable { - if (Array.isArray(actionConfig)) { - return this.getComplexPermission(actionConfig, endpointGuid, orgOrSpaceGuid, spaceGuid); - } else if (actionConfig) { - return this.getSimplePermission(actionConfig, endpointGuid, orgOrSpaceGuid, spaceGuid); - } else if (endpointGuid) { - return this.checker.getAdminCheck(endpointGuid); - } - return null; - } - - private getSimplePermission(actionConfig: PermissionConfig, endpointGuid?: string, orgOrSpaceGuid?: string, spaceGuid?: string) { - const check$ = this.checker.getSimpleCheck(actionConfig, endpointGuid, orgOrSpaceGuid, spaceGuid); - if (actionConfig.type === PermissionTypes.ORGANIZATION || actionConfig.type === PermissionTypes.SPACE) { - return this.applyAdminCheck(check$, endpointGuid); - } - return check$; - } - - private getComplexPermission(actionConfigs: PermissionConfig[], endpointGuid?: string, orgOrSpaceGuid?: string, spaceGuid?: string) { - const groupedChecks = this.checker.groupConfigs(actionConfigs); - const checks = this.getChecksFromConfigGroups(groupedChecks, endpointGuid, orgOrSpaceGuid, spaceGuid); - return this.combineChecks(checks, endpointGuid); - } - - private getChecksFromConfigGroups(groups: IConfigGroups, endpointGuid?: string, orgOrSpaceGuid?: string, spaceGuid?: string) { - return Object.keys(groups).map((permission: PermissionTypes) => { - return this.getCheckFromConfig(groups[permission], permission, endpointGuid, orgOrSpaceGuid, spaceGuid); - }); - } - - private getCheckFromConfig( - configGroup: IConfigGroup, - permission: PermissionTypes | CHECKER_GROUPS, - endpointGuid?: string, - orgOrSpaceGuid?: string, - spaceGuid?: string - ): ICheckCombiner { - switch (permission) { - case PermissionTypes.ENDPOINT: - return { - checks: this.checker.getInternalScopesChecks(configGroup), - }; - case PermissionTypes.ENDPOINT_SCOPE: - return { - checks: this.checker.getEndpointScopesChecks(configGroup, endpointGuid), - }; - case PermissionTypes.STRATOS_SCOPE: - return { - checks: this.checker.getInternalScopesChecks(configGroup), - }; - case PermissionTypes.FEATURE_FLAG: - return { - checks: this.checker.getFeatureFlagChecks(configGroup, endpointGuid), - combineType: '&&' - }; - case CHECKER_GROUPS.CF_GROUP: - return { - checks: this.checker.getCfChecks(configGroup, endpointGuid, orgOrSpaceGuid, spaceGuid) - }; - } - } - - private getConfig(config: PermissionConfigType, tries = 0): PermissionConfig[] | PermissionConfig { - const linkConfig = config as PermissionConfigLink; - if (linkConfig.link) { - if (tries >= 20) { - // Tried too many times to get permission config, circular reference very likely. - return; - } - ++tries; - return this.getLinkedPermissionConfig(linkConfig, tries); - } else { - return config as PermissionConfig[] | PermissionConfig; - } - } - - private getLinkedPermissionConfig(linkConfig: PermissionConfigLink, tries = 0) { - return this.getConfig(permissionConfigs[linkConfig.link]); - } - - private applyAdminCheck(check$: Observable, endpointGuid?: string) { - const adminCheck$ = this.checker.getAdminChecks(endpointGuid); - const readOnlyCheck$ = this.checker.getReadOnlyChecks(endpointGuid); - return combineLatest( - adminCheck$, - readOnlyCheck$ - ).pipe( - distinctUntilChanged(), - switchMap(([isAdmin, isReadOnly]) => { - if (isAdmin) { - return observableOf(true); - } - if (isReadOnly) { - return observableOf(false); - } - return check$; - }) - ); - } - - private combineChecks( - checkCombiners: ICheckCombiner[], - endpointGuid?: string - ) { - const reducedChecks = checkCombiners.map(combiner => this.checker.reduceChecks(combiner.checks, combiner.combineType)); - const check$ = combineLatest(reducedChecks).pipe( - map(checks => { - return checks.every(check => check); - }) - ); - return this.applyAdminCheck(check$, endpointGuid); - } -} diff --git a/src/frontend/packages/core/src/core/endpoints.service.spec.ts b/src/frontend/packages/core/src/core/endpoints.service.spec.ts index 89025b6c3e..006d10ccb7 100644 --- a/src/frontend/packages/core/src/core/endpoints.service.spec.ts +++ b/src/frontend/packages/core/src/core/endpoints.service.spec.ts @@ -1,7 +1,7 @@ import { inject, TestBed } from '@angular/core/testing'; import { CoreTestingModule } from '../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { PaginationMonitorFactory } from '../../../store/src/monitors/pagination-monitor.factory'; import { CoreModule } from './core.module'; import { EndpointsService } from './endpoints.service'; diff --git a/src/frontend/packages/core/src/core/log-out-dialog/log-out-dialog.component.spec.ts b/src/frontend/packages/core/src/core/log-out-dialog/log-out-dialog.component.spec.ts index caa7036c91..b497e9540e 100644 --- a/src/frontend/packages/core/src/core/log-out-dialog/log-out-dialog.component.spec.ts +++ b/src/frontend/packages/core/src/core/log-out-dialog/log-out-dialog.component.spec.ts @@ -1,10 +1,10 @@ -import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { Store } from '@ngrx/store'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { SharedModule } from '../../shared/shared.module'; import { CoreModule } from '../core.module'; import { LogOutDialogComponent } from './log-out-dialog.component'; diff --git a/src/frontend/packages/core/src/core/logger.service.spec.ts b/src/frontend/packages/core/src/core/logger.service.spec.ts index 86c39de468..e2f904e060 100644 --- a/src/frontend/packages/core/src/core/logger.service.spec.ts +++ b/src/frontend/packages/core/src/core/logger.service.spec.ts @@ -1,7 +1,7 @@ import { inject, TestBed } from '@angular/core/testing'; import { CoreTestingModule } from '../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { LoggerService } from './logger.service'; describe('LoggerService', () => { diff --git a/src/frontend/packages/core/src/core/permissions/current-user-permissions.config.ts b/src/frontend/packages/core/src/core/permissions/current-user-permissions.config.ts new file mode 100644 index 0000000000..a91481dd62 --- /dev/null +++ b/src/frontend/packages/core/src/core/permissions/current-user-permissions.config.ts @@ -0,0 +1,20 @@ +export type PermissionConfigType = PermissionConfig[] | PermissionConfig | PermissionConfigLink; +export interface IPermissionConfigs { + [permissionString: string]: PermissionConfigType; +} + +export type PermissionTypes = string; +export type CurrentUserPermissions = string; +export type ScopeStrings = string; +export type PermissionValues = string; +export class PermissionConfig { + constructor( + public type: PermissionTypes, + public permission: PermissionValues, + ) { } +} +export class PermissionConfigLink { + constructor( + public link: CurrentUserPermissions + ) { } +} diff --git a/src/frontend/packages/core/src/core/permissions/current-user-permissions.service.spec.ts b/src/frontend/packages/core/src/core/permissions/current-user-permissions.service.spec.ts new file mode 100644 index 0000000000..e8e69c7782 --- /dev/null +++ b/src/frontend/packages/core/src/core/permissions/current-user-permissions.service.spec.ts @@ -0,0 +1,511 @@ +import { TestBed } from '@angular/core/testing'; +import { createBasicStoreModule, createEntityStoreState, TestStoreEntity } from '@stratosui/store/testing'; +import { first, tap } from 'rxjs/operators'; + +import { AppState } from '../../../../store/src/app-state'; +import { EntityCatalogTestModule, TEST_CATALOGUE_ENTITIES } from '../../../../store/src/entity-catalog-test.module'; +import { EntityCatalogEntityConfig } from '../../../../store/src/entity-catalog/entity-catalog.types'; +import { EndpointModel } from '../../../../store/src/types/endpoint.types'; +import { BaseEntityValues } from '../../../../store/src/types/entity.types'; +import { PaginationState } from '../../../../store/src/types/pagination.types'; +import { AppTestModule } from '../../../test-framework/core-test.helper'; +import { endpointEntitySchema } from '../../base-entity-schemas'; +import { generateStratosEntities } from '../../base-entity-types'; +import { PermissionConfig } from './current-user-permissions.config'; +import { CurrentUserPermissionsService } from './current-user-permissions.service'; +import { StratosPermissionStrings, StratosPermissionTypes, StratosScopeStrings } from './stratos-user-permissions.checker'; + + +describe('CurrentUserPermissionsService', () => { + let service: CurrentUserPermissionsService; + + + function createStoreState(): Partial> { + // Data + const endpoints: EndpointModel[] = [ + { + guid: '0e934dc8-7ad4-40ff-b85c-53c1b61d2abb', + name: 'SCF', + cnsi_type: 'cf', + api_endpoint: { + Scheme: 'https', + Opaque: '', + User: null, + Host: 'api.10.84.93.10.nip.io:8443', + Path: '', + RawPath: '', + ForceQuery: false, + RawQuery: '', + Fragment: '' + }, + authorization_endpoint: 'https://cf.uaa.10.84.93.10.nip.io:2793', + token_endpoint: 'https://cf.uaa.10.84.93.10.nip.io:2793', + doppler_logging_endpoint: 'wss://doppler.10.84.93.10.nip.io:4443', + skip_ssl_validation: true, + user: { + guid: '670f4618-525e-4784-a56e-a238a0daf63d', + name: 'nathan', + admin: false, + scopes: [ + StratosScopeStrings.STRATOS_CHANGE_PASSWORD, + ] + }, + metricsAvailable: false, + connectionStatus: 'connected', + system_shared_token: false, + sso_allowed: false + }, + { + guid: 'c80420ca-204b-4879-bf69-b6b7a202ad87', + name: 'MainSCF', + cnsi_type: 'cf', + api_endpoint: { + Scheme: 'https', + Opaque: '', + User: null, + Host: 'api.10.84.93.55.nip.io:8443', + Path: '', + RawPath: '', + ForceQuery: false, + RawQuery: '', + Fragment: '' + }, + authorization_endpoint: 'https://cf.uaa.10.84.93.55.nip.io:2793', + token_endpoint: 'https://cf.uaa.10.84.93.55.nip.io:2793', + doppler_logging_endpoint: 'wss://doppler.10.84.93.55.nip.io:4443', + skip_ssl_validation: true, + user: { + guid: '4389dfd6-6048-4149-8b26-5aa6893ac21d', + name: 'admin', + admin: true, + scopes: [ + StratosScopeStrings.STRATOS_CHANGE_PASSWORD, + StratosScopeStrings.SCIM_READ + ] + }, + metricsAvailable: false, + connectionStatus: 'connected', + system_shared_token: false, + sso_allowed: false + } + ]; + + + // Pagination + const pagination: PaginationState = { + stratosEndpoint: { + 'endpoint-list': { + currentPage: 1, + totalResults: 2, + pageCount: 1, + ids: { + 1: endpoints.map(endpoint => endpoint.guid) + }, + pageRequests: { + 1: { + busy: false, + error: false, + message: '' + } + }, + params: { + 'results-per-page': 50, + 'order-direction': 'desc', + 'order-direction-field': 'name', + page: 1, + q: [] + }, + clientPagination: { + pageSize: 5, + currentPage: 1, + filter: { + string: '', + items: {} + }, + totalResults: 2 + }, + maxedState: {} + } + }, + }; + + // User roles + const initialState: Partial> = { + + }; + + + // Create request and requestData sections + const entityMap = new Map>([ + [ + endpointEntitySchema, + endpoints.map(endpoint => ({ + guid: endpoint.guid, + data: endpoint + })) + ], + ]); + const requestAndRequestData = createEntityStoreState(entityMap); + + return { + currentUserRoles: { + internal: { + isAdmin: false, + scopes: [ + StratosScopeStrings.STRATOS_CHANGE_PASSWORD, + StratosScopeStrings.SCIM_READ + ], + }, + endpoints: { + cf: { + '0e934dc8-7ad4-40ff-b85c-53c1b61d2abb': { + global: { + isAdmin: false, + isReadOnlyAdmin: false, + isGlobalAuditor: false, + canRead: true, + canWrite: true, + scopes: [ + 'cloud_controller.read', + 'password.write', + 'cloud_controller.write', + 'openid', + 'uaa.user' + ] + }, + spaces: { + '56eb5ecc-7c96-4bb1-bdcc-0c6c3d444dc6': { + orgId: 'abc', + isManager: true, + isAuditor: false, + isDeveloper: true + } + }, + organizations: { + 'd5e50b05-497f-4b3b-9658-a396a592a8ba': { + isManager: false, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + 'c58e7cfd-c765-400a-a473-313fa572d5c4': { + isManager: false, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + } + }, + state: { + initialised: true, + fetching: false, + error: null + } + }, + 'c80420ca-204b-4879-bf69-b6b7a202ad87': { + global: { + isAdmin: false, + isReadOnlyAdmin: false, + isGlobalAuditor: false, + canRead: true, + canWrite: true, + scopes: [ + 'openid', + 'scim.read', + 'cloud_controller.admin', + 'uaa.user', + 'routing.router_groups.read', + 'cloud_controller.read', + 'password.write', + 'cloud_controller.write', + 'doppler.firehose', + 'scim.write' + ] + }, + spaces: { + '56eb5ecc-7c96-4bb1-bdcc-0c6c3d444dc6': { + isManager: true, + isAuditor: false, + isDeveloper: true, + orgId: 'abc' + }, + 'c6450a21-aa1a-4643-9437-035cc818ea72': { + isManager: true, + isAuditor: false, + isDeveloper: true, + orgId: 'abc' + }, + '86577124-4b64-4ca1-9a78-d904c60505c4': { + isManager: true, + isAuditor: false, + isDeveloper: true, + orgId: 'abc' + } + }, + organizations: { + '367a49c1-b5dc-44e6-a8cf-84b1f56426a7': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + 'dccfedde-be2c-46a6-99cf-c1320ea8cb6d': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + '8a175cad-ff61-436b-8c6f-e5beb13edb5f': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + 'd5246255-867b-4f62-9040-346f113f0b7d': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + } + }, + state: { + initialised: true, + fetching: false, + error: null + } + }, + READ_ONLY_ADMIN: { + global: { + isAdmin: false, + isReadOnlyAdmin: true, + isGlobalAuditor: false, + canRead: true, + canWrite: true, + scopes: [ + 'openid', + 'scim.read', + 'cloud_controller.admin', + 'uaa.user', + 'routing.router_groups.read', + 'cloud_controller.read', + 'password.write', + 'cloud_controller.write', + 'doppler.firehose', + 'scim.write' + ] + }, + spaces: { + '56eb5ecc-7c96-4bb1-bdcc-0c6c3d444dc6': { + orgId: 'abc', + isManager: true, + isAuditor: false, + isDeveloper: true + }, + 'c6450a21-aa1a-4643-9437-035cc818ea72': { + orgId: 'abc', + isManager: true, + isAuditor: false, + isDeveloper: true + }, + '86577124-4b64-4ca1-9a78-d904c60505c4': { + orgId: 'abc', + isManager: true, + isAuditor: false, + isDeveloper: true + } + }, + organizations: { + '367a49c1-b5dc-44e6-a8cf-84b1f56426a7': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + 'dccfedde-be2c-46a6-99cf-c1320ea8cb6d': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + '8a175cad-ff61-436b-8c6f-e5beb13edb5f': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + 'd5246255-867b-4f62-9040-346f113f0b7d': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + } + }, + state: { + initialised: true, + fetching: false, + error: null + } + }, + READ_ONLY_USER: { + global: { + isAdmin: false, + isReadOnlyAdmin: false, + isGlobalAuditor: false, + canRead: true, + canWrite: false, + scopes: [ + 'openid', + 'scim.read', + 'cloud_controller.admin', + 'uaa.user', + 'routing.router_groups.read', + 'cloud_controller.read', + 'password.write', + 'cloud_controller.write', + 'doppler.firehose', + 'scim.write' + ] + }, + spaces: { + '56eb5ecc-7c96-4bb1-bdcc-0c6c3d444dc6': { + isManager: true, + isAuditor: false, + isDeveloper: true, + orgId: 'abc' + }, + 'c6450a21-aa1a-4643-9437-035cc818ea72': { + isManager: true, + isAuditor: false, + isDeveloper: true, + orgId: 'abc' + }, + '86577124-4b64-4ca1-9a78-d904c60505c4': { + isManager: true, + isAuditor: false, + isDeveloper: true, + orgId: 'abc' + } + }, + organizations: { + '367a49c1-b5dc-44e6-a8cf-84b1f56426a7': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + 'dccfedde-be2c-46a6-99cf-c1320ea8cb6d': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + '8a175cad-ff61-436b-8c6f-e5beb13edb5f': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + }, + 'd5246255-867b-4f62-9040-346f113f0b7d': { + isManager: true, + isAuditor: false, + isBillingManager: false, + isUser: true, + spaceGuids: [] + } + }, + state: { + initialised: true, + fetching: false, + error: null + } + } + }, + }, + state: { + initialised: true, + fetching: false, + error: null + } + }, + requestData: { + ...initialState.requestData, + ...requestAndRequestData.requestData + }, + pagination: { + ...initialState.pagination, + ...pagination + }, + }; + } + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + CurrentUserPermissionsService, + ], + imports: [ + { + ngModule: EntityCatalogTestModule, + providers: [ + { + provide: TEST_CATALOGUE_ENTITIES, useValue: [ + ...generateStratosEntities(), + ] + } + ] + }, + createBasicStoreModule(createStoreState()), + AppTestModule + ], + + }); + service = TestBed.get(CurrentUserPermissionsService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + + it('should allow if stratos admin', done => { + service.can(new PermissionConfig(StratosPermissionTypes.STRATOS, StratosPermissionStrings.STRATOS_ADMIN)).pipe( + tap(can => { + expect(can).toBe(false); + done(); + }), + first() + ).subscribe(); + }); + + it('should allow if has stratos change password scope', done => { + service.can(new PermissionConfig(StratosPermissionTypes.STRATOS_SCOPE, StratosScopeStrings.STRATOS_CHANGE_PASSWORD)).pipe( + tap(can => { + expect(can).toBe(true); + done(); + }), + first() + ).subscribe(); + + service.can([new PermissionConfig(StratosPermissionTypes.STRATOS_SCOPE, StratosScopeStrings.STRATOS_CHANGE_PASSWORD)]).pipe( + tap(can => { + expect(can).toBe(true); + done(); + }), + first() + ).subscribe(); + }); + + +}); diff --git a/src/frontend/packages/core/src/core/permissions/current-user-permissions.service.ts b/src/frontend/packages/core/src/core/permissions/current-user-permissions.service.ts new file mode 100644 index 0000000000..0a7490fb0c --- /dev/null +++ b/src/frontend/packages/core/src/core/permissions/current-user-permissions.service.ts @@ -0,0 +1,197 @@ +import { Inject, Injectable, Optional } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { combineLatest, Observable, of } from 'rxjs'; +import { distinctUntilChanged, map, switchMap } from 'rxjs/operators'; + +import { InternalAppState } from '../../../../store/src/app-state'; +import { entityCatalog } from '../../../../store/src/entity-catalog/entity-catalog'; +import { selectEntity } from '../../../../store/src/selectors/api.selectors'; +import { EndpointModel } from '../../../../store/src/types/endpoint.types'; +import { ENDPOINT_TYPE, STRATOS_ENDPOINT_TYPE } from '../../base-entity-schemas'; +import { LoggerService } from '../logger.service'; +import { + CurrentUserPermissions, + PermissionConfig, + PermissionConfigLink, + PermissionConfigType, +} from './current-user-permissions.config'; +import { + BaseCurrentUserPermissionsChecker, + ICurrentUserPermissionsChecker, + IPermissionCheckCombiner, +} from './current-user-permissions.types'; +import { StratosUserPermissionsChecker } from './stratos-user-permissions.checker'; + + +export const CUSTOM_USER_PERMISSION_CHECKERS = 'custom_user_perm_checkers' + +@Injectable() +export class CurrentUserPermissionsService { + private allCheckers: ICurrentUserPermissionsChecker[]; + constructor( + private store: Store, + @Optional() @Inject(CUSTOM_USER_PERMISSION_CHECKERS) customCheckers: ICurrentUserPermissionsChecker[], + private logger: LoggerService + ) { + // Cannot set default value for parameter as the Optional decorator sets it to null + const nullSafeCustomCheckers = customCheckers || []; + this.allCheckers = [ + new StratosUserPermissionsChecker(store), + ...nullSafeCustomCheckers + ] + } + /** + * @param action The action we're going to check the user's access to. + * @param endpointGuid If endpointGuid is provided without a orgOrSpaceGuid the checks will be done across all orgs and + * spaces within the cf. + * If no endpoint guid is provided we will do the check over all of the endpoint and all orgs/spaces. + * @param orgOrSpaceGuid If this is the only param then it will be used as the id to for all permission checks. + * @param spaceGuid If this is provided then the orgOrSpaceGuid will be used for org related permission checks and this will be + * used for space related permission checks. + */ + public can( + action: CurrentUserPermissions | PermissionConfigType, + endpointGuid?: string, + ...args: any[] + ): Observable { + let actionConfig; + if (typeof action === 'string') { + let permConfigType = this.getPermissionConfig(action); + if (!permConfigType) { + return of(false); // Logging handled in getPermissionConfig + } + actionConfig = this.getConfig(permConfigType); + } else { + actionConfig = this.getConfig(action) + } + const obs$ = this.getCanObservable(actionConfig, endpointGuid, ...args); + return obs$ ? + obs$.pipe(distinctUntilChanged()) : + of(false); + } + + private getCanObservable( + actionConfig: PermissionConfig[] | PermissionConfig, + endpointGuid: string, + ...args: any[]): Observable { + if (Array.isArray(actionConfig)) { + return this.getComplexPermission(actionConfig, endpointGuid, ...args); + } else if (actionConfig) { + return this.getSimplePermission(actionConfig, endpointGuid, ...args); + } else if (endpointGuid) { + const key = entityCatalog.getEntityKey(STRATOS_ENDPOINT_TYPE, ENDPOINT_TYPE); + return this.store.select(selectEntity(key, endpointGuid)).pipe( + switchMap(endpoint => endpoint ? + this.getFallbackPermission(endpointGuid, endpoint.cnsi_type) : + of(false) + ) + ); + } + return null; + } + + private getSimplePermission(actionConfig: PermissionConfig, endpointGuid: string, ...args: any[]): Observable { + return this.findChecker>( + (checker: ICurrentUserPermissionsChecker) => checker.getSimpleCheck(actionConfig, endpointGuid, ...args), + 'permissions check', + actionConfig.type, + of(false) + ) + } + + private getComplexPermission(permissionConfig: PermissionConfig[], endpointGuid?: string, ...args: any[]) { + const checks = this.getComplexChecks(permissionConfig, endpointGuid, ...args); + return this.combineChecks(checks); + } + + private getComplexChecks( + permissionConfig: PermissionConfig[], + endpointGuid: string, + ...args: any[] + ): IPermissionCheckCombiner[] { + return this.findChecker( + (checker: ICurrentUserPermissionsChecker) => checker.getComplexCheck(permissionConfig, endpointGuid, ...args), + 'complex permissions check', + 'a group', + [{ + checks: [of(false)] + }] + ) + } + + private getConfig(config: PermissionConfigType, tries = 0): PermissionConfig[] | PermissionConfig { + const linkConfig = config as PermissionConfigLink; + if (linkConfig.link) { + if (tries >= 20) { + // Tried too many times to get permission config, circular reference very likely. + return; + } + ++tries; + return this.getLinkedPermissionConfig(linkConfig, tries); + } else { + return config as PermissionConfig[] | PermissionConfig; + } + } + + private getLinkedPermissionConfig(linkConfig: PermissionConfigLink, tries = 0) { + return this.getConfig(this.getPermissionConfig(linkConfig.link), tries); + } + + private combineChecks( + checkCombiners: IPermissionCheckCombiner[], + ) { + const reducedChecks = checkCombiners.map(combiner => BaseCurrentUserPermissionsChecker.reduceChecks(combiner.checks, combiner.combineType)); + return combineLatest(reducedChecks).pipe( + map(checks => checks.every(check => check)) + ); + } + + private getFallbackPermission(endpointGuid: string, endpointType: string): Observable { + return this.findChecker>( + (checker: ICurrentUserPermissionsChecker) => checker.getFallbackCheck(endpointGuid, endpointType), + 'fallback permission', + 'N/A', + of(null) + ) + } + + private getPermissionConfig(key: CurrentUserPermissions): PermissionConfigType { + return this.findChecker( + (checker: ICurrentUserPermissionsChecker) => checker.getPermissionConfig(key), + 'permissions checker', + key, + null + ) + } + + /** + * Search through all known checkers for a single result + * If none are found log warning (hints at bug/misconfigure). + * If more than one is found log warning (hints re bug/misconfigure/devious plugin) + */ + private findChecker( + checkFn: (checker: ICurrentUserPermissionsChecker) => T, + checkNoun: string, + checkType: string, + failureValue: T + ): T { + const res: T[] = []; + for (let i = 0; i < this.allCheckers.length; i++) { + const checkerRes = checkFn(this.allCheckers[i]); + if (checkerRes) { + res.push(checkerRes); + } + } + if (res.length == 0) { + this.logger.warn(`Permissions: Failed to find a '${checkNoun}' for '${checkType}'. Permission Denied.`); + return failureValue; + } + if (res.length === 1) { + return res[0]; + } + if (res.length > 1) { + this.logger.warn(`Permissions: Found too many '${checkNoun}' for '${checkType}'. Permission Denied.`); + return failureValue; + } + } +} diff --git a/src/frontend/packages/core/src/core/permissions/current-user-permissions.types.ts b/src/frontend/packages/core/src/core/permissions/current-user-permissions.types.ts new file mode 100644 index 0000000000..1314130007 --- /dev/null +++ b/src/frontend/packages/core/src/core/permissions/current-user-permissions.types.ts @@ -0,0 +1,61 @@ +import { combineLatest, Observable, of } from 'rxjs'; +import { distinctUntilChanged, map } from 'rxjs/operators'; + +import { PermissionConfig, PermissionConfigType, PermissionTypes } from './current-user-permissions.config'; + +export interface IConfigGroups { + [permissionType: string]: IConfigGroup; +} + +export type IConfigGroup = PermissionConfig[]; + +export type IPermissionCheckCombineTypes = '||' | '&&'; + +export interface IPermissionCheckCombiner { + checks: Observable[]; + combineType?: IPermissionCheckCombineTypes; +} +export interface ICurrentUserPermissionsChecker { + /** + * For the given permission action find the checker configuration that will determine if the user can or cannot do the action + * If this is not supported by the the checker null is returned. If another checker also lays claim to the same string the check will + * always return denied + */ + getPermissionConfig: (action: string) => PermissionConfigType + /** + * Simple checks are used when the permission config contains a single thing to check + */ + getSimpleCheck: ( + permissionConfig: PermissionConfig, + endpointGuid?: string, + ...args: any[] + ) => Observable; + /** + * Used when the permission config contains multiple things to check + */ + getComplexCheck: ( + permissionConfig: PermissionConfig[], + permission: PermissionTypes, + ...args: any[] + ) => IPermissionCheckCombiner[]; + /** + * If no checker provides simple + */ + getFallbackCheck: ( + endpointGuid: string, + endpointType: string + ) => Observable; +} + +export abstract class BaseCurrentUserPermissionsChecker { + public static reduceChecks(checks: Observable[], type: IPermissionCheckCombineTypes = '||') { + const func = type === '||' ? 'some' : 'every'; + if (!checks || !checks.length) { + return of(true); + } + return combineLatest(checks).pipe( + map(flags => flags[func](flag => flag)), + distinctUntilChanged() + ); + } +} \ No newline at end of file diff --git a/src/frontend/packages/core/src/core/permissions/stratos-user-permissions.checker.ts b/src/frontend/packages/core/src/core/permissions/stratos-user-permissions.checker.ts new file mode 100644 index 0000000000..a0bcb58695 --- /dev/null +++ b/src/frontend/packages/core/src/core/permissions/stratos-user-permissions.checker.ts @@ -0,0 +1,131 @@ +import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; + +import { GeneralEntityAppState } from '../../../../store/src/app-state'; +import { + getCurrentUserStratosHasScope, + getCurrentUserStratosRole, +} from '../../../../store/src/selectors/current-user-role.selectors'; +import { IPermissionConfigs, PermissionConfig, PermissionTypes, PermissionValues } from './current-user-permissions.config'; +import { + BaseCurrentUserPermissionsChecker, + IConfigGroups, + ICurrentUserPermissionsChecker, + IPermissionCheckCombiner, +} from './current-user-permissions.types'; + + +export enum StratosCurrentUserPermissions { + ENDPOINT_REGISTER = 'register.endpoint', + PASSWORD_CHANGE = 'change-password', +} + +export enum StratosPermissionStrings { + _GLOBAL_ = 'global', + STRATOS_ADMIN = 'isAdmin' +} + + +export enum StratosScopeStrings { + STRATOS_CHANGE_PASSWORD = 'password.write', + SCIM_READ = 'scim.read' +} + +export enum StratosPermissionTypes { + STRATOS = 'internal', + STRATOS_SCOPE = 'internal-scope' +} + +// For each set permissions are checked by permission types of ENDPOINT, ENDPOINT_SCOPE, STRATOS_SCOPE, FEATURE_FLAG or a random bag. +// Every group result must be true in order for the permission to be true. A group result is true if all or some of it's permissions are +// true (see `getCheckFromConfig`). +export const stratosPermissionConfigs: IPermissionConfigs = { + [StratosCurrentUserPermissions.ENDPOINT_REGISTER]: new PermissionConfig(StratosPermissionTypes.STRATOS, StratosPermissionStrings.STRATOS_ADMIN), + [StratosCurrentUserPermissions.PASSWORD_CHANGE]: new PermissionConfig(StratosPermissionTypes.STRATOS_SCOPE, StratosScopeStrings.STRATOS_CHANGE_PASSWORD), +}; + +export class StratosUserPermissionsChecker extends BaseCurrentUserPermissionsChecker implements ICurrentUserPermissionsChecker { + constructor(private store: Store, ) { + super() + } + + getPermissionConfig(action: string) { + return stratosPermissionConfigs[action]; + } + + private check( + type: PermissionTypes, + permission: PermissionValues, + ) { + if (type === StratosPermissionTypes.STRATOS) { + return this.store.select(getCurrentUserStratosRole(permission)); + } + + if (type === StratosPermissionTypes.STRATOS_SCOPE) { + return this.store.select(getCurrentUserStratosHasScope(permission as StratosScopeStrings)); + } + } + /** + * @param permissionConfig Single permission to be checked + */ + public getSimpleCheck(permissionConfig: PermissionConfig): Observable { + switch (permissionConfig.type) { + case (StratosPermissionTypes.STRATOS): + return this.getInternalCheck(permissionConfig.permission as StratosPermissionStrings); + case (StratosPermissionTypes.STRATOS_SCOPE): + return this.getInternalScopesCheck(permissionConfig.permission as StratosScopeStrings); + } + } + + private getInternalCheck(permission: StratosPermissionStrings) { + return this.check(StratosPermissionTypes.STRATOS, permission); + } + + private getInternalScopesChecks( + configs: PermissionConfig[] + ) { + return configs.map(config => { + const { permission } = config; + return this.getInternalScopesCheck(permission as StratosScopeStrings); + }); + } + + private getInternalScopesCheck(permission: StratosScopeStrings) { + return this.check(StratosPermissionTypes.STRATOS_SCOPE, permission); + } + + public getComplexCheck( + permissionConfig: PermissionConfig[], + ...args: any[] + ): IPermissionCheckCombiner[] { + const groupedChecks = this.groupConfigs(permissionConfig); + const res = Object.keys(groupedChecks).map((permission: PermissionTypes) => { + const configGroup = groupedChecks[permission]; + switch (permission) { + case StratosPermissionTypes.STRATOS_SCOPE: + return { + checks: this.getInternalScopesChecks(configGroup), + }; + } + }) + // Checker must handle all configs + return res.every(check => !!check) ? res : null; + } + public getFallbackCheck(endpointGuid: string, endpointType: string): Observable { + return null; + }; + + private groupConfigs(configs: PermissionConfig[]): IConfigGroups { + return configs.reduce((grouped, config) => { + const type = config.type; + return { + ...grouped, + [type]: [ + ...(grouped[type] || []), + config + ] + }; + }, {}); + } + +} diff --git a/src/frontend/packages/core/src/core/user-favorite-helpers.ts b/src/frontend/packages/core/src/core/user-favorite-helpers.ts index 6be08fe176..7a73600412 100644 --- a/src/frontend/packages/core/src/core/user-favorite-helpers.ts +++ b/src/frontend/packages/core/src/core/user-favorite-helpers.ts @@ -10,14 +10,14 @@ export function isEndpointTypeFavorite(favorite: UserFavorite // Uses the endpoint definition to get the helper that can look up an entitty export function getFavoriteFromEntity( entity, - entityKey: string, + entityType: string, favoritesConfigMapper: FavoritesConfigMapper, - entityType: string + endpointType: string ): UserFavorite { // Use entity catalog to get favorite for the given endpoint type - const endpoint = entityCatalog.getEndpoint(entityType); + const endpoint = entityCatalog.getEndpoint(endpointType); if (endpoint && endpoint.definition && endpoint.definition.favoriteFromEntity) { - return endpoint.definition.favoriteFromEntity(entity, entityKey, favoritesConfigMapper); + return endpoint.definition.favoriteFromEntity(entity, entityType, favoritesConfigMapper); } return null; diff --git a/src/frontend/packages/core/src/core/user-profile.service.ts b/src/frontend/packages/core/src/core/user-profile.service.ts index 52e766a9b3..f83b16a256 100644 --- a/src/frontend/packages/core/src/core/user-profile.service.ts +++ b/src/frontend/packages/core/src/core/user-profile.service.ts @@ -16,6 +16,7 @@ import { EntityServiceFactory } from '../../../store/src/entity-service-factory. import { ActionState, getDefaultActionState, rootUpdatingKey } from '../../../store/src/reducers/api-request-reducer/types'; import { AuthState } from '../../../store/src/reducers/auth.reducer'; import { selectRequestInfo, selectUpdateInfo } from '../../../store/src/selectors/api.selectors'; +import { SessionData } from '../../../store/src/types/auth.types'; import { UserProfileInfo, UserProfileInfoEmail, UserProfileInfoUpdates } from '../../../store/src/types/user-profile.types'; import { userProfileEntitySchema } from '../base-entity-schemas'; @@ -69,6 +70,7 @@ export class UserProfileService { return this.store.select(s => s.auth).pipe( filter((auth: AuthState) => !!(auth && auth.sessionData)), map((auth: AuthState) => auth.sessionData), + filter((sessionData: SessionData) => !!sessionData.user), first(), map(data => new FetchUserProfileAction(data.user.guid)) ); diff --git a/src/frontend/packages/core/src/core/user.service.spec.ts b/src/frontend/packages/core/src/core/user.service.spec.ts index 33ba397021..6fdaab8b3a 100644 --- a/src/frontend/packages/core/src/core/user.service.spec.ts +++ b/src/frontend/packages/core/src/core/user.service.spec.ts @@ -1,7 +1,7 @@ import { inject, TestBed } from '@angular/core/testing'; import { CoreTestingModule } from '../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { SharedModule } from '../shared/shared.module'; import { CoreModule } from './core.module'; import { UserService } from './user.service'; diff --git a/src/frontend/packages/core/src/features/about/about-page/about-page.component.spec.ts b/src/frontend/packages/core/src/features/about/about-page/about-page.component.spec.ts index f2f29ab301..3aea3c6790 100644 --- a/src/frontend/packages/core/src/features/about/about-page/about-page.component.spec.ts +++ b/src/frontend/packages/core/src/features/about/about-page/about-page.component.spec.ts @@ -1,9 +1,9 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../core/core.module'; import { SharedModule } from '../../../shared/shared.module'; import { AboutPageComponent } from './about-page.component'; diff --git a/src/frontend/packages/core/src/features/about/diagnostics-page/diagnostics-page.component.spec.ts b/src/frontend/packages/core/src/features/about/diagnostics-page/diagnostics-page.component.spec.ts index 8ab9411e15..c65bac7fc7 100644 --- a/src/frontend/packages/core/src/features/about/diagnostics-page/diagnostics-page.component.spec.ts +++ b/src/frontend/packages/core/src/features/about/diagnostics-page/diagnostics-page.component.spec.ts @@ -1,9 +1,9 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../core/core.module'; import { SharedModule } from '../../../shared/shared.module'; import { DiagnosticsPageComponent } from './diagnostics-page.component'; diff --git a/src/frontend/packages/core/src/features/about/eula-page/eula-page.component.spec.ts b/src/frontend/packages/core/src/features/about/eula-page/eula-page.component.spec.ts index 36c9370e92..20023d114b 100644 --- a/src/frontend/packages/core/src/features/about/eula-page/eula-page.component.spec.ts +++ b/src/frontend/packages/core/src/features/about/eula-page/eula-page.component.spec.ts @@ -1,8 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { TabNavService } from '../../../../tab-nav.service'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../core/core.module'; import { SharedModule } from '../../../shared/shared.module'; import { EulaPageComponent, EulaPageContentComponent } from './eula-page.component'; diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts index 9aa632989f..c409c37976 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts @@ -7,8 +7,8 @@ import { Store } from '@ngrx/store'; import { combineLatest, Observable, of, Subscription } from 'rxjs'; import { distinctUntilChanged, filter, map, startWith, withLatestFrom } from 'rxjs/operators'; -import { GetCurrentUsersRelations } from '../../../../../cloud-foundry/src/actions/permissions.actions'; import { CloseSideNav, DisableMobileNav, EnableMobileNav } from '../../../../../store/src/actions/dashboard-actions'; +import { GetCurrentUsersRelations } from '../../../../../store/src/actions/permissions.actions'; import { GetUserFavoritesAction } from '../../../../../store/src/actions/user-favourites-actions/get-user-favorites-action'; import { DashboardOnlyAppState } from '../../../../../store/src/app-state'; import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; diff --git a/src/frontend/packages/core/src/features/dashboard/side-nav/side-nav.component.spec.ts b/src/frontend/packages/core/src/features/dashboard/side-nav/side-nav.component.spec.ts index 3099287848..99b1bc3fc1 100644 --- a/src/frontend/packages/core/src/features/dashboard/side-nav/side-nav.component.spec.ts +++ b/src/frontend/packages/core/src/features/dashboard/side-nav/side-nav.component.spec.ts @@ -1,8 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CustomizationService } from '../../../core/customizations.types'; import { MDAppModule } from '../../../core/md.module'; import { SideNavComponent } from './side-nav.component'; diff --git a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.spec.ts b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.spec.ts index 93034184a4..752e290c94 100644 --- a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.spec.ts +++ b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.spec.ts @@ -4,7 +4,7 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { createBasicStoreModule } from '@stratos/store/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; import { CoreModule } from '../../../core/core.module'; diff --git a/src/frontend/packages/core/src/features/endpoints/connect-endpoint/connect-endpoint.component.spec.ts b/src/frontend/packages/core/src/features/endpoints/connect-endpoint/connect-endpoint.component.spec.ts index d265f5b75b..589e58009c 100644 --- a/src/frontend/packages/core/src/features/endpoints/connect-endpoint/connect-endpoint.component.spec.ts +++ b/src/frontend/packages/core/src/features/endpoints/connect-endpoint/connect-endpoint.component.spec.ts @@ -1,6 +1,6 @@ import { CommonModule } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { createBasicStoreModule } from '@stratos/store/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; import { CoreModule } from '../../../core/core.module'; diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.spec.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.spec.ts index 8be52f97f8..c0108afa2d 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.spec.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.spec.ts @@ -2,10 +2,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { TabNavService } from '../../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../../core/core.module'; import { SharedModule } from '../../../../shared/shared.module'; import { CreateEndpointBaseStepComponent } from './create-endpoint-base-step.component'; diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-cf-step-1/create-endpoint-cf-step-1.component.spec.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-cf-step-1/create-endpoint-cf-step-1.component.spec.ts index 0d1c1e7a2c..ee8c879141 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-cf-step-1/create-endpoint-cf-step-1.component.spec.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-cf-step-1/create-endpoint-cf-step-1.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { ActivatedRoute } from '@angular/router'; -import { createBasicStoreModule } from '@stratos/store/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../../../../test-framework/core-test.modules'; import { CoreModule } from '../../../../core/core.module'; diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.spec.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.spec.ts index 02e8eb1026..4413a91ef7 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.spec.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { createBasicStoreModule } from '@stratos/store/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../../../../test-framework/core-test.modules'; import { CoreModule } from '../../../../core/core.module'; diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint.component.spec.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint.component.spec.ts index dda3a78ace..7479b4a0d1 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint.component.spec.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint.component.spec.ts @@ -3,7 +3,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; -import { createBasicStoreModule } from '@stratos/store/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; diff --git a/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.spec.ts b/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.spec.ts index da645164b8..2906a26a9d 100644 --- a/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.spec.ts +++ b/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.spec.ts @@ -3,15 +3,16 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; import { StoreModule } from '@ngrx/store'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { appReducers } from '../../../../../store/src/reducers.module'; import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../core/core.module'; +import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; import { SharedModule } from '../../../shared/shared.module'; -import { EndpointsPageComponent } from './endpoints-page.component'; import { SidePanelService } from './../../../shared/services/side-panel.service'; +import { EndpointsPageComponent } from './endpoints-page.component'; describe('EndpointsPageComponent', () => { let component: EndpointsPageComponent; @@ -33,7 +34,7 @@ describe('EndpointsPageComponent', () => { ), NoopAnimationsModule ], - providers: [TabNavService, SidePanelService] + providers: [TabNavService, SidePanelService, CurrentUserPermissionsService] }) .compileComponents(); })); diff --git a/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.ts b/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.ts index 969a5a5dd1..7992db8c39 100644 --- a/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.ts @@ -18,7 +18,6 @@ import { delay, first, map, tap } from 'rxjs/operators'; import { RouterNav } from '../../../../../store/src/actions/router.actions'; import { EndpointOnlyAppState } from '../../../../../store/src/app-state'; import { selectDashboardState } from '../../../../../store/src/selectors/dashboard.selectors'; -import { CurrentUserPermissions } from '../../../core/current-user-permissions.config'; import { CustomizationService, CustomizationsMetadata } from '../../../core/customizations.types'; import { EndpointsService } from '../../../core/endpoints.service'; import { @@ -26,6 +25,7 @@ import { StratosActionMetadata, StratosActionType, } from '../../../core/extension/extension-service'; +import { StratosCurrentUserPermissions } from '../../../core/permissions/stratos-user-permissions.checker'; import { safeUnsubscribe } from '../../../core/utils.service'; import { EndpointListHelper } from '../../../shared/components/list/list-types/endpoint/endpoint-list.helpers'; import { @@ -43,7 +43,7 @@ import { ListConfig } from '../../../shared/components/list/list.component.types }, EndpointListHelper] }) export class EndpointsPageComponent implements AfterViewInit, OnDestroy, OnInit { - public canRegisterEndpoint = CurrentUserPermissions.ENDPOINT_REGISTER; + public canRegisterEndpoint = StratosCurrentUserPermissions.ENDPOINT_REGISTER; private healthCheckTimeout: number; @ViewChild('customNoEndpoints', { read: ViewContainerRef, static: true }) customNoEndpointsContainer; diff --git a/src/frontend/packages/core/src/features/error-page/error-page/error-page.component.spec.ts b/src/frontend/packages/core/src/features/error-page/error-page/error-page.component.spec.ts index 5972631704..206f02d3d9 100644 --- a/src/frontend/packages/core/src/features/error-page/error-page/error-page.component.spec.ts +++ b/src/frontend/packages/core/src/features/error-page/error-page/error-page.component.spec.ts @@ -1,9 +1,9 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../core/core.module'; import { SharedModule } from '../../../shared/shared.module'; import { ErrorPageComponent } from './error-page.component'; diff --git a/src/frontend/packages/core/src/features/event-page/events-page/events-page.component.spec.ts b/src/frontend/packages/core/src/features/event-page/events-page/events-page.component.spec.ts index 1bce50ae14..883569abca 100644 --- a/src/frontend/packages/core/src/features/event-page/events-page/events-page.component.spec.ts +++ b/src/frontend/packages/core/src/features/event-page/events-page/events-page.component.spec.ts @@ -1,9 +1,9 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../core/core.module'; import { SharedModule } from '../../../shared/shared.module'; import { EventsPageComponent } from './events-page.component'; diff --git a/src/frontend/packages/core/src/features/home/home/home-page.component.spec.ts b/src/frontend/packages/core/src/features/home/home/home-page.component.spec.ts index 6da28f671d..a456ae097f 100644 --- a/src/frontend/packages/core/src/features/home/home/home-page.component.spec.ts +++ b/src/frontend/packages/core/src/features/home/home/home-page.component.spec.ts @@ -2,10 +2,10 @@ import { CommonModule } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../core/core.module'; import { SharedModule } from '../../../shared/shared.module'; import { HomePageComponent } from './home-page.component'; diff --git a/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.spec.ts b/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.spec.ts index f0097b1e8e..37500654bb 100644 --- a/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.spec.ts +++ b/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.spec.ts @@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { createBasicStoreModule } from '@stratos/store/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; diff --git a/src/frontend/packages/core/src/features/no-endpoints-non-admin/no-endpoints-non-admin.component.spec.ts b/src/frontend/packages/core/src/features/no-endpoints-non-admin/no-endpoints-non-admin.component.spec.ts index 2e8db8edab..fb688116a9 100644 --- a/src/frontend/packages/core/src/features/no-endpoints-non-admin/no-endpoints-non-admin.component.spec.ts +++ b/src/frontend/packages/core/src/features/no-endpoints-non-admin/no-endpoints-non-admin.component.spec.ts @@ -1,9 +1,9 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { TabNavService } from '../../../tab-nav.service'; import { CoreTestingModule } from '../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../core/core.module'; import { SharedModule } from '../../shared/shared.module'; import { NoEndpointsNonAdminComponent } from './no-endpoints-non-admin.component'; diff --git a/src/frontend/packages/core/src/features/setup/local-account-wizard/local-account-wizard.component.spec.ts b/src/frontend/packages/core/src/features/setup/local-account-wizard/local-account-wizard.component.spec.ts index c2eee31eeb..d76be87a52 100644 --- a/src/frontend/packages/core/src/features/setup/local-account-wizard/local-account-wizard.component.spec.ts +++ b/src/frontend/packages/core/src/features/setup/local-account-wizard/local-account-wizard.component.spec.ts @@ -2,7 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { createEmptyStoreModule } from '@stratos/store/testing'; +import { createEmptyStoreModule } from '@stratosui/store/testing'; import { TabNavService } from '../../../../tab-nav.service'; import { CoreModule } from '../../../core/core.module'; diff --git a/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.spec.ts b/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.spec.ts index 1b22dc0366..89281e1382 100644 --- a/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.spec.ts +++ b/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.spec.ts @@ -2,13 +2,14 @@ import { CommonModule } from '@angular/common'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../core/core.module'; -import { SharedModule } from '../../../shared/shared.module'; +import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; import { UserProfileService } from '../../../core/user-profile.service'; +import { SharedModule } from '../../../shared/shared.module'; import { EditProfileInfoComponent } from './edit-profile-info.component'; describe('EditProfileInfoComponent', () => { @@ -27,7 +28,7 @@ describe('EditProfileInfoComponent', () => { CoreTestingModule, createBasicStoreModule() ], - providers: [UserProfileService, TabNavService], + providers: [UserProfileService, TabNavService, CurrentUserPermissionsService], }) .compileComponents(); }); diff --git a/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.ts b/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.ts index 8f14434f3a..54194241ee 100644 --- a/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.ts +++ b/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.ts @@ -5,8 +5,8 @@ import { Subscription } from 'rxjs'; import { first, map, take, tap } from 'rxjs/operators'; import { UserProfileInfo, UserProfileInfoUpdates } from '../../../../../store/src/types/user-profile.types'; -import { CurrentUserPermissions } from '../../../core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../core/current-user-permissions.service'; +import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; +import { StratosCurrentUserPermissions } from '../../../core/permissions/stratos-user-permissions.checker'; import { UserProfileService } from '../../../core/user-profile.service'; import { StepOnNextFunction } from '../../../shared/components/stepper/step/step.component'; @@ -53,7 +53,7 @@ export class EditProfileInfoComponent implements OnInit, OnDestroy { // Only allow password change if user has the 'password.write' group - public canChangePassword = this.currentUserPermissionsService.can(CurrentUserPermissions.PASSWORD_CHANGE); + public canChangePassword = this.currentUserPermissionsService.can(StratosCurrentUserPermissions.PASSWORD_CHANGE); public passwordRequired = false; diff --git a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.spec.ts b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.spec.ts index 849f04870e..2c855307de 100644 --- a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.spec.ts +++ b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.spec.ts @@ -2,13 +2,13 @@ import { CommonModule } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../core/core.module'; -import { SharedModule } from '../../../shared/shared.module'; import { UserProfileService } from '../../../core/user-profile.service'; +import { SharedModule } from '../../../shared/shared.module'; import { ProfileInfoComponent } from './profile-info.component'; describe('ProfileInfoComponent', () => { diff --git a/src/frontend/packages/core/src/logged-in.service.spec.ts b/src/frontend/packages/core/src/logged-in.service.spec.ts index e910c7d7f3..9d4deef54d 100644 --- a/src/frontend/packages/core/src/logged-in.service.spec.ts +++ b/src/frontend/packages/core/src/logged-in.service.spec.ts @@ -1,8 +1,8 @@ import { inject, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from './core/core.module'; import { LoggedInService } from './logged-in.service'; diff --git a/src/frontend/packages/core/src/shared/components/code-block/code-block.component.spec.ts b/src/frontend/packages/core/src/shared/components/code-block/code-block.component.spec.ts index 470cba4925..63f1ac0b9d 100644 --- a/src/frontend/packages/core/src/shared/components/code-block/code-block.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/code-block/code-block.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../core/core.module'; import { CopyToClipboardComponent } from '../copy-to-clipboard/copy-to-clipboard.component'; import { CodeBlockComponent } from './code-block.component'; diff --git a/src/frontend/packages/core/src/shared/components/copy-to-clipboard/copy-to-clipboard.component.spec.ts b/src/frontend/packages/core/src/shared/components/copy-to-clipboard/copy-to-clipboard.component.spec.ts index 636bd2a970..952c401a0f 100644 --- a/src/frontend/packages/core/src/shared/components/copy-to-clipboard/copy-to-clipboard.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/copy-to-clipboard/copy-to-clipboard.component.spec.ts @@ -1,5 +1,5 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { createBasicStoreModule } from '@stratos/store/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; import { CoreModule } from '../../../core/core.module'; diff --git a/src/frontend/packages/core/src/shared/components/endpoints-missing/endpoints-missing.component.spec.ts b/src/frontend/packages/core/src/shared/components/endpoints-missing/endpoints-missing.component.spec.ts index c71c11e604..b5687f40e7 100644 --- a/src/frontend/packages/core/src/shared/components/endpoints-missing/endpoints-missing.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/endpoints-missing/endpoints-missing.component.spec.ts @@ -1,8 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../core/core.module'; import { SharedModule } from '../../shared.module'; import { EndpointsMissingComponent } from './endpoints-missing.component'; diff --git a/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component.spec.ts b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component.spec.ts index 93b850ca5e..ed7de6c1dd 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component.spec.ts @@ -2,7 +2,7 @@ import { Component, ViewChild } from '@angular/core'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations'; import { StoreModule } from '@ngrx/store'; -import { createBasicStoreModule } from '@stratos/store/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { Observable, of } from 'rxjs'; import { EntitySchema } from '../../../../../../../../store/src/helpers/entity-schema'; diff --git a/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component.ts b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component.ts index 868d8477d4..0aec42062f 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component.ts @@ -25,7 +25,7 @@ export function createMetaCardMenuItemSeparator() { return { label: '-', separator: true, - action: () => {} + action: () => { } }; } @@ -79,7 +79,7 @@ export class MetaCardComponent implements OnDestroy { first(), tap(entity => this.favorite = getFavoriteFromEntity( entity, - entityConfig.schema.key, + entityConfig.schema.entityType, this.favoritesConfigMapper, entityConfig.schema.endpointType )) diff --git a/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.scss b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.scss index 7ad3af708e..2912084f8b 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.scss +++ b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.scss @@ -58,3 +58,14 @@ } } } + +.meta-card-item-multiline { + align-items: flex-start; + display: flex; + justify-content: space-between; + min-height: 30px; + padding: 7px 0 2px; + .meta-card-item__value { + white-space: unset; + } +} diff --git a/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.ts b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.ts index 3a620e9a56..239b476a02 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-item/meta-card-item.component.ts @@ -18,6 +18,7 @@ export class MetaCardItemComponent implements OnInit { column: 'meta-card-item-column', 'long-text': 'meta-card-item-long-text', 'long-text-fixed': 'meta-card-item-long-text-fixed', + multiline: 'meta-card-item-multiline', }; itemStyle = 'meta-card-item-row'; @ContentChild(MetaCardKeyComponent, { static: true }) diff --git a/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-actions/table-cell-actions.component.spec.ts b/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-actions/table-cell-actions.component.spec.ts index 278e409532..a171fb8dcc 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-actions/table-cell-actions.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-actions/table-cell-actions.component.spec.ts @@ -1,9 +1,9 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { of as observableOf } from 'rxjs'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { CoreTestingModule } from '../../../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../../../core/core.module'; import { IListDataSource } from '../../data-sources-controllers/list-data-source-types'; import { ListConfig } from '../../list.component.types'; diff --git a/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-request-monitor-icon/table-cell-request-monitor-icon.component.ts b/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-request-monitor-icon/table-cell-request-monitor-icon.component.ts index 6bb2311dfb..762307187d 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-request-monitor-icon/table-cell-request-monitor-icon.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-request-monitor-icon/table-cell-request-monitor-icon.component.ts @@ -1,5 +1,5 @@ import { Component, Input, OnInit } from '@angular/core'; -import { getRowMetadata } from '@stratos/store'; +import { getRowMetadata } from '@stratosui/store'; import { EntitySchema } from '../../../../../../../store/src/helpers/entity-schema'; import { APIResource } from '../../../../../../../store/src/types/api.types'; diff --git a/src/frontend/packages/core/src/shared/components/list/list-table/table-cell/table-cell.component.ts b/src/frontend/packages/core/src/shared/components/list/list-table/table-cell/table-cell.component.ts index beb3046000..2bd2fd27d5 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-table/table-cell/table-cell.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-table/table-cell/table-cell.component.ts @@ -12,6 +12,9 @@ import { import { MultiActionListEntity } from '../../../../../../../store/src/monitors/pagination-monitor'; import { coreEndpointListDetailsComponents } from '../../../../../features/endpoints/endpoint-helpers'; import { IListDataSource } from '../../data-sources-controllers/list-data-source-types'; +import { + TableCellEndpointAddressComponent, +} from '../../list-types/endpoint/table-cell-endpoint-address/table-cell-endpoint-address.component'; import { TableCellEndpointDetailsComponent, } from '../../list-types/endpoint/table-cell-endpoint-details/table-cell-endpoint-details.component'; @@ -55,6 +58,7 @@ export const listTableCells: Type>[] = [ TableCellSidePanelComponent, TableCellIconComponent, TableCellExpanderComponent, + TableCellEndpointAddressComponent, ...coreEndpointListDetailsComponents ]; diff --git a/src/frontend/packages/core/src/shared/components/list/list-table/table.component.spec.ts b/src/frontend/packages/core/src/shared/components/list/list-table/table.component.spec.ts index 2b463228d6..875a84c668 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-table/table.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-table/table.component.spec.ts @@ -2,11 +2,11 @@ import { CdkTableModule } from '@angular/cdk/table'; import { Component, ViewChild } from '@angular/core'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { EMPTY, of as observableOf } from 'rxjs'; import { ListSort } from '../../../../../../store/src/actions/list.actions'; import { CoreTestingModule } from '../../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../../core/core.module'; import { UtilsService } from '../../../../core/utils.service'; import { SharedModule } from '../../../shared.module'; diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.html b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.html index af9d52a709..e5e6cb868e 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.html +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.html @@ -19,7 +19,8 @@ -->
- + @@ -43,10 +44,10 @@ - + Details
- + \ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.ts b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.ts index 22843439b7..581680cd4f 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.ts @@ -9,8 +9,7 @@ import { ViewContainerRef, } from '@angular/core'; import { Store } from '@ngrx/store'; -import { CurrentUserPermissions } from 'frontend/packages/core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from 'frontend/packages/core/src/core/current-user-permissions.service'; +import { CurrentUserPermissionsService } from 'frontend/packages/core/src/core/permissions/current-user-permissions.service'; import { AppState } from 'frontend/packages/store/src/app-state'; import { Observable, of, ReplaySubject, Subscription } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; @@ -64,7 +63,7 @@ export class EndpointCardComponent extends CardCell implements On @Input() component: EndpointListDetailsComponent; private endpointDetails: ViewContainerRef; - @ViewChild('endpointDetails', { read: ViewContainerRef }) set content(content: ViewContainerRef) { + @ViewChild('endpointDetails', { read: ViewContainerRef, static: true }) set content(content: ViewContainerRef) { this.endpointDetails = content; this.updateInnerComponent(); } @@ -107,15 +106,8 @@ export class EndpointCardComponent extends CardCell implements On can: endpointAction.createVisible(this.rowObs) })); - // Add edit - this.cardMenu.push({ - label: 'Edit endpoint', - action: () => this.editEndpoint(), - can: this.currentUserPermissionsService.can(CurrentUserPermissions.ENDPOINT_REGISTER) - }); - this.cardMenu.push(createMetaCardMenuItemSeparator()); - // Add a copy address to clipboard + this.cardMenu.push(createMetaCardMenuItemSeparator()); this.cardMenu.push({ label: 'Copy address to Clipboard', action: () => this.copyToClipboard.copyToClipboard(), diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-list.helpers.ts b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-list.helpers.ts index d785fb066b..b621bc2513 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-list.helpers.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-list.helpers.ts @@ -5,6 +5,7 @@ import { combineLatest, Observable } from 'rxjs'; import { map, pairwise } from 'rxjs/operators'; import { DisconnectEndpoint, UnregisterEndpoint } from '../../../../../../../store/src/actions/endpoint.actions'; +import { RouterNav } from '../../../../../../../store/src/actions/router.actions'; import { ShowSnackBar } from '../../../../../../../store/src/actions/snackBar.actions'; import { GetSystemInfo } from '../../../../../../../store/src/actions/system.actions'; import { AppState } from '../../../../../../../store/src/app-state'; @@ -14,9 +15,9 @@ import { endpointSchemaKey } from '../../../../../../../store/src/helpers/entity import { selectDeletionInfo, selectUpdateInfo } from '../../../../../../../store/src/selectors/api.selectors'; import { EndpointModel } from '../../../../../../../store/src/types/endpoint.types'; import { STRATOS_ENDPOINT_TYPE } from '../../../../../base-entity-schemas'; -import { CurrentUserPermissions } from '../../../../../core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../../../../../core/current-user-permissions.service'; import { LoggerService } from '../../../../../core/logger.service'; +import { CurrentUserPermissionsService } from '../../../../../core/permissions/current-user-permissions.service'; +import { StratosCurrentUserPermissions } from '../../../../../core/permissions/stratos-user-permissions.checker'; import { ConnectEndpointDialogComponent, } from '../../../../../features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component'; @@ -72,7 +73,7 @@ export class EndpointListHelper { label: 'Disconnect', description: ``, // Description depends on console user permission createVisible: (row$: Observable) => combineLatest( - this.currentUserPermissionsService.can(CurrentUserPermissions.ENDPOINT_REGISTER), + this.currentUserPermissionsService.can(StratosCurrentUserPermissions.ENDPOINT_REGISTER), row$ ).pipe( map(([isAdmin, row]) => { @@ -119,7 +120,16 @@ export class EndpointListHelper { }, label: 'Unregister', description: 'Remove the endpoint', - createVisible: () => this.currentUserPermissionsService.can(CurrentUserPermissions.ENDPOINT_REGISTER) + createVisible: () => this.currentUserPermissionsService.can(StratosCurrentUserPermissions.ENDPOINT_REGISTER) + }, + { + action: (item) => { + const routerLink = `/endpoints/edit/${item.guid}`; + this.store.dispatch(new RouterNav({ path: routerLink })); + }, + label: 'Edit endpoint', + description: 'Edit the endpoint', + createVisible: () => this.currentUserPermissionsService.can(StratosCurrentUserPermissions.ENDPOINT_REGISTER) } ]; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service.spec.ts b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service.spec.ts index 0024d16c78..25c6056137 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service.spec.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service.spec.ts @@ -1,9 +1,10 @@ import { CommonModule } from '@angular/common'; import { inject, TestBed } from '@angular/core/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../../../core/core.module'; +import { CurrentUserPermissionsService } from '../../../../../core/permissions/current-user-permissions.service'; import { SharedModule } from '../../../../shared.module'; import { EndpointListHelper } from './endpoint-list.helpers'; import { EndpointsListConfigService } from './endpoints-list-config.service'; @@ -11,7 +12,7 @@ import { EndpointsListConfigService } from './endpoints-list-config.service'; describe('EndpointsListConfigService', () => { beforeEach(() => { TestBed.configureTestingModule({ - providers: [EndpointsListConfigService, EndpointListHelper], + providers: [EndpointsListConfigService, EndpointListHelper, CurrentUserPermissionsService], imports: [ CommonModule, CoreModule, diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts index fd8d8c57fc..1848861dae 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts @@ -9,7 +9,6 @@ import { EntityMonitorFactory } from '../../../../../../../store/src/monitors/en import { InternalEventMonitorFactory } from '../../../../../../../store/src/monitors/internal-event-monitor.factory'; import { PaginationMonitorFactory } from '../../../../../../../store/src/monitors/pagination-monitor.factory'; import { EndpointModel } from '../../../../../../../store/src/types/endpoint.types'; -import { getFullEndpointApiUrl } from '../../../../../features/endpoints/endpoint-helpers'; import { FavoritesConfigMapper } from '../../../favorites-meta-card/favorite-config-mapper'; import { createTableColumnFavorite } from '../../list-table/table-cell-favorite/table-cell-favorite.component'; import { ITableColumn } from '../../list-table/table.types'; @@ -17,6 +16,7 @@ import { IListAction, IListConfig, ListViewTypes } from '../../list.component.ty import { EndpointCardComponent } from './endpoint-card/endpoint-card.component'; import { EndpointListHelper } from './endpoint-list.helpers'; import { EndpointsDataSource } from './endpoints-data-source'; +import { TableCellEndpointAddressComponent } from './table-cell-endpoint-address/table-cell-endpoint-address.component'; import { TableCellEndpointDetailsComponent } from './table-cell-endpoint-details/table-cell-endpoint-details.component'; import { TableCellEndpointNameComponent } from './table-cell-endpoint-name/table-cell-endpoint-name.component'; import { TableCellEndpointStatusComponent } from './table-cell-endpoint-status/table-cell-endpoint-status.component'; @@ -71,9 +71,7 @@ export class EndpointsListConfigService implements IListConfig { { columnId: 'address', headerCell: () => 'Address', - cellDefinition: { - getValue: getFullEndpointApiUrl - }, + cellComponent: TableCellEndpointAddressComponent, sort: { type: 'sort', orderKey: 'address', diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/table-cell-endpoint-address/table-cell-endpoint-address.component.html b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/table-cell-endpoint-address/table-cell-endpoint-address.component.html new file mode 100644 index 0000000000..f12d8357db --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/table-cell-endpoint-address/table-cell-endpoint-address.component.html @@ -0,0 +1,5 @@ +
+
{{ address }}
+ + +
diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/table-cell-endpoint-address/table-cell-endpoint-address.component.scss b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/table-cell-endpoint-address/table-cell-endpoint-address.component.scss new file mode 100644 index 0000000000..8f6f809311 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/table-cell-endpoint-address/table-cell-endpoint-address.component.scss @@ -0,0 +1,8 @@ +.endpoint-address-cell { + display: flex; + + &__copy { + width: 24px; + } + +} diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/table-cell-endpoint-address/table-cell-endpoint-address.component.spec.ts b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/table-cell-endpoint-address/table-cell-endpoint-address.component.spec.ts new file mode 100644 index 0000000000..1222435209 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/table-cell-endpoint-address/table-cell-endpoint-address.component.spec.ts @@ -0,0 +1,28 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BaseTestModules } from '../../../../../../../test-framework/core-test.helper'; +import { EndpointListHelper } from '../endpoint-list.helpers'; +import { TableCellEndpointAddressComponent } from './table-cell-endpoint-address.component'; + +describe('TableCellEndpointAddressComponent', () => { + let component: TableCellEndpointAddressComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [], + imports: [...BaseTestModules], + providers: [EndpointListHelper] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TableCellEndpointAddressComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/table-cell-endpoint-address/table-cell-endpoint-address.component.ts b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/table-cell-endpoint-address/table-cell-endpoint-address.component.ts new file mode 100644 index 0000000000..32956ffa4b --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/table-cell-endpoint-address/table-cell-endpoint-address.component.ts @@ -0,0 +1,33 @@ +import { Component, Input } from '@angular/core'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { GetAllEndpoints } from '../../../../../../../../store/src/actions/endpoint.actions'; +import { EntityServiceFactory } from '../../../../../../../../store/src/entity-service-factory.service'; +import { EndpointModel } from '../../../../../../../../store/src/types/endpoint.types'; +import { getFullEndpointApiUrl } from '../../../../../../features/endpoints/endpoint-helpers'; +import { TableCellCustom } from '../../../list.types'; +import { RowWithEndpointId } from '../table-cell-endpoint-name/table-cell-endpoint-name.component'; + +@Component({ + selector: 'app-table-cell-endpoint-address', + templateUrl: './table-cell-endpoint-address.component.html', + styleUrls: ['./table-cell-endpoint-address.component.scss'] +}) +export class TableCellEndpointAddressComponent extends TableCellCustom { + public endpointAddress$: Observable; + + constructor(private entityServiceFactory: EntityServiceFactory) { + super(); + } + + @Input('row') + set row(row: EndpointModel | RowWithEndpointId) { + /* tslint:disable-next-line:no-string-literal */ + const id = row['endpointId'] || row['guid']; + this.endpointAddress$ = this.entityServiceFactory.create(id, new GetAllEndpoints()).waitForEntity$.pipe( + map(data => data.entity), + map((data: any) => getFullEndpointApiUrl(data)) + ); + } +} diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.spec.ts b/src/frontend/packages/core/src/shared/components/list/list.component.spec.ts index 6a7ce0ac11..79d01782ed 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/list/list.component.spec.ts @@ -2,7 +2,7 @@ import { ChangeDetectorRef, NgZone } from '@angular/core'; import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { Store } from '@ngrx/store'; -import { createBasicStoreModule } from '@stratos/store/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { BehaviorSubject, of as observableOf } from 'rxjs'; import { switchMap } from 'rxjs/operators'; @@ -14,6 +14,7 @@ import { APIResource } from '../../../../../store/src/types/api.types'; import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; import { CoreModule } from '../../../core/core.module'; +import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; import { SharedModule } from '../../shared.module'; import { EndpointCardComponent } from './list-types/endpoint/endpoint-card/endpoint-card.component'; import { EndpointListHelper } from './list-types/endpoint/endpoint-list.helpers'; @@ -125,7 +126,8 @@ describe('ListComponent', () => { // ApplicationStateService, PaginationMonitorFactory, EntityMonitorFactory, - EndpointListHelper + EndpointListHelper, + CurrentUserPermissionsService ], imports: [ CoreModule, diff --git a/src/frontend/packages/core/src/shared/components/loading-page/loading-page.component.spec.ts b/src/frontend/packages/core/src/shared/components/loading-page/loading-page.component.spec.ts index a1dba5260d..8a6c026a8a 100644 --- a/src/frontend/packages/core/src/shared/components/loading-page/loading-page.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/loading-page/loading-page.component.spec.ts @@ -1,12 +1,12 @@ import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { of } from 'rxjs'; import { EntitySchema } from '../../../../../store/src/helpers/entity-schema'; +import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { MDAppModule } from '../../../core/md.module'; -import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; import { SharedModule } from '../../shared.module'; import { LoadingPageComponent } from './loading-page.component'; diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.spec.ts b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.spec.ts index c5f27babe0..6e783f38c2 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.spec.ts @@ -2,7 +2,7 @@ import { HttpClient, HttpClientModule, HttpHandler } from '@angular/common/http' import { HttpClientTestingModule } from '@angular/common/http/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { createBasicStoreModule } from '@stratos/store/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; import { LoggerService } from '../../../core/logger.service'; diff --git a/src/frontend/packages/core/src/shared/components/metrics-chart/metrics-chart.component.spec.ts b/src/frontend/packages/core/src/shared/components/metrics-chart/metrics-chart.component.spec.ts index 09af467030..6c913ac361 100644 --- a/src/frontend/packages/core/src/shared/components/metrics-chart/metrics-chart.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/metrics-chart/metrics-chart.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { createBasicStoreModule } from '@stratos/store/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; import { CoreModule } from '../../../core/core.module'; diff --git a/src/frontend/packages/core/src/shared/components/metrics-parent-range-selector/metrics-parent-range-selector.component.spec.ts b/src/frontend/packages/core/src/shared/components/metrics-parent-range-selector/metrics-parent-range-selector.component.spec.ts index 92d03659d4..e14b522546 100644 --- a/src/frontend/packages/core/src/shared/components/metrics-parent-range-selector/metrics-parent-range-selector.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/metrics-parent-range-selector/metrics-parent-range-selector.component.spec.ts @@ -1,10 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { createBasicStoreModule } from '@stratosui/store/testing'; +import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../core/core.module'; -import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; import { MetricsRangeSelectorService } from '../../services/metrics-range-selector.service'; import { DateTimeComponent } from '../date-time/date-time.component'; import { StartEndDateComponent } from '../start-end-date/start-end-date.component'; diff --git a/src/frontend/packages/core/src/shared/components/metrics-range-selector/metrics-range-selector.component.spec.ts b/src/frontend/packages/core/src/shared/components/metrics-range-selector/metrics-range-selector.component.spec.ts index df3e79c48c..64d5bd8610 100644 --- a/src/frontend/packages/core/src/shared/components/metrics-range-selector/metrics-range-selector.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/metrics-range-selector/metrics-range-selector.component.spec.ts @@ -1,10 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { createBasicStoreModule } from '@stratosui/store/testing'; +import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../core/core.module'; -import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; import { MetricsRangeSelectorService } from '../../services/metrics-range-selector.service'; import { DateTimeComponent } from '../date-time/date-time.component'; import { StartEndDateComponent } from '../start-end-date/start-end-date.component'; diff --git a/src/frontend/packages/core/src/shared/components/page-header/page-header.component.html b/src/frontend/packages/core/src/shared/components/page-header/page-header.component.html index 79b2f5296e..84b796dbea 100644 --- a/src/frontend/packages/core/src/shared/components/page-header/page-header.component.html +++ b/src/frontend/packages/core/src/shared/components/page-header/page-header.component.html @@ -37,7 +37,7 @@ |
diff --git a/src/frontend/packages/core/src/shared/components/recent-entities/recent-entities.component.html b/src/frontend/packages/core/src/shared/components/recent-entities/recent-entities.component.html index 61b636ae6a..4aa002471d 100644 --- a/src/frontend/packages/core/src/shared/components/recent-entities/recent-entities.component.html +++ b/src/frontend/packages/core/src/shared/components/recent-entities/recent-entities.component.html @@ -56,5 +56,5 @@ - + \ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/recent-entities/recent-entities.component.spec.ts b/src/frontend/packages/core/src/shared/components/recent-entities/recent-entities.component.spec.ts index 9002b55d23..4db2802dc4 100644 --- a/src/frontend/packages/core/src/shared/components/recent-entities/recent-entities.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/recent-entities/recent-entities.component.spec.ts @@ -1,9 +1,9 @@ import { CommonModule } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../core/core.module'; import { RecentEntitiesComponent } from './recent-entities.component'; diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts index 5c14146db8..e09366568c 100644 --- a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts @@ -2,7 +2,7 @@ import { HttpClient, HttpClientModule, HttpHandler } from '@angular/common/http' import { HttpClientTestingModule } from '@angular/common/http/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { createBasicStoreModule } from '@stratos/store/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; import { LoggerService } from '../../../core/logger.service'; diff --git a/src/frontend/packages/core/src/shared/components/stepper/steppers/steppers.component.spec.ts b/src/frontend/packages/core/src/shared/components/stepper/steppers/steppers.component.spec.ts index a0515d7e9e..91ab8b04a2 100644 --- a/src/frontend/packages/core/src/shared/components/stepper/steppers/steppers.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/stepper/steppers/steppers.component.spec.ts @@ -1,9 +1,9 @@ import { CommonModule } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../../../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; import { CoreModule } from '../../../../core/core.module'; import { MDAppModule } from '../../../../core/md.module'; import { SteppersComponent } from './steppers.component'; diff --git a/src/frontend/packages/core/src/shared/global-events.service.spec.ts b/src/frontend/packages/core/src/shared/global-events.service.spec.ts index 708fcc7104..7359c9a610 100644 --- a/src/frontend/packages/core/src/shared/global-events.service.spec.ts +++ b/src/frontend/packages/core/src/shared/global-events.service.spec.ts @@ -1,7 +1,7 @@ import { TestBed } from '@angular/core/testing'; import { CoreTestingModule } from '../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratos/store/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreModule } from '../core/core.module'; import { GlobalEventService } from './global-events.service'; import { SharedModule } from './shared.module'; diff --git a/src/frontend/packages/core/src/shared/shared.module.ts b/src/frontend/packages/core/src/shared/shared.module.ts index b218f0e621..d5ab5af42e 100644 --- a/src/frontend/packages/core/src/shared/shared.module.ts +++ b/src/frontend/packages/core/src/shared/shared.module.ts @@ -278,7 +278,6 @@ import { UserPermissionDirective } from './user-permission.directive'; UserPermissionDirective, BooleanIndicatorComponent, TableComponent, - UserPermissionDirective, CapitalizeFirstPipe, RoutingIndicatorComponent, DateTimeComponent, diff --git a/src/frontend/packages/core/src/shared/user-permission.directive.spec.ts b/src/frontend/packages/core/src/shared/user-permission.directive.spec.ts index 6e3ea57118..d1e89790c6 100644 --- a/src/frontend/packages/core/src/shared/user-permission.directive.spec.ts +++ b/src/frontend/packages/core/src/shared/user-permission.directive.spec.ts @@ -1,8 +1,7 @@ -import { Component, TemplateRef } from '@angular/core'; +import { Component } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseTestModules, generateBaseTestStoreModules } from '../../test-framework/core-test.helper'; -import { UserPermissionDirective } from './user-permission.directive'; +import { BaseTestModules } from '../../test-framework/core-test.helper'; @Component({ template: `` diff --git a/src/frontend/packages/core/src/shared/user-permission.directive.ts b/src/frontend/packages/core/src/shared/user-permission.directive.ts index 6cb7102f04..7b633408fb 100644 --- a/src/frontend/packages/core/src/shared/user-permission.directive.ts +++ b/src/frontend/packages/core/src/shared/user-permission.directive.ts @@ -1,60 +1,38 @@ import { Directive, Input, OnDestroy, OnInit, TemplateRef, ViewContainerRef } from '@angular/core'; -import { Store } from '@ngrx/store'; -import { Observable, of as observableOf, Subscription } from 'rxjs'; -import { switchMap } from 'rxjs/operators'; +import { Subscription } from 'rxjs'; -import { waitForCFPermissions } from '../../../cloud-foundry/src/features/cloud-foundry/cf.helpers'; -import { AppState } from '../../../store/src/app-state'; -import { CurrentUserPermissions } from '../core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from '../core/current-user-permissions.service'; +import { PermissionTypes } from '../core/permissions/current-user-permissions.config'; +import { CurrentUserPermissionsService } from '../core/permissions/current-user-permissions.service'; @Directive({ selector: '[appUserPermission]' }) export class UserPermissionDirective implements OnDestroy, OnInit { - @Input() - public appUserPermission: CurrentUserPermissions; + public appUserPermission: PermissionTypes; @Input() public appUserPermissionEndpointGuid: string; - @Input() - private appUserPermissionOrganizationGuid: string; - - @Input() - private appUserPermissionSpaceGuid: string; - private canSub: Subscription; constructor( - private store: Store, private templateRef: TemplateRef, private viewContainer: ViewContainerRef, private currentUserPermissionsService: CurrentUserPermissionsService, ) { } public ngOnInit() { - this.canSub = this.waitForEndpointPermissions(this.appUserPermissionEndpointGuid).pipe( - switchMap(() => this.currentUserPermissionsService.can( - this.appUserPermission, - this.appUserPermissionEndpointGuid, - this.getOrgOrSpaceGuid(), - this.getSpaceGuid() - )) - ).subscribe( - can => { - if (can) { - this.viewContainer.createEmbeddedView(this.templateRef); - } else { - this.viewContainer.clear(); - } + this.canSub = this.currentUserPermissionsService.can( + this.appUserPermission, + this.appUserPermissionEndpointGuid, + ).subscribe(can => { + if (can) { + this.viewContainer.createEmbeddedView(this.templateRef); + } else { + this.viewContainer.clear(); } - ); - } - - private waitForEndpointPermissions(endpointGuid: string): Observable { - return endpointGuid && endpointGuid.length > 0 ? waitForCFPermissions(this.store, endpointGuid) : observableOf(true); + }); } public ngOnDestroy() { @@ -63,19 +41,4 @@ export class UserPermissionDirective implements OnDestroy, OnInit { } } - private getOrgOrSpaceGuid() { - if (this.appUserPermissionSpaceGuid && !this.appUserPermissionOrganizationGuid) { - return this.appUserPermissionSpaceGuid; - } - return this.appUserPermissionOrganizationGuid; - } - - private getSpaceGuid() { - if (this.appUserPermissionOrganizationGuid) { - return this.appUserPermissionSpaceGuid; - } - return null; - } - - } diff --git a/src/frontend/packages/core/test-framework/core-test.helper.ts b/src/frontend/packages/core/test-framework/core-test.helper.ts index 99e36a22d7..bad6c14d0f 100644 --- a/src/frontend/packages/core/test-framework/core-test.helper.ts +++ b/src/frontend/packages/core/test-framework/core-test.helper.ts @@ -3,12 +3,13 @@ import { NgModule } from '@angular/core'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; import { StoreModule } from '@ngrx/store'; -import { createBasicStoreModule } from '@stratos/store/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { EntityCatalogHelper } from '../../store/src/entity-catalog/entity-catalog-entity/entity-catalog.service'; import { EntityCatalogHelpers } from '../../store/src/entity-catalog/entity-catalog.helper'; import { appReducers } from '../../store/src/reducers.module'; import { CoreModule } from '../src/core/core.module'; +import { CurrentUserPermissionsService } from '../src/core/permissions/current-user-permissions.service'; import { ApplicationStateIconComponent, } from '../src/shared/components/application-state/application-state-icon/application-state-icon.component'; @@ -35,7 +36,10 @@ import { CoreTestingModule } from './core-test.modules'; @NgModule({ - imports: [CoreModule] + imports: [CoreModule], + providers: [ + CurrentUserPermissionsService + ] }) export class AppTestModule { constructor( diff --git a/src/frontend/packages/store/src/actions/permissions.actions.ts b/src/frontend/packages/store/src/actions/permissions.actions.ts new file mode 100644 index 0000000000..0fd84d207b --- /dev/null +++ b/src/frontend/packages/store/src/actions/permissions.actions.ts @@ -0,0 +1,9 @@ +import { Action } from '@ngrx/store'; + +export const GET_CURRENT_USER_RELATIONS = '[Current User] Get relations'; +export const GET_CURRENT_USER_RELATIONS_SUCCESS = '[Current User] Get relations success'; +export const GET_CURRENT_USER_RELATIONS_FAILED = '[Current User] Get relations failed'; + +export class GetCurrentUsersRelations implements Action { + type = GET_CURRENT_USER_RELATIONS; +} \ No newline at end of file diff --git a/src/frontend/packages/store/src/effects/permissions.effect.ts b/src/frontend/packages/store/src/effects/permissions.effect.ts new file mode 100644 index 0000000000..bb890b00a7 --- /dev/null +++ b/src/frontend/packages/store/src/effects/permissions.effect.ts @@ -0,0 +1,73 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Action, Store } from '@ngrx/store'; +import { combineLatest, EMPTY, of } from 'rxjs'; +import { catchError, map, switchMap } from 'rxjs/operators'; + +import { LoggerService } from '../../../core/src/core/logger.service'; +import { CONNECT_ENDPOINTS_SUCCESS, EndpointActionComplete } from '../actions/endpoint.actions'; +import { + GET_CURRENT_USER_RELATIONS, + GET_CURRENT_USER_RELATIONS_FAILED, + GET_CURRENT_USER_RELATIONS_SUCCESS, + GetCurrentUsersRelations, +} from '../actions/permissions.actions'; +import { AppState } from '../app-state'; +import { entityCatalog } from '../entity-catalog/entity-catalog'; +import { EntityUserRolesEndpoint } from '../entity-request-pipeline/entity-request-pipeline.types'; + +const successAction: Action = { type: GET_CURRENT_USER_RELATIONS_SUCCESS }; +const failedAction: Action = { type: GET_CURRENT_USER_RELATIONS_FAILED }; + + +@Injectable() +export class PermissionsEffects { + constructor( + private httpClient: HttpClient, + private actions$: Actions, + private store: Store, + private logService: LoggerService + ) { } + + @Effect() getCurrentUsersPermissions$ = this.actions$.pipe( + ofType(GET_CURRENT_USER_RELATIONS), + switchMap(action => { + const allRequestsCompleted = entityCatalog.getAllBaseEndpointTypes().reduce((res, endpointType) => { + if (endpointType.definition.userRolesFetch) { + res.push(endpointType.definition.userRolesFetch([], this.store, this.logService, this.httpClient)); + } + return res; + }, []); + return combineLatest(allRequestsCompleted).pipe( + switchMap(succeeds => succeeds.every(succeeded => !!succeeded) ? [successAction] : [failedAction]) + ); + }), + catchError(err => { + this.logService.warn('Failed to fetch current user permissions: ', err); + return of(failedAction); + }) + ); + + + @Effect() getPermissionForNewlyConnectedEndpoint$ = this.actions$.pipe( + ofType(CONNECT_ENDPOINTS_SUCCESS), + switchMap(action => { + const endpointType = entityCatalog.getEndpoint(action.endpointType) + if (!endpointType.definition.userRolesFetch) { + return EMPTY; + } + const endpoint: EntityUserRolesEndpoint = { + guid: action.guid, + user: action.endpoint.user + } + return endpointType.definition.userRolesFetch([endpoint], this.store, this.logService, this.httpClient).pipe( + map(succeeded => succeeded ? successAction : failedAction) + ); + }), + catchError(err => { + this.logService.warn('Failed to fetch current user permissions after endpoint connected: ', err); + return of(failedAction); + }) + ); +} diff --git a/src/frontend/packages/store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity.ts b/src/frontend/packages/store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity.ts index d02ba1eadd..3fa2b47b0a 100644 --- a/src/frontend/packages/store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity.ts +++ b/src/frontend/packages/store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity.ts @@ -38,7 +38,7 @@ import { ActionBuilderConfigMapper } from './action-builder-config.mapper'; import { ActionDispatchers, EntityCatalogEntityStoreHelpers } from './entity-catalog-entity-store-helpers'; import { EntityCatalogEntityStore } from './entity-catalog-entity.types'; -export type KnownActionBuilders = Pick>>> +export type KnownActionBuilders = Pick>>>; export interface EntityCatalogBuilders< T extends IEntityMetadata = IEntityMetadata, @@ -301,7 +301,7 @@ export class StratosCatalogEntity< public getEntitiesEmitHandler(): EntitiesInfoHandler { return this.definition.entitiesEmitHandler || - this.definition.endpoint ? this.definition.endpoint.entitiesEmitHandler : null + this.definition.endpoint ? this.definition.endpoint.entitiesEmitHandler : null; } public getEntityFetchHandler(): EntityFetchHandler { diff --git a/src/frontend/packages/store/src/entity-catalog/entity-catalog.ts b/src/frontend/packages/store/src/entity-catalog/entity-catalog.ts index d10b67359f..c6371e3e17 100644 --- a/src/frontend/packages/store/src/entity-catalog/entity-catalog.ts +++ b/src/frontend/packages/store/src/entity-catalog/entity-catalog.ts @@ -1,6 +1,9 @@ +import { Action } from '@ngrx/store'; + import { STRATOS_ENDPOINT_TYPE } from '../../../core/src/base-entity-schemas'; import { IRequestEntityTypeState } from '../app-state'; import { ExtraApiReducers } from '../reducers/api-request-reducers.generator.helpers'; +import { ICurrentUserRolesState } from '../types/current-user-roles.types'; import { OrchestratedActionBuilders } from './action-orchestrator/action-orchestrator'; import { StratosBaseCatalogEntity, @@ -200,6 +203,29 @@ class EntityCatalog { return allEntityReducers; }, {} as ExtraApiReducers>); } + + public getAllCurrentUserReducers(state: ICurrentUserRolesState, action: Action): ICurrentUserRolesState { + const endpoints = this.getAllEndpointTypes(); + let oneChanged = false; + endpoints.forEach(endpoint => { + if (endpoint.definition.userRolesReducer) { + const endpointState = endpoint.definition.userRolesReducer(state.endpoints[endpoint.type], action); + oneChanged = oneChanged || !!endpointState; + if (!!endpointState) { + state = { + ...state, + endpoints: { + ...state.endpoints, + [endpoint.type]: endpointState + } + } + } + } + }) + return oneChanged ? { + ...state + } : state; + } } // Only to be used for tests diff --git a/src/frontend/packages/store/src/entity-catalog/entity-catalog.types.ts b/src/frontend/packages/store/src/entity-catalog/entity-catalog.types.ts index 130194253b..7205f8ff23 100644 --- a/src/frontend/packages/store/src/entity-catalog/entity-catalog.types.ts +++ b/src/frontend/packages/store/src/entity-catalog/entity-catalog.types.ts @@ -12,6 +12,8 @@ import { EntitiesInfoHandler, EntityFetchHandler, EntityInfoHandler, + EntityUserRolesFetch, + EntityUserRolesReducer, PreApiRequest, PrePaginationApiRequest, SuccessfulApiResponseDataMapper, @@ -93,8 +95,6 @@ export interface IStratosBaseEntityDefinition( entity: any, entityKey: string, favoritesConfigMapper: FavoritesConfigMapper ) => UserFavorite; + /** + * Allows the endpoint to fetch user roles, for example when the user loads Stratos or connects an endpoint of this type + */ + readonly userRolesFetch?: EntityUserRolesFetch + /** + * Allows the user roles to be stored, updated and removed in the current user permissions section of the store + */ + readonly userRolesReducer?: EntityUserRolesReducer } export interface StratosEndpointExtensionDefinition extends Omit { } diff --git a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/success-entity-request.handler.ts b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/success-entity-request.handler.ts index 9302a6a42c..7827aa9952 100644 --- a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/success-entity-request.handler.ts +++ b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/success-entity-request.handler.ts @@ -1,16 +1,20 @@ -import { CF_ENDPOINT_TYPE } from '../../../../cloud-foundry/src/cf-types'; +import { Action } from '@ngrx/store'; + import { ClearPaginationOfEntity, ClearPaginationOfType } from '../../actions/pagination.actions'; import { RecursiveDeleteComplete } from '../../effects/recursive-entity-delete.effect'; import { entityCatalog } from '../../entity-catalog/entity-catalog'; import { StratosBaseCatalogEntity } from '../../entity-catalog/entity-catalog-entity/entity-catalog-entity'; -import { WrapperRequestActionSuccess } from '../../types/request.types'; +import { ApiRequestTypes } from '../../reducers/api-request-reducer/request-helpers'; +import { EntityRequestAction, WrapperRequestActionSuccess } from '../../types/request.types'; +import { PipelineResult } from '../entity-request-pipeline.types'; + export function successEntityHandler( - actionDispatcher, + actionDispatcher: (actionToDispatch: Action) => void, catalogEntity: StratosBaseCatalogEntity, - requestType, - action, - result, + requestType: ApiRequestTypes, + action: EntityRequestAction, + result: PipelineResult, recursivelyDeleting = false ) { const entityAction = catalogEntity.getRequestAction('success', action, requestType, result.response); @@ -29,7 +33,7 @@ export function successEntityHandler( if (Array.isArray(action.clearPaginationEntityKeys)) { // If clearPaginationEntityKeys is an array then clear the pagination sections regardless of removeEntityOnDelete action.clearPaginationEntityKeys.forEach(key => { - const entityConfig = entityCatalog.getEntity(CF_ENDPOINT_TYPE, key); + const entityConfig = entityCatalog.getEntity(action.endpointType, key); actionDispatcher(new ClearPaginationOfType(entityConfig.getSchema())); }); } diff --git a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-pipeline.types.ts b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-pipeline.types.ts index bc71e61254..46e1d685cd 100644 --- a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-pipeline.types.ts +++ b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-pipeline.types.ts @@ -1,7 +1,8 @@ -import { HttpRequest } from '@angular/common/http'; +import { HttpClient, HttpRequest } from '@angular/common/http'; import { Action, Store } from '@ngrx/store'; import { Observable } from 'rxjs'; +import { LoggerService } from '../../../core/src/core/logger.service'; import { JetStreamErrorResponse } from '../../../core/src/jetstream.helpers'; import { AppState, GeneralEntityAppState, InternalAppState } from '../app-state'; import { @@ -10,6 +11,7 @@ import { } from '../entity-catalog/entity-catalog-entity/entity-catalog-entity'; import { ApiRequestTypes } from '../reducers/api-request-reducer/request-helpers'; import { EntityInfo, NormalizedResponse } from '../types/api.types'; +import { EndpointUser } from '../types/endpoint.types'; import { PaginatedAction, PaginationEntityState } from '../types/pagination.types'; import { EntityRequestAction } from '../types/request.types'; import { JetstreamError } from './entity-request-base-handlers/handle-multi-endpoints.pipe'; @@ -135,3 +137,17 @@ export type EntitiesInfoHandler = ( export type EntityFetch = (entity: T) => void; export type EntityFetchHandler = (store: Store, action: EntityRequestAction) => EntityFetch; export type EntitiesFetchHandler = (store: Store, actions: PaginatedAction[]) => () => void; + +export interface EntityUserRolesEndpoint { + user?: EndpointUser; + guid?: string; +} + +export type EntityUserRolesFetch = ( + endpoints: string[] | EntityUserRolesEndpoint[], + store: Store, + logService: LoggerService, + httpClient: HttpClient +) => Observable; + +export type EntityUserRolesReducer = (state: T, action: Action) => T; \ No newline at end of file diff --git a/src/frontend/packages/store/src/entity-service.ts b/src/frontend/packages/store/src/entity-service.ts index 5d583342dd..38f2f1c911 100644 --- a/src/frontend/packages/store/src/entity-service.ts +++ b/src/frontend/packages/store/src/entity-service.ts @@ -94,6 +94,9 @@ export class EntityService { this.waitForEntity$ = this.entityObs$.pipe( filter((ent) => { const { entityRequestInfo, entity } = ent; + // Note - isEntityAvailable does not block on updating, decision taken to ensure we show entity as soon as possible. + // This means, in the cf world, entities will be emitted here that are still in the validation process and as such may be missing + // required relations return this.isEntityAvailable(entity, entityRequestInfo); }), publishReplay(1), diff --git a/src/frontend/packages/store/src/monitors/entity-monitor.factory.service.ts b/src/frontend/packages/store/src/monitors/entity-monitor.factory.service.ts index c7a6dadd26..697b731300 100644 --- a/src/frontend/packages/store/src/monitors/entity-monitor.factory.service.ts +++ b/src/frontend/packages/store/src/monitors/entity-monitor.factory.service.ts @@ -22,8 +22,8 @@ export class EntityMonitorFactory { entityConfig: EntityCatalogEntityConfig, startWithNull = true ): EntityMonitor { - const { endpointType, entityType } = entityConfig; - const cacheKey = id + endpointType + entityType; + const { endpointType, entityType, schemaKey, subType } = entityConfig; + const cacheKey = id + endpointType + entityType + schemaKey + subType; if (this.monitorCache[cacheKey]) { return this.monitorCache[cacheKey]; } else { diff --git a/src/frontend/packages/store/src/reducers/api-request-reducer/types.ts b/src/frontend/packages/store/src/reducers/api-request-reducer/types.ts index 65952a2884..8b514a0c4a 100644 --- a/src/frontend/packages/store/src/reducers/api-request-reducer/types.ts +++ b/src/frontend/packages/store/src/reducers/api-request-reducer/types.ts @@ -45,7 +45,7 @@ export const defaultDeletingActionState = { }; export interface UpdatingSection { - _root_: ActionState; + [rootUpdatingKey]: ActionState; [key: string]: ActionState; } export interface RequestInfoState { @@ -61,7 +61,7 @@ export interface RequestInfoState { const defaultRequestState = { fetching: false, updating: { - _root_: getDefaultActionState() + [rootUpdatingKey]: getDefaultActionState() }, creating: false, error: false, diff --git a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-request-state.reducers.ts b/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-request-state.reducers.ts deleted file mode 100644 index 1deb05551d..0000000000 --- a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-request-state.reducers.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { - GET_CURRENT_USER_CF_RELATIONS, - GET_CURRENT_USER_CF_RELATIONS_FAILED, - GET_CURRENT_USER_CF_RELATIONS_SUCCESS, - GET_CURRENT_USER_RELATIONS, - GET_CURRENT_USER_RELATIONS_FAILED, - GET_CURRENT_USER_RELATIONS_SUCCESS, - GetUserCfRelations, -} from '../../../../cloud-foundry/src/actions/permissions.actions'; -import { IAllCfRolesState } from '../../../../cloud-foundry/src/store/types/cf-current-user-roles.types'; -import { getDefaultRolesRequestState, RolesRequestState } from '../../types/current-user-roles.types'; - -export function currentUserRolesRequestStateReducer(state: RolesRequestState = getDefaultRolesRequestState(), type: string) { - switch (type) { - case GET_CURRENT_USER_RELATIONS: - case GET_CURRENT_USER_CF_RELATIONS: - return { - ...state, - fetching: true - }; - case GET_CURRENT_USER_RELATIONS_SUCCESS: - case GET_CURRENT_USER_CF_RELATIONS_SUCCESS: - return { - ...state, - initialised: true, - fetching: false - }; - case GET_CURRENT_USER_RELATIONS_FAILED: - case GET_CURRENT_USER_CF_RELATIONS_FAILED: - return { - ...state, - fetching: false, - error: true - }; - } -} - -export function currentUserCfRolesRequestStateReducer(cf: IAllCfRolesState = {}, action: GetUserCfRelations) { - const cfGuid = (action as GetUserCfRelations).cfGuid; - return { - ...cf, - [cfGuid]: { - ...cf[cfGuid], - state: { - ...cf[cfGuid].state, - ...currentUserRolesRequestStateReducer(cf[cfGuid].state, action.type) - } - } - }; -} diff --git a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-role-session.reducer.ts b/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-role-session.reducer.ts deleted file mode 100644 index 8e41424bf8..0000000000 --- a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-role-session.reducer.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { - IAllCfRolesState, - ICfRolesState, - IGlobalRolesState, -} from '../../../../cloud-foundry/src/store/types/cf-current-user-roles.types'; -import { ScopeStrings } from '../../../../core/src/core/current-user-permissions.config'; -import { VerifiedSession } from '../../actions/auth.actions'; -import { EndpointActionComplete } from '../../actions/endpoint.actions'; -import { SessionUser } from '../../types/auth.types'; -import { getDefaultEndpointRoles, ICurrentUserRolesState } from '../../types/current-user-roles.types'; -import { EndpointUser, INewlyConnectedEndpointInfo } from '../../types/endpoint.types'; - -interface PartialEndpoint { - user: EndpointUser | SessionUser; - guid: string; -} - -export function roleInfoFromSessionReducer( - state: ICurrentUserRolesState, - action: VerifiedSession -): ICurrentUserRolesState { - const { user, endpoints } = action.sessionData; - const cfRoles = propagateEndpointsAdminPermissions(state.cf, Object.values(endpoints.cf)); - return applyInternalScopes(state, cfRoles, user); -} - -export function updateNewlyConnectedEndpoint( - state: ICurrentUserRolesState, - action: EndpointActionComplete -): ICurrentUserRolesState { - if (action.endpointType !== 'cf') { - return state; - } - const endpoint = action.endpoint as INewlyConnectedEndpointInfo; - const cfRoles = propagateEndpointsAdminPermissions(state.cf, [{ - user: endpoint.user, - guid: action.guid - }]); - return { - ...state, - cf: cfRoles - }; -} - -function applyInternalScopes(state: ICurrentUserRolesState, cfRoles: IAllCfRolesState, user?: SessionUser | EndpointUser) { - const internalRoles = { ...state.internal }; - if (user) { - internalRoles.scopes = user.scopes || []; - // The admin scope is configurable - so look at the flag provided by the backend - internalRoles.isAdmin = user.admin; - } - - return { - ...state, - cf: cfRoles, - internal: internalRoles - }; -} - -function propagateEndpointsAdminPermissions( - cfState: IAllCfRolesState, - endpoints: PartialEndpoint[] -): IAllCfRolesState { - return Object.values(endpoints).reduce((state, endpoint) => { - return { - ...state, - [endpoint.guid]: propagateEndpointAdminPermissions(state[endpoint.guid], endpoint) - }; - }, { ...cfState }); -} - -function propagateEndpointAdminPermissions(state: ICfRolesState = getDefaultEndpointRoles(), endpoint: PartialEndpoint) { - const scopes = endpoint.user ? endpoint.user.scopes : []; - const global = getEndpointRoles(scopes, state.global); - return { - ...state, - global - }; -} - -function getEndpointRoles(scopes: string[], globalEndpointState: IGlobalRolesState) { - const newEndpointState = { - ...globalEndpointState, - scopes - }; - return scopes.reduce((roles, scope) => { - if (scope === ScopeStrings.CF_ADMIN_GROUP) { - roles.isAdmin = true; - } - if (scope === ScopeStrings.CF_READ_ONLY_ADMIN_GROUP) { - roles.isReadOnlyAdmin = true; - } - if (scope === ScopeStrings.CF_ADMIN_GLOBAL_AUDITOR_GROUP) { - roles.isGlobalAuditor = true; - } - if (scope === ScopeStrings.CF_READ_SCOPE) { - roles.canRead = true; - } - if (scope === ScopeStrings.CF_WRITE_SCOPE) { - roles.canWrite = true; - } - return roles; - }, newEndpointState); -} - diff --git a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-roles-clear.reducers.ts b/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-roles-clear.reducers.ts deleted file mode 100644 index feb787a9fd..0000000000 --- a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-roles-clear.reducers.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { EndpointActionComplete } from '../../actions/endpoint.actions'; -import { getDefaultEndpointRoles, ICurrentUserRolesState } from '../../types/current-user-roles.types'; -import { EndpointModel } from '../../types/endpoint.types'; -import { APISuccessOrFailedAction } from '../../types/request.types'; - -export function removeEndpointRoles(state: ICurrentUserRolesState, action: EndpointActionComplete) { - const cfState = { - ...state.cf - }; - if (action.endpointType !== 'cf' || !cfState[action.guid]) { - return state; - } - delete cfState[action.guid]; - return { - ...state, - cf: cfState - }; -} - -export function addEndpoint(state: ICurrentUserRolesState, action: EndpointActionComplete) { - const endpoint = action.endpoint as EndpointModel; - const guid = endpoint.guid; - if (action.endpointType !== 'cf' || state[guid]) { - return state; - } - const cfState = { - ...state.cf - }; - - cfState[guid] = getDefaultEndpointRoles(); - return { - ...state, - cf: cfState - }; -} - -export function removeSpaceRoles(state: ICurrentUserRolesState, action: APISuccessOrFailedAction) { - const { endpointGuid, guid } = action.apiAction; - const removedOrgOrSpaceState = removeOrgOrSpaceRoles(state, endpointGuid as string, guid, 'spaces'); - return removeSpaceIdFromOrg(state, endpointGuid as string, guid); -} - -function removeSpaceIdFromOrg(state: ICurrentUserRolesState, endpointGuid: string, spaceGuid: string) { - const space = state.cf[endpointGuid].spaces[spaceGuid]; - if (!space) { - return state; - } - const { orgId } = space; - return { - ...state, - cf: { - ...state.cf, - [endpointGuid]: { - ...state.cf[endpointGuid], - organizations: { - ...state.cf[endpointGuid].organizations, - [orgId]: { - ...state.cf[endpointGuid].organizations[orgId], - spaceGuids: state.cf[endpointGuid].organizations[orgId].spaceGuids.filter(id => id !== spaceGuid) - } - } - } - } - }; -} - -export function removeOrgRoles(state: ICurrentUserRolesState, action: APISuccessOrFailedAction) { - const { endpointGuid, guid } = action.apiAction; - if (!state.cf[endpointGuid as string].organizations[guid]) { - return state; - } - // const spaceIds = state.cf[endpointGuid].organizations[guid].spaceIds; - const spaceIds = []; - const newState = removeOrgOrSpaceRoles(state, endpointGuid, guid, 'organizations'); - return cleanUpOrgSpaces(newState, spaceIds, endpointGuid); -} - -function removeOrgOrSpaceRoles( - state: ICurrentUserRolesState, - endpointGuid: string, - orgOrSpaceId: string, - type: 'organizations' | 'spaces' -) { - if (!state.cf[endpointGuid][type][orgOrSpaceId]) { - return state; - } - // Remove orgOrSpaceId - const { - [orgOrSpaceId]: omit, - ...newTypeState - } = state.cf[endpointGuid][type]; - - const newState = { - ...state, - cf: { - ...state.cf, - [endpointGuid]: { - ...state.cf[endpointGuid], - [type]: newTypeState - } - } - }; - return newState; -} - -function cleanUpOrgSpaces(state: ICurrentUserRolesState, spaceIds: string[], endpointGuid: string) { - if (!spaceIds || spaceIds.length === 0 || !state.cf[endpointGuid]) { - return state; - } - return spaceIds.reduce((newState, spaceId) => { - if (newState.cf[endpointGuid].spaces[spaceId]) { - delete newState.cf[endpointGuid].spaces[spaceId]; - } - return newState; - }, { ...state }); -} - - - diff --git a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-roles.reducer.spec.ts b/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-roles.reducer.spec.ts index 57018352db..083fe4629f 100644 --- a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-roles.reducer.spec.ts +++ b/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-roles.reducer.spec.ts @@ -1,74 +1,7 @@ -import { - GetCurrentUserRelationsComplete, - UserRelationTypes, -} from '../../../../cloud-foundry/src/actions/permissions.actions'; -import { - createOrgRoleStateState, -} from '../../../../cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-roles-org.reducer'; -import { - ICfRolesState, - IOrgRoleState, - ISpaceRoleState, - RoleEntities, -} from '../../../../cloud-foundry/src/store/types/cf-current-user-roles.types'; -import { getDefaultEndpointRoles, ICurrentUserRolesState, getDefaultRolesRequestState } from '../../types/current-user-roles.types'; +import { getDefaultRolesRequestState, ICurrentUserRolesState } from '../../types/current-user-roles.types'; import { currentUserRolesReducer } from './current-user-roles.reducer'; -const testOrgGuid = 'org-1'; -const testSpaceGuid = 'space-1'; -const generalGuid = 'guid-123'; -const testCFEndpointGuid = 'cf-1'; - -function getSpaceAction(type: UserRelationTypes, orgGuid: string = testOrgGuid, spaceGuid: string = testSpaceGuid) { - return new GetCurrentUserRelationsComplete( - type, - testCFEndpointGuid, - [{ metadata: { guid: spaceGuid, created_at: '1', updated_at: '1', url: '1' }, entity: { organization_guid: orgGuid } }] - ); -} - -function getOrgAction(type: UserRelationTypes, orgGuid: string = testOrgGuid) { - return new GetCurrentUserRelationsComplete( - type, - testCFEndpointGuid, - [{ metadata: { guid: orgGuid, created_at: '1', updated_at: '1', url: '1' }, entity: {} }] - ); -} - -function getState( - orgOrSpace: RoleEntities, - allRoles: { guid: string, roles: ISpaceRoleState | IOrgRoleState }[] = [], - roles?: ISpaceRoleState | IOrgRoleState -): ICfRolesState { - const baseState = getDefaultEndpointRoles(); - if (!allRoles.length) { - let guid = testSpaceGuid; - if (orgOrSpace === RoleEntities.ORGS) { - guid = testOrgGuid; - } - allRoles.push({ guid, roles }); - } - const orgSpaceRoles = { - [orgOrSpace]: {} - }; - if (orgOrSpace === RoleEntities.SPACES) { - orgSpaceRoles.organizations = { - [testOrgGuid]: createOrgRoleStateState() - }; - } - allRoles.forEach(role => { - orgSpaceRoles[orgOrSpace][role.guid] = role.roles; - if (orgOrSpace === RoleEntities.SPACES) { - orgSpaceRoles.organizations[testOrgGuid].spaceGuids.push(role.guid); - } - }); - return { - ...baseState, - ...orgSpaceRoles - }; -} - -describe('currentUserReducer', () => { +describe('currentUserRolesReducer', () => { it('set defaults', () => { const state = currentUserRolesReducer(undefined, { type: 'FAKE_ACTION' }); const expectedState: ICurrentUserRolesState = { @@ -76,185 +9,9 @@ describe('currentUserReducer', () => { isAdmin: false, scopes: [] }, - cf: {}, + endpoints: {}, state: getDefaultRolesRequestState() }; expect(state).toEqual(expectedState); }); - it('should add org manager role to org', () => { - const state = currentUserRolesReducer(undefined, getOrgAction(UserRelationTypes.MANAGED_ORGANIZATION)); - const cfPermissions = state.cf[testCFEndpointGuid]; - expect(cfPermissions).toEqual(getState(RoleEntities.ORGS, [], { - isManager: true, - isAuditor: false, - isBillingManager: false, - isUser: false, - spaceGuids: [] - })); - }); - it('should add org auditor role to org', () => { - const state = currentUserRolesReducer(undefined, getOrgAction(UserRelationTypes.AUDITED_ORGANIZATIONS)); - const cfPermissions = state.cf[testCFEndpointGuid]; - expect(cfPermissions).toEqual(getState(RoleEntities.ORGS, [], { - isManager: false, - isAuditor: true, - isBillingManager: false, - isUser: false, - spaceGuids: [] - })); - }); - it('should add org billing manager role to org', () => { - const state = currentUserRolesReducer(undefined, getOrgAction(UserRelationTypes.BILLING_MANAGED_ORGANIZATION)); - const cfPermissions = state.cf[testCFEndpointGuid]; - expect(cfPermissions).toEqual(getState(RoleEntities.ORGS, [], { - isManager: false, - isAuditor: false, - isBillingManager: true, - isUser: false, - spaceGuids: [] - })); - }); - it('should add org user role to org', () => { - const state = currentUserRolesReducer(undefined, getOrgAction(UserRelationTypes.ORGANIZATIONS)); - const cfPermissions = state.cf[testCFEndpointGuid]; - expect(cfPermissions).toEqual(getState(RoleEntities.ORGS, [], { - isManager: false, - isAuditor: false, - isBillingManager: false, - isUser: true, - spaceGuids: [] - })); - }); - - it('should retain other org roles', () => { - let state = currentUserRolesReducer(undefined, getOrgAction(UserRelationTypes.ORGANIZATIONS)); - state = currentUserRolesReducer(state, getOrgAction(UserRelationTypes.AUDITED_ORGANIZATIONS, generalGuid)); - const cfPermissions = state.cf[testCFEndpointGuid]; - const toEqual = getState(RoleEntities.ORGS, [{ - guid: testOrgGuid, - roles: { - isManager: false, - isAuditor: false, - isBillingManager: false, - isUser: true, - spaceGuids: [] - }, - }, - { - guid: generalGuid, - roles: { - isManager: false, - isAuditor: true, - isBillingManager: false, - isUser: false, - spaceGuids: [] - } - }]); - expect(cfPermissions).toEqual(toEqual); - }); - - it('should retain other space roles', () => { - let state = currentUserRolesReducer(undefined, getSpaceAction(UserRelationTypes.SPACES)); - state = currentUserRolesReducer(state, getSpaceAction(UserRelationTypes.MANAGED_SPACES, generalGuid, generalGuid)); - const cfPermissions = state.cf[testCFEndpointGuid]; - const toEqual = getState(RoleEntities.SPACES, [{ - guid: testSpaceGuid, - roles: { - orgId: testOrgGuid, - isManager: false, - isAuditor: false, - isDeveloper: true - } - }, - { - guid: generalGuid, - roles: { - orgId: generalGuid, - isManager: true, - isAuditor: false, - isDeveloper: false - } - }]); - const orgState = getState(RoleEntities.ORGS, [{ - guid: testOrgGuid, - roles: { - isManager: false, - isAuditor: false, - isBillingManager: false, - isUser: false, - spaceGuids: [ - testSpaceGuid - ] - } - }, - { - guid: generalGuid, - roles: { - isManager: false, - isAuditor: false, - isBillingManager: false, - isUser: false, - spaceGuids: [ - generalGuid - ] - } - }]); - toEqual.organizations = orgState.organizations; - expect(cfPermissions).toEqual(toEqual); - }); - - it('should retain other space and org roles', () => { - let state = currentUserRolesReducer(undefined, getSpaceAction(UserRelationTypes.SPACES)); - state = currentUserRolesReducer(state, getSpaceAction(UserRelationTypes.MANAGED_SPACES, generalGuid, generalGuid)); - state = currentUserRolesReducer(state, getOrgAction(UserRelationTypes.ORGANIZATIONS)); - state = currentUserRolesReducer(state, getOrgAction(UserRelationTypes.AUDITED_ORGANIZATIONS, generalGuid)); - const cfPermissions = state.cf[testCFEndpointGuid]; - - - const spaceState = getState(RoleEntities.SPACES, [{ - guid: testSpaceGuid, - roles: { - orgId: testOrgGuid, - isManager: false, - isAuditor: false, - isDeveloper: true - } - }, - { - guid: generalGuid, - roles: { - orgId: generalGuid, - isManager: true, - isAuditor: false, - isDeveloper: false - } - }]); - - const orgState = getState(RoleEntities.ORGS, [{ - guid: testOrgGuid, - roles: { - isManager: false, - isAuditor: false, - isBillingManager: false, - isUser: true, - spaceGuids: [ - testSpaceGuid - ] - } - }, - { - guid: generalGuid, - roles: { - isManager: false, - isAuditor: true, - isBillingManager: false, - isUser: false, - spaceGuids: [ - generalGuid - ] - } - }]); - spaceState.organizations = orgState.organizations; - expect(cfPermissions).toEqual(spaceState); - }); }); diff --git a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-roles.reducer.ts b/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-roles.reducer.ts index a6b8e15508..ad4ffc58b4 100644 --- a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-roles.reducer.ts +++ b/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-roles.reducer.ts @@ -1,89 +1,96 @@ import { Action } from '@ngrx/store'; -import { DELETE_ORGANIZATION_SUCCESS } from '../../../../cloud-foundry/src/actions/organization.actions'; +import { SESSION_VERIFIED, VerifiedSession } from '../../actions/auth.actions'; import { - GET_CURRENT_USER_CF_RELATIONS, - GET_CURRENT_USER_CF_RELATIONS_FAILED, - GET_CURRENT_USER_CF_RELATIONS_SUCCESS, - GET_CURRENT_USER_RELATION_SUCCESS, GET_CURRENT_USER_RELATIONS, GET_CURRENT_USER_RELATIONS_FAILED, GET_CURRENT_USER_RELATIONS_SUCCESS, - GetCurrentUserRelationsComplete, - GetUserCfRelations, -} from '../../../../cloud-foundry/src/actions/permissions.actions'; -import { DELETE_SPACE_SUCCESS } from '../../../../cloud-foundry/src/actions/space.actions'; -import { ADD_ROLE_SUCCESS, REMOVE_ROLE_SUCCESS } from '../../../../cloud-foundry/src/actions/users.actions'; -import { - currentUserBaseCFRolesReducer, -} from '../../../../cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-base-cf-role.reducer'; -import { - updateAfterRoleChange, -} from '../../../../cloud-foundry/src/store/reducers/current-user-roles-reducer/current-user-roles-changed.reducers'; -import { SESSION_VERIFIED, VerifiedSession } from '../../actions/auth.actions'; +} from '../../actions/permissions.actions'; +import { entityCatalog } from '../../entity-catalog/entity-catalog'; +import { SessionUser } from '../../types/auth.types'; import { - CONNECT_ENDPOINTS_SUCCESS, - DISCONNECT_ENDPOINTS_SUCCESS, - EndpointActionComplete, - REGISTER_ENDPOINTS_SUCCESS, - UNREGISTER_ENDPOINTS_SUCCESS, -} from '../../actions/endpoint.actions'; -import { getDefaultRolesRequestState, ICurrentUserRolesState } from '../../types/current-user-roles.types'; -import { APISuccessOrFailedAction } from '../../types/request.types'; -import { - currentUserCfRolesRequestStateReducer, - currentUserRolesRequestStateReducer, -} from './current-user-request-state.reducers'; -import { roleInfoFromSessionReducer, updateNewlyConnectedEndpoint } from './current-user-role-session.reducer'; -import { addEndpoint, removeEndpointRoles, removeOrgRoles, removeSpaceRoles } from './current-user-roles-clear.reducers'; + getDefaultRolesRequestState, + ICurrentUserRolesState, + RolesRequestState, +} from '../../types/current-user-roles.types'; const getDefaultState = () => ({ internal: { isAdmin: false, scopes: [] }, - cf: {}, + endpoints: {}, state: getDefaultRolesRequestState() }); export function currentUserRolesReducer(state: ICurrentUserRolesState = getDefaultState(), action: Action): ICurrentUserRolesState { + const stateAfterCoreChanges = coreCurrentUserRolesReducer(state, action); + return entityCatalog.getAllCurrentUserReducers(stateAfterCoreChanges, action); +} + +function coreCurrentUserRolesReducer(state: ICurrentUserRolesState, action: Action): ICurrentUserRolesState { switch (action.type) { - case GET_CURRENT_USER_RELATION_SUCCESS: + case GET_CURRENT_USER_RELATIONS: return { ...state, - cf: currentUserBaseCFRolesReducer(state.cf, action as GetCurrentUserRelationsComplete) + state: currentUserRolesRequestStateReducer(state.state, RolesRequestStateStage.START) }; - case SESSION_VERIFIED: - return roleInfoFromSessionReducer(state, action as VerifiedSession); - case REGISTER_ENDPOINTS_SUCCESS: - return addEndpoint(state, action as EndpointActionComplete); - case CONNECT_ENDPOINTS_SUCCESS: - return updateNewlyConnectedEndpoint(state, action as EndpointActionComplete); - case DISCONNECT_ENDPOINTS_SUCCESS: - case UNREGISTER_ENDPOINTS_SUCCESS: - return removeEndpointRoles(state, action as EndpointActionComplete); - case DELETE_ORGANIZATION_SUCCESS: - return removeOrgRoles(state, action as APISuccessOrFailedAction); - case DELETE_SPACE_SUCCESS: - return removeSpaceRoles(state, action as APISuccessOrFailedAction); - case ADD_ROLE_SUCCESS: - return updateAfterRoleChange(state, true, action as APISuccessOrFailedAction); - case REMOVE_ROLE_SUCCESS: - return updateAfterRoleChange(state, false, action as APISuccessOrFailedAction); - case GET_CURRENT_USER_RELATIONS: case GET_CURRENT_USER_RELATIONS_SUCCESS: - case GET_CURRENT_USER_RELATIONS_FAILED: return { ...state, - state: currentUserRolesRequestStateReducer(state.state, action.type) + state: currentUserRolesRequestStateReducer(state.state, RolesRequestStateStage.SUCCESS) }; - case GET_CURRENT_USER_CF_RELATIONS: - case GET_CURRENT_USER_CF_RELATIONS_SUCCESS: - case GET_CURRENT_USER_CF_RELATIONS_FAILED: + case GET_CURRENT_USER_RELATIONS_FAILED: return { ...state, - cf: currentUserCfRolesRequestStateReducer(state.cf, action as GetUserCfRelations) + state: currentUserRolesRequestStateReducer(state.state, RolesRequestStateStage.FAILURE) }; + case SESSION_VERIFIED: + const svAction = action as VerifiedSession + return applyInternalScopes(state, svAction.sessionData.user); } return state; } + +export enum RolesRequestStateStage { + START, + SUCCESS, + FAILURE, + OTHER +} + +export function currentUserRolesRequestStateReducer(state: RolesRequestState = getDefaultRolesRequestState(), stage: RolesRequestStateStage) { + switch (stage) { + case RolesRequestStateStage.START: + return { + ...state, + fetching: true + }; + case RolesRequestStateStage.SUCCESS: + return { + ...state, + initialised: true, + fetching: false + }; + case RolesRequestStateStage.FAILURE: + return { + ...state, + fetching: false, + error: true + }; + } +} + +function applyInternalScopes(state: ICurrentUserRolesState, user: SessionUser): ICurrentUserRolesState { + const internalRoles = { ...state.internal }; + if (user) { + internalRoles.scopes = user.scopes || []; + // The admin scope is configurable - so look at the flag provided by the backend + internalRoles.isAdmin = user.admin; + } + + return { + ...state, + internal: internalRoles + }; +} \ No newline at end of file diff --git a/src/frontend/packages/store/src/selectors/current-user-role.selectors.ts b/src/frontend/packages/store/src/selectors/current-user-role.selectors.ts index 5f52c49b13..166767e168 100644 --- a/src/frontend/packages/store/src/selectors/current-user-role.selectors.ts +++ b/src/frontend/packages/store/src/selectors/current-user-role.selectors.ts @@ -1,35 +1,28 @@ import { compose } from '@ngrx/store'; -import { ICfRolesState, RoleEntities } from '../../../cloud-foundry/src/store/types/cf-current-user-roles.types'; -import { PermissionStrings, PermissionValues, ScopeStrings } from '../../../core/src/core/current-user-permissions.config'; +import { PermissionValues } from '../../../core/src/core/permissions/current-user-permissions.config'; +import { StratosScopeStrings } from '../../../core/src/core/permissions/stratos-user-permissions.checker'; import { CurrentUserRolesAppState } from '../app-state'; import { ICurrentUserRolesState, IStratosRolesState } from '../types/current-user-roles.types'; +import { UserScopeStrings } from '../types/endpoint.types'; export const selectCurrentUserRolesState = (state: CurrentUserRolesAppState) => state.currentUserRoles; -export const selectCurrentUserStratosRolesState = (state: ICurrentUserRolesState) => state.internal; +const selectCurrentUserStratosRolesState = (state: ICurrentUserRolesState) => state.internal; -export const selectCurrentUserStratosRoles = (role: PermissionValues) => (state: Omit) => { +const selectCurrentUserStratosRoles = (role: PermissionValues) => (state: Omit) => { // Note - should not cover `scopes` return state[role] || false; }; -export const selectEntityWithRole = (role: PermissionStrings, type: RoleEntities) => (state: ICfRolesState) => { - const entityType = state[type]; - return Object.keys(entityType).filter(entity => entityType[entity][role]); -}; - -export const selectCurrentUserRequestState = (state: ICurrentUserRolesState | ICfRolesState) => state.state; - -// TODO: CF code in this file - needs to be moved out - #3769 -export const selectCurrentUserCFGlobalHasScopes = (scope: ScopeStrings) => (scopes: ScopeStrings[]) => scopes.includes(scope); -export const selectCurrentUserCFStratosScopesState = (state: IStratosRolesState) => state.scopes; +export const selectCurrentUserGlobalHasScopes = (scope: UserScopeStrings) => (scopes: UserScopeStrings[]) => scopes.includes(scope); +const selectCurrentUserStratosScopesState = (state: IStratosRolesState) => state.scopes; // Top level stratos endpoint role objects // ============================ -export const getCurrentUserStratosRolesState = compose( +const getCurrentUserStratosRolesState = compose( selectCurrentUserStratosRolesState, selectCurrentUserRolesState ); @@ -45,19 +38,10 @@ export const getCurrentUserStratosRole = (role: PermissionValues) => compose( // Top level stratos endpoint scopes // ============================ -export const getCurrentUserStratosHasScope = (scope: ScopeStrings) => compose( - selectCurrentUserCFGlobalHasScopes(scope), - selectCurrentUserCFStratosScopesState, +export const getCurrentUserStratosHasScope = (scope: StratosScopeStrings) => compose( + selectCurrentUserGlobalHasScopes(scope), + selectCurrentUserStratosScopesState, getCurrentUserStratosRolesState ); // ============================ - -// Top level request state -// ============================ -export const getCurrentUserRequestState = compose( - selectCurrentUserRequestState, - selectCurrentUserRolesState -); -// ============================ - diff --git a/src/frontend/packages/store/src/selectors/endpoint.selectors.ts b/src/frontend/packages/store/src/selectors/endpoint.selectors.ts index cfcd00d4c9..2846302148 100644 --- a/src/frontend/packages/store/src/selectors/endpoint.selectors.ts +++ b/src/frontend/packages/store/src/selectors/endpoint.selectors.ts @@ -1,4 +1,4 @@ -import { compose, createSelector } from '@ngrx/store'; +import { compose } from '@ngrx/store'; import { STRATOS_ENDPOINT_TYPE } from '../../../core/src/base-entity-schemas'; import { InternalAppState, IRequestEntityTypeState } from '../app-state'; @@ -15,7 +15,7 @@ export const endpointStatusSelector = (state: InternalAppState): EndpointState = const endpointEntityKey = EntityCatalogHelpers.buildEntityKey(endpointSchemaKey, STRATOS_ENDPOINT_TYPE); export const endpointEntitiesSelector = selectEntities(endpointEntityKey); -const endpointOfType = (type: string) => +export const endpointOfType = (type: string) => (endpoints: IRequestEntityTypeState): IRequestEntityTypeState => { return Object.values(endpoints || {}).reduce((endpointsOfType, endpoint) => { if (endpoint.cnsi_type === type) { @@ -30,8 +30,6 @@ export const endpointOfTypeSelector = (endpointType: string) => compose( endpointEntitiesSelector, ); -// TODO: Move this #3769 -const cfEndpointEntitiesSelector = endpointOfType('cf'); const getConnectedEndpoints = (endpoints: IRequestEntityTypeState) => Object.values(endpoints || {}).reduce((connected, endpoint) => { @@ -55,15 +53,6 @@ export const connectedEndpointsOfTypesSelector = (endpointType: string) => compo ); -// TODO: Move this #3769 -export const endpointsCFEntitiesSelector = createSelector( - endpointEntitiesSelector, - cfEndpointEntitiesSelector -); - -// TODO: Move this #3769 -export const endpointsCfEntitiesConnectedSelector = connectedEndpointsOfTypesSelector('cf'); - // Single endpoint request information export const endpointsEntityRequestSelector = (guid: string) => selectRequestInfo(endpointEntityKey, guid); // Single endpoint request data diff --git a/src/frontend/packages/store/src/selectors/users-roles.selector.ts b/src/frontend/packages/store/src/selectors/users-roles.selector.ts deleted file mode 100644 index 7c868a7768..0000000000 --- a/src/frontend/packages/store/src/selectors/users-roles.selector.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { compose } from '@ngrx/store'; - -import { CFAppState } from '../../../cloud-foundry/src/cf-app-state'; -import { IUserPermissionInOrg } from '../../../cloud-foundry/src/store/types/user.types'; -import { UsersRolesState } from '../../../cloud-foundry/src/store/types/users-roles.types'; - -export const selectUsersRoles = (state: CFAppState): UsersRolesState => state.manageUsersRoles; - -const selectUsers = (usersRoles: UsersRolesState) => usersRoles.users; -export const selectUsersRolesPicked = compose( - selectUsers, - selectUsersRoles -); - -const selectNewRoles = (usersRoles: UsersRolesState) => usersRoles.newRoles; -export const selectUsersRolesRoles = compose( - selectNewRoles, - selectUsersRoles -); - -const selectCfGuid = (usersRoles: UsersRolesState) => usersRoles.cfGuid; -export const selectUsersRolesCf = compose( - selectCfGuid, - selectUsersRoles -); - -const selectChanged = (usersRoles: UsersRolesState) => usersRoles.changedRoles; -export const selectUsersRolesChangedRoles = compose( - selectChanged, - selectUsersRoles -); - -const selectNewRoleOrgGuid = (newRoles: IUserPermissionInOrg) => newRoles.orgGuid; -export const selectUsersRolesOrgGuid = compose( - selectNewRoleOrgGuid, - selectNewRoles, - selectUsersRoles -); - -const isRemove = (usersRoles: UsersRolesState) => usersRoles.isRemove; -export const selectUsersIsRemove = compose( - isRemove, - selectUsersRoles -); - -const isSetByUsername = (usersRoles: UsersRolesState) => usersRoles.isSetByUsername; -export const selectUsersIsSetByUsername = compose( - isSetByUsername, - selectUsersRoles -); diff --git a/src/frontend/packages/store/src/store.module.ts b/src/frontend/packages/store/src/store.module.ts index a29f7e5e41..d66eb95791 100644 --- a/src/frontend/packages/store/src/store.module.ts +++ b/src/frontend/packages/store/src/store.module.ts @@ -10,6 +10,7 @@ import { EndpointApiError } from './effects/endpoint-api-errors.effects'; import { EndpointsEffect } from './effects/endpoint.effects'; import { MetricsEffect } from './effects/metrics.effects'; import { PaginationEffects } from './effects/pagination.effects'; +import { PermissionsEffects } from './effects/permissions.effect'; import { RecursiveDeleteEffect } from './effects/recursive-entity-delete.effect'; import { RouterEffect } from './effects/router.effects'; import { SetClientFilterEffect } from './effects/set-client-filter.effect'; @@ -18,7 +19,6 @@ import { SystemEffects } from './effects/system.effects'; import { UAASetupEffect } from './effects/uaa-setup.effects'; import { UserFavoritesEffect } from './effects/user-favorites-effect'; import { UserProfileEffect } from './effects/user-profile.effects'; -import { UsersRolesEffects } from './effects/users-roles.effects'; import { PipelineHttpClient } from './entity-request-pipeline/pipline-http-client.service'; import { AppReducersModule } from './reducers.module'; @@ -45,9 +45,9 @@ import { AppReducersModule } from './reducers.module'; SetClientFilterEffect, MetricsEffect, UserProfileEffect, - UsersRolesEffects, RecursiveDeleteEffect, - UserFavoritesEffect + UserFavoritesEffect, + PermissionsEffects ]) ] }) diff --git a/src/frontend/packages/store/src/types/auth.types.ts b/src/frontend/packages/store/src/types/auth.types.ts index de78d33feb..5cf7e40f42 100644 --- a/src/frontend/packages/store/src/types/auth.types.ts +++ b/src/frontend/packages/store/src/types/auth.types.ts @@ -1,22 +1,17 @@ -import { ScopeStrings } from '../../../core/src/core/current-user-permissions.config'; +import { StratosScopeStrings } from '../../../core/src/core/permissions/stratos-user-permissions.checker'; export interface SessionDataEndpoint { guid: string; name: string; version: string; - user: { - admin: boolean, - guid: string, - name: string, - scopes: ScopeStrings[]; - }; + user: SessionUser; type: string; } export interface SessionUser { admin: boolean; guid: string; name: string; - scopes: ScopeStrings[]; + scopes: StratosScopeStrings[]; } export interface PluginConfig { userInvitationsEnabled: 'true' | 'false'; diff --git a/src/frontend/packages/store/src/types/current-user-roles.types.ts b/src/frontend/packages/store/src/types/current-user-roles.types.ts index c79f4c25e1..90c41efcb4 100644 --- a/src/frontend/packages/store/src/types/current-user-roles.types.ts +++ b/src/frontend/packages/store/src/types/current-user-roles.types.ts @@ -1,5 +1,4 @@ -import { IAllCfRolesState, ICfRolesState } from '../../../cloud-foundry/src/store/types/cf-current-user-roles.types'; -import { ScopeStrings } from '../../../core/src/core/current-user-permissions.config'; +import { UserScopeStrings } from './endpoint.types'; export interface RolesRequestState { initialised: boolean; @@ -15,33 +14,15 @@ export function getDefaultRolesRequestState(): RolesRequestState { }; } -export function getDefaultEndpointRoles(): ICfRolesState { - return { - global: { - isAdmin: false, - isReadOnlyAdmin: false, - isGlobalAuditor: false, - canRead: false, - canWrite: false, - scopes: [] - }, - spaces: { - - }, - organizations: { - - }, - state: getDefaultRolesRequestState() - }; -} - export interface IStratosRolesState { isAdmin: boolean; - scopes: ScopeStrings[]; + scopes: UserScopeStrings[]; } export interface ICurrentUserRolesState { internal: IStratosRolesState; - cf: IAllCfRolesState; + endpoints: { + [endpointType: string]: any + } state: RolesRequestState; } diff --git a/src/frontend/packages/store/src/types/endpoint.types.ts b/src/frontend/packages/store/src/types/endpoint.types.ts index aedd7673fd..317c1aab7e 100644 --- a/src/frontend/packages/store/src/types/endpoint.types.ts +++ b/src/frontend/packages/store/src/types/endpoint.types.ts @@ -1,5 +1,5 @@ -import { ScopeStrings } from '../../../core/src/core/current-user-permissions.config'; import { EndpointType } from '../../../core/src/core/extension/extension-types'; +import { StratosScopeStrings } from '../../../core/src/core/permissions/stratos-user-permissions.checker'; import { MetricsAPITargets, MetricsStratosInfo } from '../actions/metrics-api.actions'; import { endpointSchemaKey } from '../helpers/entity-factory'; import { RequestSectionKeys, TRequestTypeKeys } from '../reducers/api-request-reducer/types'; @@ -63,12 +63,14 @@ export interface EndpointModel { export const SystemSharedUserGuid = '00000000-1111-2222-3333-444444444444'; +export type UserScopeStrings = string | StratosScopeStrings; + // Metadata for the user connected to an endpoint export interface EndpointUser { guid: string; name: string; admin: boolean; - scopes?: ScopeStrings[]; + scopes?: UserScopeStrings[]; } export interface EndpointState { diff --git a/src/frontend/packages/store/testing/src/store-test-helper.ts b/src/frontend/packages/store/testing/src/store-test-helper.ts index 9ad3e9ab30..3dac3230b7 100644 --- a/src/frontend/packages/store/testing/src/store-test-helper.ts +++ b/src/frontend/packages/store/testing/src/store-test-helper.ts @@ -7,11 +7,11 @@ import { AppState } from '../../src/app-state'; import { entityCatalog } from '../../src/entity-catalog/entity-catalog'; import { EntityCatalogEntityConfig } from '../../src/entity-catalog/entity-catalog.types'; import { appReducers } from '../../src/reducers.module'; -import { getDefaultRequestState } from '../../src/reducers/api-request-reducer/types'; +import { getDefaultRequestState, rootUpdatingKey } from '../../src/reducers/api-request-reducer/types'; import { getDefaultPaginationEntityState } from '../../src/reducers/pagination-reducer/pagination-reducer-reset-pagination'; import { NormalizedResponse } from '../../src/types/api.types'; import { SessionData, SessionDataEndpoint } from '../../src/types/auth.types'; -import { getDefaultEndpointRoles, getDefaultRolesRequestState } from '../../src/types/current-user-roles.types'; +import { getDefaultRolesRequestState } from '../../src/types/current-user-roles.types'; import { EndpointModel } from '../../src/types/endpoint.types'; import { BaseEntityValues } from '../../src/types/entity.types'; import { WrapperRequestActionSuccess } from '../../src/types/request.types'; @@ -202,9 +202,7 @@ function getDefaultInitialTestStratosStoreState() { isAdmin: false, scopes: [] }, - cf: { - [testSCFEndpointGuid]: getDefaultEndpointRoles() - }, + endpoints: {}, state: getDefaultRolesRequestState() } }; @@ -250,7 +248,7 @@ function getDefaultInitialTestStoreState(): AppState { '57ab08d8-86cc-473a-8818-25d5e8d0ea23': { fetching: false, updating: { - _root_: { + [rootUpdatingKey]: { busy: false, error: false, message: '' diff --git a/src/jetstream/go.mod b/src/jetstream/go.mod index 53e96fbe80..59494917e6 100644 --- a/src/jetstream/go.mod +++ b/src/jetstream/go.mod @@ -40,7 +40,7 @@ require ( github.com/gorilla/context v1.1.1 github.com/gorilla/securecookie v1.1.1 github.com/gorilla/sessions v1.1.3 - github.com/gorilla/websocket v1.4.1 + github.com/gorilla/websocket v1.4.2 github.com/govau/cf-common v0.0.7 github.com/jessevdk/go-flags v1.4.0 // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect diff --git a/src/jetstream/go.sum b/src/jetstream/go.sum index 9bf918f27d..a49e701b9a 100644 --- a/src/jetstream/go.sum +++ b/src/jetstream/go.sum @@ -341,12 +341,12 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU= github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= -github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosuri/uitable v0.0.1 h1:M9sMNgSZPyAu1FJZJLpJ16ofL8q5ko2EDUkICsynvlY= github.com/gosuri/uitable v0.0.1/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/govau/cf-common v0.0.7 h1:uhp1P6XM6GGzu1+A4C7LELLX/9mCmH6W5DpJZC0kWmo= github.com/govau/cf-common v0.0.7/go.mod h1:5xL/OfE7wxeyHlXb7iei0rAbdQ/5v6dF18BZknPv7NQ= github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= @@ -782,7 +782,6 @@ gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -793,6 +792,8 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81 helm.sh/helm/v3 v3.0.0 h1:or/9cs1GgfcTQeEnR2CVJNw893/rmqIG1KsNHmUiSFw= helm.sh/helm/v3 v3.0.0/go.mod h1:sI7B9yfvMgxtTPMWdk1jSKJ2aa59UyP9qhPydqW6mgo= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/src/test-e2e/cloud-foundry/manage-users-by-username-stepper-e2e.spec.ts b/src/test-e2e/cloud-foundry/manage-users-by-username-stepper-e2e.spec.ts index 3cbd402994..f72a3d3422 100644 --- a/src/test-e2e/cloud-foundry/manage-users-by-username-stepper-e2e.spec.ts +++ b/src/test-e2e/cloud-foundry/manage-users-by-username-stepper-e2e.spec.ts @@ -1,6 +1,6 @@ import { browser, by, element, promise } from 'protractor'; -import { CfUser } from '../../frontend/packages/cloud-foundry/src/store/types/user.types'; +import { CfUser } from '../../frontend/packages/cloud-foundry/src/store/types/cf-user.types'; import { APIResource } from '../../frontend/packages/store/src/types/api.types'; import { e2e } from '../e2e'; import { CFHelpers } from '../helpers/cf-helpers'; diff --git a/src/test-e2e/cloud-foundry/users-removal-e2e.helper.ts b/src/test-e2e/cloud-foundry/users-removal-e2e.helper.ts index 931679b0a4..a449dddd5f 100644 --- a/src/test-e2e/cloud-foundry/users-removal-e2e.helper.ts +++ b/src/test-e2e/cloud-foundry/users-removal-e2e.helper.ts @@ -1,7 +1,7 @@ import { browser, by, element, promise } from 'protractor'; import { protractor } from 'protractor/built/ptor'; -import { CfUser } from '../../frontend/packages/cloud-foundry/src/store/types/user.types'; +import { CfUser } from '../../frontend/packages/cloud-foundry/src/store/types/cf-user.types'; import { APIResource } from '../../frontend/packages/store/src/types/api.types'; import { e2e } from '../e2e'; import { CFHelpers } from '../helpers/cf-helpers'; diff --git a/src/test-e2e/helpers/cf-helpers.ts b/src/test-e2e/helpers/cf-helpers.ts index e0df19f6f9..a18d65e8d0 100644 --- a/src/test-e2e/helpers/cf-helpers.ts +++ b/src/test-e2e/helpers/cf-helpers.ts @@ -10,7 +10,7 @@ import { ISpaceQuotaDefinition, } from '../../frontend/packages/cloud-foundry/src/cf-api.types'; import { CFResponse } from '../../frontend/packages/cloud-foundry/src/store/types/cf-api.types'; -import { CfUser } from '../../frontend/packages/cloud-foundry/src/store/types/user.types'; +import { CfUser } from '../../frontend/packages/cloud-foundry/src/store/types/cf-user.types'; import { APIResource } from '../../frontend/packages/store/src/types/api.types'; import { ApplicationPageSummaryTab } from '../application/po/application-page-summary.po'; import { CfTopLevelPage } from '../cloud-foundry/cf-level/cf-top-level-page.po'; diff --git a/src/tsconfig.json b/src/tsconfig.json index a3a2d47e41..acd4416c47 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -30,8 +30,8 @@ ], "preserveSymlinks": true, "paths": { - "@stratos/store": ["frontend/packages/store/src/public-api.ts"], - "@stratos/store/testing": ["frontend/packages/store/testing/public-api.ts"] + "@stratosui/store": ["frontend/packages/store/src/public-api.ts"], + "@stratosui/store/testing": ["frontend/packages/store/testing/public-api.ts"] } } } \ No newline at end of file From e798fc6793c55384df7e7673f3476abe8b6c1c71 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 23 Jun 2020 11:22:09 +0100 Subject: [PATCH 542/648] Indicate which commands are available --- deploy/containers/kube-terminal/kubeconsole.bashrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deploy/containers/kube-terminal/kubeconsole.bashrc b/deploy/containers/kube-terminal/kubeconsole.bashrc index e71545ed5a..0a1edfdd56 100644 --- a/deploy/containers/kube-terminal/kubeconsole.bashrc +++ b/deploy/containers/kube-terminal/kubeconsole.bashrc @@ -72,5 +72,6 @@ touch "/stratos/.firstrun" # Remove any env vars matching KUBERNETES unset `compgen -A variable | grep KUBERNETES` -echo "Ready" +echo +echo -e "Ready - ${CYAN}kubectl${RESET} and ${CYAN}helm${RESET} commands are available" echo "" From f34497f1ee6e3cea9967fd456793d0d65f829753 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 23 Jun 2020 11:25:30 +0100 Subject: [PATCH 543/648] Fix merge issue --- deploy/kubernetes/imagelist-gen.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deploy/kubernetes/imagelist-gen.sh b/deploy/kubernetes/imagelist-gen.sh index 4114e81aae..1605cdb54d 100755 --- a/deploy/kubernetes/imagelist-gen.sh +++ b/deploy/kubernetes/imagelist-gen.sh @@ -52,16 +52,16 @@ printf "${CYAN}Chart folder contents:${RESET}\n" ls -alR echo "" -# Add any customizations -addCustomizations - helm template -f ${__DIRNAME}/imagelist.values.yaml ${CHART_FOLDER} | grep "image:" | grep --extended --only-matching '([^"/[:space:]]+/)?[^"/[:space:]]+/[^:[:space:]]+:[a-zA-Z0-9\._-]+' | sort | uniq | awk -F'/' '{print $2}' > imagelist.txt - if [ $? -ne 0 ]; then echo -e "${BOLD}${RED}ERROR: Failed to render Helm Chart in order to generate image list" exit 1 fi +# Add any customizations to the image list +# Mainly used if there are unreferenced images that need to be included +addCustomizations + popd > /dev/null printf "${CYAN}" From 53a4b2a2171eec7d620c3d5706e6d5497b73c591 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 23 Jun 2020 11:27:04 +0100 Subject: [PATCH 544/648] Remove commented-out code --- .../src/shared/components/ssh-viewer/ssh-viewer.component.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/frontend/packages/core/src/shared/components/ssh-viewer/ssh-viewer.component.ts b/src/frontend/packages/core/src/shared/components/ssh-viewer/ssh-viewer.component.ts index c12df6d6a1..8d9cedccb8 100644 --- a/src/frontend/packages/core/src/shared/components/ssh-viewer/ssh-viewer.component.ts +++ b/src/frontend/packages/core/src/shared/components/ssh-viewer/ssh-viewer.component.ts @@ -68,7 +68,6 @@ export class SshViewerComponent implements OnInit, OnDestroy { this.xterm = new Terminal(); this.xterm.loadAddon(this.xtermFitAddon); this.xterm.open(this.container.nativeElement); - // this.xtermFitAddon.fit(); this.resize(); this.xterm.onKey(e => { @@ -125,7 +124,6 @@ export class SshViewerComponent implements OnInit, OnDestroy { } else { console.log('Error') const eMsg = this.errorMessage; - //this.disconnect(); this.errorMessage = eMsg; } }, From d64f5bfff2190377089bea7efba1d614ad29357c Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 23 Jun 2020 14:50:04 +0100 Subject: [PATCH 545/648] Helm chart updates for --recreate-pods (#382) * Fix misaligned user button - fixes #4316 * Temporarily add branch v3.2.1 to travis * check sso whitelist in more places Signed-off-by: Ben Berry * refactor sso state checks into single function Signed-off-by: Ben Berry * Update version * First pass at changelog (additional SSO fix required) * Update changelog * Fix package-lock.json * Remove v3.2.1 was .travis.taml * Remove need for --recreate-pods when upgrading * Add support for helm chart customizations * Removed change not needed * Apply recreate-pods fix to SUSE additional containers Co-authored-by: Richard Cox Co-authored-by: Ben Berry Co-authored-by: Richard Cox --- custom-src/deploy/kubernetes/customize-helm.sh | 6 +++++- deploy/kubernetes/console/templates/chartsync.yaml | 3 +++ .../console/templates/fdbdoclayer/deployment.yaml | 4 ++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/custom-src/deploy/kubernetes/customize-helm.sh b/custom-src/deploy/kubernetes/customize-helm.sh index 6284fe6c35..e57c571440 100755 --- a/custom-src/deploy/kubernetes/customize-helm.sh +++ b/custom-src/deploy/kubernetes/customize-helm.sh @@ -15,6 +15,11 @@ echo "Customizations folder: ${DIR}" echo "Chart folder : ${CHART_PATH}" echo "" +# =========================================================================================== +# Copy our customization helper over the default, empty one +# =========================================================================================== +cp "${DIR}/__stratos.tpl" "${CHART_PATH}/templates/__stratos.tpl" + # =========================================================================================== # Chart.yaml changes # =========================================================================================== @@ -45,7 +50,6 @@ echo -e "${CYAN}Patching README.md${RESET}" sed -i.bak -e 's@Stratos@SUSE Stratos Console@g' ${CHART_PATH}/README.md # Change first paragraph to include Kubernetes -console for Cloud Foundry. SRC="console for Cloud Foundry." DEST="console for Cloud Foundry and Kubernetes." sed -i.bak -e 's@'"${SRC}"'@'"${DEST}"'@g' ${CHART_PATH}/README.md diff --git a/deploy/kubernetes/console/templates/chartsync.yaml b/deploy/kubernetes/console/templates/chartsync.yaml index 2acedc0bca..1eeab2affd 100644 --- a/deploy/kubernetes/console/templates/chartsync.yaml +++ b/deploy/kubernetes/console/templates/chartsync.yaml @@ -34,6 +34,9 @@ spec: imagePullPolicy: {{.Values.imagePullPolicy}} command: ["/chartrepo"] args: ["serve", "--doclayer-url=mongodb://{{ .Release.Name }}-fdbdoclayer:27016", "--cafile=/etc/certs/ca.crt", "--certfile=/etc/certs/tls.crt", "--keyfile=/etc/certs/tls.key"] + env: + - name: STRATOS_IMAGE_REF + value: "{{.Values.consoleVersion}}:{{ .Release.Revision }}" ports: - name: endpoint containerPort: 8080 diff --git a/deploy/kubernetes/console/templates/fdbdoclayer/deployment.yaml b/deploy/kubernetes/console/templates/fdbdoclayer/deployment.yaml index 63cdb0e7e8..4a1a02e06d 100644 --- a/deploy/kubernetes/console/templates/fdbdoclayer/deployment.yaml +++ b/deploy/kubernetes/console/templates/fdbdoclayer/deployment.yaml @@ -52,6 +52,8 @@ spec: image: {{.Values.kube.registry.hostname}}/{{.Values.kube.organization}}/{{.Values.images.fdbdoclayer}}:{{.Values.consoleVersion}} imagePullPolicy: {{.Values.imagePullPolicy}} env: + - name: STRATOS_IMAGE_REF + value: "{{.Values.consoleVersion}}:{{ .Release.Revision }}" - name: FDB_COORDINATOR value: {{ .Release.Name }}-fdbdoclayer - name: FDB_LISTEN_IP @@ -88,6 +90,8 @@ spec: image: {{.Values.kube.registry.hostname}}/{{.Values.kube.organization}}/{{.Values.images.fdbserver}}:{{.Values.consoleVersion}} imagePullPolicy: {{.Values.imagePullPolicy}} env: + - name: STRATOS_IMAGE_REF + value: "{{.Values.consoleVersion}}:{{ .Release.Revision }}" - name: FDB_COORDINATOR value: {{ .Release.Name }}-fdbdoclayer - name: FDB_LISTEN_IP From 817b72948d9541e58f0370b5e95df6f1b4e4e966 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 23 Jun 2020 15:27:11 +0100 Subject: [PATCH 546/648] Fix unit test compilation --- .../kube-terminal/kube-console.component.spec.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/custom-src/frontend/app/custom/kubernetes/kube-terminal/kube-console.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kube-terminal/kube-console.component.spec.ts index 85fda06bf6..8671538bc9 100644 --- a/custom-src/frontend/app/custom/kubernetes/kube-terminal/kube-console.component.spec.ts +++ b/custom-src/frontend/app/custom/kubernetes/kube-terminal/kube-console.component.spec.ts @@ -1,9 +1,10 @@ -import { async, ComponentFixture, TestBed } from './@angular/core/testing'; -import { RouterTestingModule } from './@angular/router/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; +import { ApplicationService } from '../../../../../cloud-foundry/src/features/applications/application.service'; +import { ApplicationServiceMock } from '../../../../../cloud-foundry/test-framework/application-service-helper'; import { TabNavService } from '../../../../tab-nav.service'; -import { ApplicationServiceMock } from '../../../../test-framework/application-service-helper'; -import { createBasicStoreModule } from '../../../../test-framework/store-test-helper'; import { CoreModule } from '../../../core/core.module'; import { SharedModule } from '../../../shared/shared.module'; import { KubeConsoleComponent } from './kube-console.component'; From 3ec93620868aa0eb1b4efba269c56533c2c458a5 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 24 Jun 2020 11:44:44 +0100 Subject: [PATCH 547/648] Bug fix for when terminal is not enabled --- src/jetstream/plugins/kubernetes/main.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/jetstream/plugins/kubernetes/main.go b/src/jetstream/plugins/kubernetes/main.go index 929c610494..2cee120fcc 100644 --- a/src/jetstream/plugins/kubernetes/main.go +++ b/src/jetstream/plugins/kubernetes/main.go @@ -146,7 +146,9 @@ func (c *KubernetesSpecification) Init() error { c.portalProxy.GetConfig().PluginConfig[kubeTerminalPluginConfigSetting] = strconv.FormatBool(c.portalProxy.GetConfig().EnableTechPreview) // Kick off the cleanup of any old kube terminal pods - c.kubeTerminal.StartCleanup() + if c.kubeTerminal != nil { + c.kubeTerminal.StartCleanup() + } return nil } @@ -177,7 +179,9 @@ func (c *KubernetesSpecification) AddSessionGroupRoutes(echoGroup *echo.Group) { echoGroup.GET("/helm/releases/:endpoint/:namespace/:name", c.GetRelease) // Kube Terminal - echoGroup.GET("/kubeterminal/:guid", c.kubeTerminal.Start) + if c.kubeTerminal != nil { + echoGroup.GET("/kubeterminal/:guid", c.kubeTerminal.Start) + } } func (c *KubernetesSpecification) Info(apiEndpoint string, skipSSLValidation bool) (interfaces.CNSIRecord, interface{}, error) { From 16326d56fb94a46a8341e26825c94cd32a19d860 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 24 Jun 2020 11:48:18 +0100 Subject: [PATCH 548/648] Only enable if tech preview is on --- .../plugins/kubernetes/terminal/terminal.go | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/jetstream/plugins/kubernetes/terminal/terminal.go b/src/jetstream/plugins/kubernetes/terminal/terminal.go index a77f792908..83d259147b 100644 --- a/src/jetstream/plugins/kubernetes/terminal/terminal.go +++ b/src/jetstream/plugins/kubernetes/terminal/terminal.go @@ -4,8 +4,8 @@ import ( "fmt" "io/ioutil" - "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes/api" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces/config" log "github.com/sirupsen/logrus" @@ -13,13 +13,13 @@ import ( const ( serviceAccountTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token" - serviceHostEnvVar = "KUBERNETES_SERVICE_HOST" - servicePortEnvVar = "KUBERNETES_SERVICE_PORT" + serviceHostEnvVar = "KUBERNETES_SERVICE_HOST" + servicePortEnvVar = "KUBERNETES_SERVICE_PORT" // For dev - read token from env var serviceTokenEnvVar = "KUBE_TERMINAL_SERVICE_ACCOUNT_TOKEN" - stratosRoleLabel = "stratos-role" - stratosKubeTerminalRole = "kube-terminal" + stratosRoleLabel = "stratos-role" + stratosKubeTerminalRole = "kube-terminal" stratosSessionAnnotation = "stratos-session" consoleContainerName = "kube-terminal" @@ -27,16 +27,22 @@ const ( // KubeTerminal supports spawning pods to provide a CLI environment to the user type KubeTerminal struct { - PortalProxy interfaces.PortalProxy - Namespace string `configName:"STRATOS_KUBERNETES_NAMESPACE"` - Image string `configName:"STRATOS_KUBERNETES_TERMINAL_IMAGE"` - Token []byte - APIServer string - Kube api.Kubernetes + PortalProxy interfaces.PortalProxy + Namespace string `configName:"STRATOS_KUBERNETES_NAMESPACE"` + Image string `configName:"STRATOS_KUBERNETES_TERMINAL_IMAGE"` + Token []byte + APIServer string + Kube api.Kubernetes } // NewKubeTerminal checks that the environment is set up to support the Kube Terminal func NewKubeTerminal(p interfaces.PortalProxy) *KubeTerminal { + // Only enabled in tech preview + if !p.GetConfig().EnableTechPreview { + log.Info("Kube Terminal not enabled - requires tech preview") + return nil + } + kt := &KubeTerminal{ PortalProxy: p, } From cd65b145a8c0cefde02f0a7f77b83d15bde21772 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 24 Jun 2020 16:49:25 +0100 Subject: [PATCH 549/648] Add dev doc and helper setup script --- build/tools/kube-terminal-dev.sh | 58 ++++++++++++++++++++++++++++++++ docs/suse/kube-terminal-dev.md | 21 ++++++++++++ 2 files changed, 79 insertions(+) create mode 100755 build/tools/kube-terminal-dev.sh create mode 100644 docs/suse/kube-terminal-dev.md diff --git a/build/tools/kube-terminal-dev.sh b/build/tools/kube-terminal-dev.sh new file mode 100755 index 0000000000..2ec5c69c3d --- /dev/null +++ b/build/tools/kube-terminal-dev.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +# Colours +CYAN="\033[96m" +YELLOW="\033[93m" +RED="\033[91m" +RESET="\033[0m" +BOLD="\033[1m" + +# Program Paths: +PROG=$(basename ${BASH_SOURCE[0]}) +PROG_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +STRATOS_DIR="$( cd "${PROG_DIR}/../.." && pwd )" + +echo "Creating Service Account" +SRC="${STRATOS_DIR}/deploy/kubernetes/console/templates/service-account.yaml" + +TEMPFILE=$(mktemp) +cp $SRC $TEMPFILE +sed -i.bak '/\s*helm/d' $TEMPFILE +sed -i.bak '/\s*app\.kubernetes\.io\/version/d' $TEMPFILE +sed -i.bak '/\s*app\.kubernetes\.io\/instance/d' $TEMPFILE +sed -i.bak '/\s*{{-/d' $TEMPFILE + +kubectl apply -f $TEMPFILE + +# Service account should be created - now need to get token +SECRET=$(kubectl get sa stratos -o json | jq -r '.secrets[0].name') +TOKEN=$(kubectl get secret $SECRET -o json | jq -r '.data.token') +echo "Token secret: $SECRET" +echo "Token $TOKEN" + +rm -f $TEMPFILE +rm -f $TEMPFILE.bak + +# Create a namespace +NS="stratos-dev" +kubectl get ns $NS > /dev/null +if [ $? -ne 0 ]; then + kubectl create ns $NS +fi + +CFG=${STRATOS_DIR}/src/jetstream/config.properties +touch $CFG + +echo -e "\n# Kubernetes Terminal Config for dev" >> $CFG +echo "STRATOS_KUBERNETES_NAMESPACE=stratos-dev" >> $CFG +echo "STRATOS_KUBERNETES_TERMINAL_IMAGE=splatform/stratos-kube-terminal:dev" >> $CFG +echo "KUBE_TERMINAL_SERVICE_ACCOUNT_TOKEN=$TOKEN" >> $CFG + +MKUBE=$(minikube ip) +if [ $? -eq 0 ]; then + echo "KUBERNETES_SERVICE_HOST=$MKUBE" >> $CFG + echo "KUBERNETES_SERVICE_PORT=8443" >> $CFG +else + echo "KUBERNETES_SERVICE_HOST=" >> $CFG + echo "KUBERNETES_SERVICE_PORT=8443" >> $CFG +fi diff --git a/docs/suse/kube-terminal-dev.md b/docs/suse/kube-terminal-dev.md new file mode 100644 index 0000000000..fe552b064c --- /dev/null +++ b/docs/suse/kube-terminal-dev.md @@ -0,0 +1,21 @@ +# Enabling the Kubernetes Terminal in local development + +You need a Kubernetes cluster with `kubectl` set up and configured with the kubeconfig file. + +Run the script `build/tools/kube-terminal-dev.sh` + +This script will: + +- Create a service account named `stratos` +- Create a namespace named `stratos-dev` +- Write environment variables to the `src/jetstream/config.properties` file + +If you have minikube running, the configuration for your Kubernetes API Server will be set correctly - otherwise +you will need to edit the `src/jetstream/config.properties` file and set these two variables: + +- `KUBERNETES_SERVICE_HOST` +- `KUBERNETES_SERVICE_PORT` + +The Jetstream backend should be configured. + +> Note: Ensure you set `ENABLE_TECH_PREVIEW=true` to enable the Kubernetes Terminal feature. \ No newline at end of file From 37f53d56f80f5b530db894ed718862d62659aad6 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 26 Jun 2020 10:59:00 +0100 Subject: [PATCH 550/648] Add ability to import K8S endpoints from kube config file (#381) * Fix table passing issue * WIP: Import from kube config file * Minor tidy ups * Remove debugging code * Fix for error message being swallowed * Improvements to load, add edit name - tidied up selection component - move non-ui logic into helper - move record of clusters into helper - tidied up config helper - fixed select all intermediate state on bizare selections & first load - cleans a lot of table datasource interfaces * Specific fixes for upstream - Fix table header alignment - Fix checkbox table column alignment * Rename name column component, fix default context selection when invalid * Tidying up, add skip ssl, fix register of new * Allow user to skip connect by not suppling user * Tidying up, set AZK type, fixes * Minor fixes, subtle edit symbols * Fix case where cluster is register only but cannot connect, allow user to review final step * Add detection for EKS * Remove border between row that's errored and it's errror description row * Fix unit tests * Set initial state of skip ssl checkbox given request to kube * Fix unit tests * Remove some console.logs * Multiple small changes/fixes - tidy up - remove /kubeconfig.yaml fetch - fix select all at indeterminate state (should always select all) - fix apply for auto skip ssl * Fix connect error status message, change title of register endpoint stepper, minor changes * Improve table row error - remove border radius after expansion change - error rows have large indent to help show associated row - remove old styles that weren't applied following expansion change * Spacing * Changes following review * Changes following review * Fix build warning which is silent in dev world * Fixes following merge * Fix e2e test * Add icon for kube config import Co-authored-by: Richard Cox --- .../kube-config-auth.helper.ts | 158 +++++++++ .../kube-config-import.component.html | 3 + .../kube-config-import.component.scss | 8 + .../kube-config-import.component.spec.ts | 29 ++ .../kube-config-import.component.ts | 300 ++++++++++++++++++ ...-config-table-import-status.component.html | 1 + ...-config-table-import-status.component.scss | 0 ...nfig-table-import-status.component.spec.ts | 29 ++ ...be-config-table-import-status.component.ts | 26 ++ .../kube-config-registration.component.html | 11 + .../kube-config-registration.component.scss | 0 ...kube-config-registration.component.spec.ts | 35 ++ .../kube-config-registration.component.ts | 8 + .../kube-config-selection.component.html | 17 + .../kube-config-selection.component.scss | 35 ++ .../kube-config-selection.component.spec.ts | 29 ++ .../kube-config-selection.component.ts | 210 ++++++++++++ .../kube-config-table-cert.component.html | 6 + .../kube-config-table-cert.component.scss | 0 .../kube-config-table-cert.component.spec.ts | 33 ++ .../kube-config-table-cert.component.ts | 73 +++++ .../kube-config-table-name.component.html | 9 + .../kube-config-table-name.component.scss | 12 + .../kube-config-table-name.component.spec.ts | 34 ++ .../kube-config-table-name.component.ts | 11 + .../kube-config-table-select.component.html | 1 + .../kube-config-table-select.component.scss | 0 ...kube-config-table-select.component.spec.ts | 35 ++ .../kube-config-table-select.component.ts | 22 ++ ...onfig-table-sub-type-select.component.html | 3 + ...onfig-table-sub-type-select.component.scss | 0 ...ig-table-sub-type-select.component.spec.ts | 35 ++ ...-config-table-sub-type-select.component.ts | 33 ++ ...be-config-table-user-select.component.html | 9 + ...be-config-table-user-select.component.scss | 0 ...config-table-user-select.component.spec.ts | 39 +++ ...kube-config-table-user-select.component.ts | 31 ++ .../kube-config.helper.ts | 201 ++++++++++++ .../kube-config.types.ts | 99 ++++++ .../kubernetes/kubernetes-entity-generator.ts | 64 ++-- .../custom/kubernetes/kubernetes.module.ts | 6 +- .../kubernetes/kubernetes.setup.module.ts | 40 ++- .../frontend/assets/custom/kube_import.png | Bin 0 -> 13298 bytes .../table-cell-edit-variable.component.html | 3 +- .../table-cell-edit-variable.component.scss | 4 + .../cf-quotas-data-source.service.ts | 2 +- .../cf-space-quotas-data-source.service.ts | 2 +- .../packages/core/sass/_all-theme.scss | 2 +- .../core/sass/components/mat-table.scss | 2 +- .../packages/core/src/core/utils.service.ts | 8 + .../src/features/endpoints/connect.service.ts | 19 ++ .../create-endpoint-base-step.component.html | 2 +- .../create-endpoint-base-step.component.ts | 9 +- .../create-endpoint.component.html | 8 +- .../create-endpoint.component.ts | 30 +- .../app-action-monitor-icon.component.ts | 11 +- .../app-action-monitor.component.html | 5 +- .../app-action-monitor.component.ts | 18 +- .../file-input/file-input.component.html | 3 +- .../file-input/file-input.component.ts | 15 +- .../list-data-source-types.ts | 33 +- .../table-cell-edit.component.html | 21 +- .../table-cell-edit.component.scss | 12 +- .../table-cell-edit.component.ts | 23 +- .../table-header-select.component.html | 5 +- .../table-row/table-row.component.html | 12 +- .../table-row/table-row.component.scss | 24 +- .../table-row/table-row.component.theme.scss | 29 +- .../table-row/table-row.component.ts | 9 + .../list/list-table/table.component.html | 2 +- .../list/list-table/table.component.scss | 14 +- .../list/list-table/table.component.ts | 1 + .../packages/core/src/shared/shared.module.ts | 4 +- .../entity-catalog/entity-catalog.types.ts | 2 + .../api-request-reducer/fail-request.ts | 2 +- .../src/reducers/api-request-reducer/types.ts | 8 + .../store/src/selectors/api.selectors.ts | 4 +- src/jetstream/default.config.properties | 4 +- src/jetstream/plugins/kubernetes/main.go | 32 +- src/test-e2e/endpoints/register-dialog.po.ts | 2 +- 80 files changed, 1963 insertions(+), 118 deletions(-) create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-auth.helper.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.html create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.scss create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.spec.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.html create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.scss create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.spec.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-registration.component.html create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-registration.component.scss create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-registration.component.spec.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-registration.component.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.html create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.scss create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.spec.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.html create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.scss create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.spec.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.html create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.scss create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.spec.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-select/kube-config-table-select.component.html create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-select/kube-config-table-select.component.scss create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-select/kube-config-table-select.component.spec.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-select/kube-config-table-select.component.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-sub-type-select/kube-config-table-sub-type-select.component.html create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-sub-type-select/kube-config-table-sub-type-select.component.scss create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-sub-type-select/kube-config-table-sub-type-select.component.spec.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-sub-type-select/kube-config-table-sub-type-select.component.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-user-select/kube-config-table-user-select.component.html create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-user-select/kube-config-table-user-select.component.scss create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-user-select/kube-config-table-user-select.component.spec.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-user-select/kube-config-table-user-select.component.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config.helper.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config.types.ts create mode 100644 custom-src/frontend/assets/custom/kube_import.png diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-auth.helper.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-auth.helper.ts new file mode 100644 index 0000000000..4f2c119a4c --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-auth.helper.ts @@ -0,0 +1,158 @@ +import { ComponentFactoryResolver, Injector } from '@angular/core'; +import { FormBuilder } from '@angular/forms'; + +import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; +import { ConnectEndpointData } from '../../../features/endpoints/connect.service'; +import { RowState } from '../../../shared/components/list/data-sources-controllers/list-data-source-types'; +import { KUBERNETES_ENDPOINT_TYPE } from '../kubernetes-entity-factory'; +import { EndpointAuthTypeConfig, IAuthForm } from './../../../core/extension/extension-types'; +import { KubeConfigFileCluster, KubeConfigFileUser } from './kube-config.types'; + +/** + * Auth helper tries to figure out the Kubernetes sub-type and auth to use + * based on the kube config file contents + */ +export class KubeConfigAuthHelper { + + authTypes: { [name: string]: EndpointAuthTypeConfig } = {}; + + public subTypes = []; + + constructor() { + const epTypeInfo = entityCatalog.getAllEndpointTypes(false); + const k8s = epTypeInfo.find(entity => entity.type === KUBERNETES_ENDPOINT_TYPE); + if (k8s && k8s.definition) { + const defn = k8s.definition; + + // Collect all of the auth types + defn.authTypes.forEach(at => { + this.authTypes[at.value] = at; + }); + + this.subTypes.push({ id: '', name: 'Generic' }); + + // Collect all of the auth types for the sub-types + defn.subTypes.forEach(st => { + if (st.type !== 'config') { + this.subTypes.push({ id: st.type, name: st.labelShort }); + } + st.authTypes.forEach(at => { + this.authTypes[at.value] = at; + }); + }); + + // Sort the subtypes + this.subTypes = this.subTypes.sort((a, b) => a.name.localeCompare(b.name)); + } + } + + // Try and parse the authentication metadata + public parseAuth(cluster: KubeConfigFileCluster, user: KubeConfigFileUser): RowState { + + // Default subtype is generic Kubernetes + cluster._subType = ''; + + // Certificate authentication first + + // In-file certificate authentication + if (user.user['client-certificate-data'] && user.user['client-key-data']) { + // We are good to go - create the form data + + // Default is generic kubernetes + let subType = ''; + const authType = 'kube-cert-auth'; + if (cluster.cluster.server.indexOf('azmk8s.io') >= 0) { + // Probably Azure + subType = 'aks'; + cluster._subType = 'aks'; + } + + const authData = { + authType, + subType, + values: { + cert: user.user['client-certificate-data'], + certKey: user.user['client-key-data'] + } + }; + user._authData = authData; + return {}; + } + + if (user.user['client-certificate'] || user.user['client-key']) { + cluster._additionalUserInfo = true; + return { + message: 'This endpoint will be registered but not connected (additional information is required)', + info: true + }; + } + + const authProvider = user.user['auth-provider']; + + + if (authProvider && authProvider.config) { + if (authProvider.config['cmd-path'] && authProvider.config['cmd-path'].indexOf('gcloud') !== -1) { + // GKE + cluster._subType = 'gke'; + // Can not connect to GKE - user must do so manually + cluster._additionalUserInfo = true; + return { + message: 'This endpoint will be registered but not connected (additional information is required)', + info: true + }; + } + } + + if ( + cluster.cluster.server.indexOf('eks.amazonaws.com') >= 0 || + (user.user.exec && user.user.exec.command && user.user.exec.command === 'aws-iam-authenticator') + ) { + // Probably EKS + cluster._subType = 'eks'; + cluster._additionalUserInfo = true; + return { + message: 'This endpoint will be registered but not connected (additional information is required)', + info: true + }; + } + + return { message: 'Authentication mechanism is not supported', warning: true }; + } + + // Use the auto component to get the data in the correct format for connecting to the endpoint + public getAuthDataForConnect(resolver: ComponentFactoryResolver, injector: Injector, fb: FormBuilder, user: KubeConfigFileUser) + : ConnectEndpointData | null { + + let data = null; + + // Get the component to us + if (user && user._authData) { + const authType = this.authTypes[user._authData.authType]; + + const factory = resolver.resolveComponentFactory(authType.component); + + const ref = factory.create(injector); + + const form = fb.group({ + authType: authType.value, + systemShared: false, + authValues: fb.group(user._authData.values) + }); + + ref.instance.formGroup = form; + + // Allow the auth form to supply body content if it needs to + const endpointFormInstance = ref.instance as any; + if (endpointFormInstance.getBody && endpointFormInstance.getValues) { + data = { + authType: authType.value, + authVal: endpointFormInstance.getValues(user._authData.values), + systemShared: false, + bodyContent: endpointFormInstance.getBody() + }; + } + ref.destroy(); + } + return data; + } +} diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.html b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.html new file mode 100644 index 0000000000..b78c35d87c --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.scss b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.scss new file mode 100644 index 0000000000..05510cdc8c --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.scss @@ -0,0 +1,8 @@ +:host { + display: flex; + flex: 1; +} + +.kubeconfig-import { + flex: 1; +} \ No newline at end of file diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.spec.ts new file mode 100644 index 0000000000..a006e2debc --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.spec.ts @@ -0,0 +1,29 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { KubernetesBaseTestModules } from '../../kubernetes.testing.module'; +import { KubeConfigImportComponent } from './kube-config-import.component'; + +describe('KubeConfigImportComponent', () => { + let component: KubeConfigImportComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + ...KubernetesBaseTestModules + ], + declarations: [KubeConfigImportComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubeConfigImportComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.ts new file mode 100644 index 0000000000..e5e95dffd5 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.ts @@ -0,0 +1,300 @@ +import { Component, ComponentFactoryResolver, Injector, OnDestroy } from '@angular/core'; +import { FormBuilder } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { BehaviorSubject, Observable, of as observableOf, Subscription } from 'rxjs'; +import { distinctUntilChanged, filter, first, map, pairwise, startWith, withLatestFrom } from 'rxjs/operators'; + +import { entityCatalog } from '../../../../../../store/src/entity-catalog/entity-catalog'; +import { safeUnsubscribe } from '../../../../core/utils.service'; +import { ITableColumn } from '../../../../shared/components/list/list-table/table.types'; +import { KUBERNETES_ENDPOINT_TYPE } from '../../kubernetes-entity-factory'; +import { KubeConfigAuthHelper } from '../kube-config-auth.helper'; +import { KubeConfigFileCluster, KubeConfigImportAction, KubeImportState } from '../kube-config.types'; +import { RegisterEndpoint } from './../../../../../../store/src/actions/endpoint.actions'; +import { AppState } from './../../../../../../store/src/app-state'; +import { EndpointsEffect } from './../../../../../../store/src/effects/endpoint.effects'; +import { endpointSchemaKey } from './../../../../../../store/src/helpers/entity-factory'; +import { ActionState } from './../../../../../../store/src/reducers/api-request-reducer/types'; +import { selectUpdateInfo } from './../../../../../../store/src/selectors/api.selectors'; +import { STRATOS_ENDPOINT_TYPE } from './../../../../base-entity-schemas'; +import { EndpointsService } from './../../../../core/endpoints.service'; +import { + ConnectEndpointConfig, + ConnectEndpointData, + ConnectEndpointService, +} from './../../../../features/endpoints/connect.service'; +import { + ITableListDataSource, + RowState, +} from './../../../../shared/components/list/data-sources-controllers/list-data-source-types'; +import { StepOnNextFunction } from './../../../../shared/components/stepper/step/step.component'; +import { + KubeConfigTableImportStatusComponent, +} from './kube-config-table-import-status/kube-config-table-import-status.component'; + +const REGISTER_ACTION = 'Register endpoint'; +const CONNECT_ACTION = 'Connect endpoint'; + +@Component({ + selector: 'app-kube-config-import', + templateUrl: './kube-config-import.component.html', + styleUrls: ['./kube-config-import.component.scss'] +}) +export class KubeConfigImportComponent implements OnDestroy { + + done = new BehaviorSubject(false); + done$ = this.done.asObservable(); + busy = new BehaviorSubject(false); + busy$ = this.busy.asObservable(); + data = new BehaviorSubject([]); + data$ = this.data.asObservable(); + + public dataSource: ITableListDataSource = { + connect: () => this.data$, + disconnect: () => { }, + // Ensure unique per entry to step (in case user went back step and updated) + trackBy: (index, item) => item.cluster.name + this.iteration, + isTableLoading$: this.data$.pipe(map(data => !(data && data.length > 0))), + getRowState: (row: KubeConfigImportAction): Observable => { + return row ? row.state.asObservable() : observableOf({}); + } + }; + public columns: ITableColumn[] = [ + { + columnId: 'action', headerCell: () => 'Action', + cellDefinition: { + valuePath: 'action' + }, + cellFlex: '1', + }, + { + columnId: 'description', headerCell: () => 'Description', + cellDefinition: { + valuePath: 'description' + }, + cellFlex: '4', + }, + // Right-hand column to show the action progress + { + columnId: 'monitorState', + cellComponent: KubeConfigTableImportStatusComponent, + cellConfig: (row) => row.actionState.asObservable(), + cellFlex: '0 0 24px' + } + ]; + + subs: Subscription[] = []; + applyStarted: boolean; + private iteration = 0; + + private endpointEntityKey = entityCatalog.getEntityKey(STRATOS_ENDPOINT_TYPE, endpointSchemaKey); + private connectService: ConnectEndpointService; + + constructor( + public store: Store, + public resolver: ComponentFactoryResolver, + private injector: Injector, + private fb: FormBuilder, + private endpointsService: EndpointsService, + ) { + } + + // Process the next action in the list + private processAction(actions: KubeConfigImportAction[]) { + if (actions.length === 0) { + // We are done + this.done.next(true); + this.busy.next(false); + return; + } + + // Get the next action + const i = actions.shift(); + if (i.action === REGISTER_ACTION) { + this.doRegister(i, actions); + } else if (i.action === CONNECT_ACTION) { + this.doConnect(i, actions); + } else { + // Do the next action + this.processAction(actions); + } + } + + private doRegister(reg: KubeConfigImportAction, next: KubeConfigImportAction[]) { + const obs$ = this.registerEndpoint(reg.cluster.name, reg.cluster.cluster.server, reg.cluster.cluster['insecure-skip-tls-verify']); + const mainObs$ = this.getUpdatingState(obs$).pipe( + startWith({ busy: true, error: false, completed: false }) + ); + + this.subs.push(mainObs$.subscribe(reg.actionState)); + + const sub = reg.actionState.subscribe(progress => { + // Not sure what the status is used for? + reg.status = progress; + if (progress.error && progress.message) { + // Mark all dependency jobs as skip + next.forEach(action => { + if (action.depends === reg) { + // Mark it as skipped by setting the action to null + action.action = null; + action.state.next({ message: 'Skipping action as endpoint could not be registered', warning: true }); + } + }); + reg.state.next({ message: progress.message, error: true }); + } + if (progress.completed) { + if (!progress.error) { + // If we created okay, then guid is in the message + reg.cluster._guid = progress.message; + } + sub.unsubscribe(); + // Do the next one + this.processAction(next); + } + }); + this.subs.push(sub); + } + + private doConnect(connect: KubeConfigImportAction, next: KubeConfigImportAction[]) { + if (!connect.user) { + return; + } + const helper = new KubeConfigAuthHelper(); + const data = helper.getAuthDataForConnect(this.resolver, this.injector, this.fb, connect.user); + if (data) { + const obs$ = this.connectEndpoint(connect, data); + + // Echo obs$ to the behaviour subject + this.subs.push(obs$.subscribe(connect.actionState)); + + this.subs.push(connect.actionState.pipe(filter(status => status.completed), first()).subscribe(status => { + if (status.error) { + connect.state.next({ message: status.errorMessage || status.message, error: true }); + } + this.processAction(next); + })); + } + } + + ngOnDestroy() { + safeUnsubscribe(...this.subs); + + if (this.connectService) { + this.connectService.destroy(); + } + } + + // Register the endpoint + private registerEndpoint(name: string, url: string, skipSslValidation: boolean) { + const action = new RegisterEndpoint(KUBERNETES_ENDPOINT_TYPE, null, name, url, skipSslValidation, '', '', false); + this.store.dispatch(action); + const update$ = this.store.select( + selectUpdateInfo(this.endpointEntityKey, action.guid(), EndpointsEffect.registeringKey) + ).pipe(filter(update => !!update)); + return update$; + } + + // Connect to an endpoint + private connectEndpoint(action: KubeConfigImportAction, pData: ConnectEndpointData) { + const config: ConnectEndpointConfig = { + name: action.cluster.name, + guid: action.depends.cluster._guid || action.cluster._guid, + type: null, + subType: action.user._authData.subType, + ssoAllowed: false + }; + + if (this.connectService) { + this.connectService.destroy(); + } + this.connectService = new ConnectEndpointService(this.store, this.endpointsService, config); + this.connectService.setData(pData); + this.connectService.submit(); + return this.connectService.getConnectingObservable(); + } + + // Enter the step - process the list of clusters to import + onEnter = (data: KubeConfigFileCluster[]) => { + this.applyStarted = false; + this.iteration += 1; + const imports: KubeConfigImportAction[] = []; + data.forEach(item => { + if (item._selected) { + const register = { + action: REGISTER_ACTION, + description: `Register "${item.name}" with the URL "${item.cluster.server}"`, + cluster: item, + state: new BehaviorSubject({}), + actionState: new BehaviorSubject({}), + }; + // Only include if the endpoint does not already exist + if (!item._guid) { + imports.push(register); + } + if (item._additionalUserInfo) { + return; + } + const user = item._users.find(u => u.name === item._user); + if (user) { + imports.push({ + action: CONNECT_ACTION, + description: `Connect "${item.name}" with the user "${user.name}"`, + cluster: item, + user, + state: new BehaviorSubject({}), + depends: register, + actionState: new BehaviorSubject({}), + }); + } + } + }); + this.data.next(imports); + } + + // Finish - go back to the endpoints view + onNext: StepOnNextFunction = () => { + if (this.applyStarted) { + // this.store.dispatch(new RouterNav({ path: ['endpoints'] })); + return observableOf({ success: true, redirect: true }); + + } else { + this.applyStarted = true; + this.busy.next(true); + this.data$.pipe( + filter((data => data && data.length > 0)), + first() + ).subscribe(imports => { + // Go through the imports and dispatch the actions to perform them in sequence + this.processAction([...imports]); + }) + return observableOf({ success: true, ignoreSuccess: true }); + } + } + + // These two should be somewhere else + private getUpdatingState(actionState$: Observable): Observable { + const completed$ = this.getHasCompletedObservable(actionState$.pipe(map(requestState => requestState.busy))); + return actionState$.pipe( + pairwise(), + withLatestFrom(completed$), + map(([[, requestState], completed]) => { + return { + busy: requestState.busy, + error: requestState.error, + completed, + message: requestState.message, + }; + }) + ); + } + + private getHasCompletedObservable(busy$: Observable) { + return busy$.pipe( + distinctUntilChanged(), + pairwise(), + map(([oldBusy, newBusy]) => oldBusy && !newBusy), + startWith(false), + ); + } + +} diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.html b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.html new file mode 100644 index 0000000000..e4b06461a1 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.html @@ -0,0 +1 @@ + diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.scss b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.spec.ts new file mode 100644 index 0000000000..6d2263d935 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.spec.ts @@ -0,0 +1,29 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; +import { KubeConfigTableImportStatusComponent } from './kube-config-table-import-status.component'; + +describe('KubeConfigTableImportStatusComponent', () => { + let component: KubeConfigTableImportStatusComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + ...KubernetesBaseTestModules + ], + declarations: [KubeConfigTableImportStatusComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubeConfigTableImportStatusComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.ts new file mode 100644 index 0000000000..08bd785052 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.ts @@ -0,0 +1,26 @@ +import { Component, Input } from '@angular/core'; +import { Observable } from 'rxjs'; + +import { TableCellCustom } from '../../../../../shared/components/list/list.types'; +import { KubeConfigFileCluster } from '../../kube-config.types'; + +@Component({ + selector: 'app-kube-config-table-import-status', + templateUrl: './kube-config-table-import-status.component.html', + styleUrls: ['./kube-config-table-import-status.component.scss'] +}) +export class KubeConfigTableImportStatusComponent extends TableCellCustom { + + public state: Observable; + + constructor() { + super(); + } + + @Input() + set config(element) { + if (!this.state) { + this.state = element(this.row); + } + } +} diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-registration.component.html b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-registration.component.html new file mode 100644 index 0000000000..6611b3a683 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-registration.component.html @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-registration.component.scss b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-registration.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-registration.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-registration.component.spec.ts new file mode 100644 index 0000000000..bfcf2b0024 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-registration.component.spec.ts @@ -0,0 +1,35 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { KubernetesBaseTestModules } from '../kubernetes.testing.module'; +import { KubeConfigImportComponent } from './kube-config-import/kube-config-import.component'; +import { KubeConfigRegistrationComponent } from './kube-config-registration.component'; +import { KubeConfigSelectionComponent } from './kube-config-selection/kube-config-selection.component'; + +describe('KubeConfigRegistrationComponent', () => { + let component: KubeConfigRegistrationComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + ...KubernetesBaseTestModules + ], + declarations: [ + KubeConfigRegistrationComponent, + KubeConfigSelectionComponent, + KubeConfigImportComponent + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubeConfigRegistrationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-registration.component.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-registration.component.ts new file mode 100644 index 0000000000..1708ec7897 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-registration.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-kube-config-registration', + templateUrl: './kube-config-registration.component.html', + styleUrls: ['./kube-config-registration.component.scss'] +}) +export class KubeConfigRegistrationComponent { } diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.html b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.html new file mode 100644 index 0000000000..77ce84fc8a --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.html @@ -0,0 +1,17 @@ +
+
+ insert_drive_file +

Select a Kube Config file to import clusters

+
+ + +
+
+
+ + +
+ +
+
\ No newline at end of file diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.scss b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.scss new file mode 100644 index 0000000000..df0fad3cd0 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.scss @@ -0,0 +1,35 @@ +:host { + display: flex; + flex: 1; +} + +.kube-config-select { + &__panel { + align-items: center; + display: flex; + flex: 1; + justify-content: center; + } + &__upload { + text-align: center; + } + &__title { + font-size: 24px; + text-align: center; + } + &__icon { + font-size: 96px; + height: 96px; + opacity: .7; + width: 96px; + } + &__table { + flex: 1; + } + &__buttons { + padding-bottom: 12px; + button { + margin-right: 24px; + } + } +} \ No newline at end of file diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.spec.ts new file mode 100644 index 0000000000..cc646004a4 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.spec.ts @@ -0,0 +1,29 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { KubernetesBaseTestModules } from '../../kubernetes.testing.module'; +import { KubeConfigSelectionComponent } from './kube-config-selection.component'; + +describe('KubeConfigSelectionComponent', () => { + let component: KubeConfigSelectionComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + ...KubernetesBaseTestModules + ], + declarations: [KubeConfigSelectionComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubeConfigSelectionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.ts new file mode 100644 index 0000000000..4a89f59baf --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.ts @@ -0,0 +1,210 @@ +import { Component, Input } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { BehaviorSubject, combineLatest, Observable, of as observableOf, of } from 'rxjs'; +import { first, map, switchMap } from 'rxjs/operators'; + +import { HideSnackBar, ShowSnackBar } from '../../../../../../store/src/actions/snackBar.actions'; +import { AppState } from '../../../../../../store/src/app-state'; +import { + TableHeaderSelectComponent, +} from '../../../../shared/components/list/list-table/table-header-select/table-header-select.component'; +import { KubeConfigHelper } from '../kube-config.helper'; +import { KubeConfigFileCluster } from '../kube-config.types'; +import { + ITableListDataSource, + RowState, +} from './../../../../shared/components/list/data-sources-controllers/list-data-source-types'; +import { ITableColumn } from './../../../../shared/components/list/list-table/table.types'; +import { KubeConfigTableCertComponent } from './kube-config-table-cert/kube-config-table-cert.component'; +import { KubeConfigTableName } from './kube-config-table-name/kube-config-table-name.component'; +import { KubeConfigTableSelectComponent } from './kube-config-table-select/kube-config-table-select.component'; +import { + KubeConfigTableSubTypeSelectComponent, +} from './kube-config-table-sub-type-select/kube-config-table-sub-type-select.component'; +import { KubeConfigTableUserSelectComponent } from './kube-config-table-user-select/kube-config-table-user-select.component'; + +export interface KubeConfigTableListDataSource extends ITableListDataSource { + editRowName: string; +} + +@Component({ + selector: 'app-kube-config-selection', + templateUrl: './kube-config-selection.component.html', + styleUrls: ['./kube-config-selection.component.scss'], + providers: [ + KubeConfigHelper + ], +}) +export class KubeConfigSelectionComponent { + + @Input() applyStarted: boolean; + public dataSource: KubeConfigTableListDataSource = { + connect: () => this.helper.clusters$, + disconnect: () => { }, + trackBy: (index, row) => row.name, + isTableLoading$: observableOf(false), + getRowState: (row: KubeConfigFileCluster, schemaKey: string): Observable => { + return row ? row._state.asObservable() : observableOf({}); + }, + selectAllIndeterminate: false, + selectAllChecked: false, + selectAllFilteredRows: () => { + // Should always go to true from indeterminate + this.dataSource.selectAllChecked = this.dataSource.selectAllIndeterminate ? true : !this.dataSource.selectAllChecked + this.dataSource.selectAllIndeterminate = false; // either all off or all on, cannot be indeterminate + + this.helper.clusters$.pipe( + first(), + switchMap(clusters => combineLatest(clusters.map(cluster => { + if (!cluster._invalid) { + cluster._selected = this.dataSource.selectAllChecked; + return this.helper.checkValidity(cluster).pipe(map(() => cluster)); + } + return of(cluster); + }))), + first(), + ).subscribe(clusters => { + this.checkCanGoNext(clusters); + }) + }, + editRow: null, + editRowName: null, + startEdit: (c: KubeConfigFileCluster) => { + this.dataSource.editRow = c; + }, + saveEdit: () => { + this.dataSource.editRow.name = this.dataSource.editRowName; + this.helper.update(this.dataSource.editRow); + delete this.dataSource.editRowName; + delete this.dataSource.editRow; + }, + cancelEdit: () => { + delete this.dataSource.editRowName; + delete this.dataSource.editRow; + }, + getRowUniqueId: (c: KubeConfigFileCluster) => c ? c._id : null + }; + + public columns: ITableColumn[] = [ + { + columnId: 'select', + headerCellComponent: TableHeaderSelectComponent, + cellComponent: KubeConfigTableSelectComponent, + class: 'table-column-select', + cellFlex: '0 0 48px' + }, + { + columnId: 'name', headerCell: () => 'Name', + cellComponent: KubeConfigTableName, + cellFlex: '3', + class: 'app-table__cell--table-no-v-padding' + }, + { + columnId: 'url', headerCell: () => 'URL', + cellDefinition: { + valuePath: 'cluster.server' + }, + cellFlex: '4', + }, + { + columnId: 'type', headerCell: () => 'Type', + cellFlex: '1', + cellComponent: KubeConfigTableSubTypeSelectComponent + }, + { + columnId: 'user', headerCell: () => 'User', + cellFlex: '4', + cellComponent: KubeConfigTableUserSelectComponent + }, + { + columnId: 'cert', headerCell: () => 'Skip SSL Validation', + cellFlex: '0 0 62px', + class: 'app-table__cell--table-centred', + cellComponent: KubeConfigTableCertComponent + } + ]; + + // Is the import data valid? + valid = new BehaviorSubject(false); + valid$ = this.valid.asObservable(); + + canSetIntermediate = false; + + constructor( + private store: Store, + public helper: KubeConfigHelper + ) { + this.helper.clustersChanged = () => this.clustersChanged() + } + + // Save data for the next step to know the list of clusters to import + onNext = () => this.helper.clusters$.pipe( + first(), + map(clusters => ({ + success: true, + data: clusters + })) + ) + + clustersParse(cluster: string) { + this.store.dispatch(new HideSnackBar()); + this.helper.parse(cluster).pipe(first()).subscribe(errorString => { + if (errorString) { + this.store.dispatch(new ShowSnackBar(`Failed to load Kube Config: ${errorString}`, 'Close')) + } + }) + } + + onEnter = () => { + if (!this.applyStarted) { + return; + } + // Handle back from review step (ensure newly registered endpoints are taken into account) + this.helper.updateAll().pipe(first()).subscribe(() => { }) + } + + // Row changed event - update the next button and selection state + clustersChanged() { + this.helper.clusters$.pipe( + first() + ).subscribe(clusters => { + this.checkCanGoNext(clusters); + + // Check the select all state + let selectedCount = 0; + let totalCount = 0; + clusters.forEach(i => { + if (!i._invalid) { + totalCount++; + selectedCount += i._selected ? 1 : 0; + } + }); + + if (selectedCount === 0 || totalCount === selectedCount) { + this.dataSource.selectAllIndeterminate = false; + this.dataSource.selectAllChecked = (selectedCount !== 0); + } else { + this.dataSource.selectAllIndeterminate = true; + } + }) + + } + + // Can we proceed? + checkCanGoNext(clusters: KubeConfigFileCluster[]) { + let selected = 0; + let okay = 0; + clusters.forEach(i => { + if (i._selected) { + selected++; + if (!i._invalid) { + okay++; + } + } + }); + + // Must be at least one selected and they all must be okay to import + this.valid.next(selected > 0 && selected === okay); + } + +} diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.html b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.html new file mode 100644 index 0000000000..5d0a8d6faf --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.html @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.scss b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.spec.ts new file mode 100644 index 0000000000..0b1bdaf8b8 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.spec.ts @@ -0,0 +1,33 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; +import { KubeConfigHelper } from '../../kube-config.helper'; +import { KubeConfigTableCertComponent } from './kube-config-table-cert.component'; + +describe('KubeConfigTableCertComponent', () => { + let component: KubeConfigTableCertComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + ...KubernetesBaseTestModules + ], + declarations: [KubeConfigTableCertComponent], + providers: [ + KubeConfigHelper + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubeConfigTableCertComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.ts new file mode 100644 index 0000000000..c776a3d0bf --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.ts @@ -0,0 +1,73 @@ +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; +import { Component, Input } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; +import { timeout } from 'rxjs/operators'; + +import { TableCellCustom } from '../../../../../shared/components/list/list.types'; +import { KubeConfigHelper } from '../../kube-config.helper'; +import { KubeConfigFileCluster } from '../../kube-config.types'; + +type CertResponse = { + Status: number; + Required: boolean; + Error: boolean; + Message: string; +} + +@Component({ + selector: 'app-kube-config-table-cert', + templateUrl: './kube-config-table-cert.component.html', + styleUrls: ['./kube-config-table-cert.component.scss'] +}) +export class KubeConfigTableCertComponent extends TableCellCustom { + + initialValue = new BehaviorSubject<{ + checked: boolean + }>(null) + initialValue$ = this.initialValue.asObservable(); + + private pRow: KubeConfigFileCluster; + @Input() + set row(row: KubeConfigFileCluster) { + if (!this.pRow) { + this.pRow = row; + if (row.cluster['insecure-skip-tls-verify']) { + // User has manually specified default skip option + this.initialValue.next({ + checked: true + }); + } else { + // Manually check if a cert is required, if so tick by default + this.http.get(`/pp/v1/kube/cert?url=${row.cluster.server}`).pipe( + timeout(5000), + ).subscribe( + // Success, no cert required + (res: CertResponse) => this.update(res.Required), + // Failed, check for specific cert required error + (e: HttpErrorResponse) => this.update(false) + ) + } + } + } + get row(): KubeConfigFileCluster { + return this.pRow; + } + + constructor( + private helper: KubeConfigHelper, + private http: HttpClient + ) { + super() + } + + private update(checked: boolean) { + this.initialValue.next({ checked }); + this.valueChanged(checked); + } + + valueChanged(value) { + this.row.cluster['insecure-skip-tls-verify'] = value; + this.helper.update(this.row); + } + +} diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.html b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.html new file mode 100644 index 0000000000..39cdc6abec --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.html @@ -0,0 +1,9 @@ +
+ + + + {{row.name}} + +
\ No newline at end of file diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.scss b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.scss new file mode 100644 index 0000000000..1d85cadb70 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.scss @@ -0,0 +1,12 @@ +.name { + align-items: center; + display: flex; + + .cell-edit-variable { + flex: 1; + } + + app-table-cell-edit { + margin-bottom: 3px; + } +} diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.spec.ts new file mode 100644 index 0000000000..c37211591f --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { IListDataSource } from '../../../../../shared/components/list/data-sources-controllers/list-data-source-types'; +import { KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; +import { KubeConfigFileCluster } from '../../kube-config.types'; +import { KubeConfigTableName } from './kube-config-table-name.component'; + +describe('KubeConfigTableName', () => { + let component: KubeConfigTableName; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + ...KubernetesBaseTestModules + ], + declarations: [KubeConfigTableName] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubeConfigTableName); + component = fixture.componentInstance; + component.dataSource = { + getRowUniqueId: (row) => "" + } as IListDataSource + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.ts new file mode 100644 index 0000000000..576f4c19f2 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +import { TableCellCustom } from '../../../../../shared/components/list/list.types'; +import { KubeConfigFileCluster } from '../../kube-config.types'; + +@Component({ + selector: 'app-kube-config-table-name', + templateUrl: './kube-config-table-name.component.html', + styleUrls: ['./kube-config-table-name.component.scss'] +}) +export class KubeConfigTableName extends TableCellCustom { } diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-select/kube-config-table-select.component.html b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-select/kube-config-table-select.component.html new file mode 100644 index 0000000000..944326f9e6 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-select/kube-config-table-select.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-select/kube-config-table-select.component.scss b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-select/kube-config-table-select.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-select/kube-config-table-select.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-select/kube-config-table-select.component.spec.ts new file mode 100644 index 0000000000..3bf72f8139 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-select/kube-config-table-select.component.spec.ts @@ -0,0 +1,35 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; +import { KubeConfigHelper } from '../../kube-config.helper'; +import { KubeConfigFileCluster } from '../../kube-config.types'; +import { KubeConfigTableSelectComponent } from './kube-config-table-select.component'; + +describe('KubeConfigTableSelectComponent', () => { + let component: KubeConfigTableSelectComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + ...KubernetesBaseTestModules + ], + declarations: [KubeConfigTableSelectComponent], + providers: [ + KubeConfigHelper + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubeConfigTableSelectComponent); + component = fixture.componentInstance; + component.row = {} as KubeConfigFileCluster; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-select/kube-config-table-select.component.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-select/kube-config-table-select.component.ts new file mode 100644 index 0000000000..b13745ed48 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-select/kube-config-table-select.component.ts @@ -0,0 +1,22 @@ +import { Component } from '@angular/core'; + +import { TableCellCustom } from '../../../../../shared/components/list/list.types'; +import { KubeConfigHelper } from '../../kube-config.helper'; +import { KubeConfigFileCluster } from '../../kube-config.types'; + +@Component({ + selector: 'app-kube-config-table-select', + templateUrl: './kube-config-table-select.component.html', + styleUrls: ['./kube-config-table-select.component.scss'] +}) +export class KubeConfigTableSelectComponent extends TableCellCustom { + + constructor(private helper: KubeConfigHelper) { + super(); + } + changed(v) { + this.row._selected = v.checked; + this.helper.update(this.row); + } + +} diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-sub-type-select/kube-config-table-sub-type-select.component.html b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-sub-type-select/kube-config-table-sub-type-select.component.html new file mode 100644 index 0000000000..92a8d0e90e --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-sub-type-select/kube-config-table-sub-type-select.component.html @@ -0,0 +1,3 @@ + + {{ type.name }} + diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-sub-type-select/kube-config-table-sub-type-select.component.scss b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-sub-type-select/kube-config-table-sub-type-select.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-sub-type-select/kube-config-table-sub-type-select.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-sub-type-select/kube-config-table-sub-type-select.component.spec.ts new file mode 100644 index 0000000000..c2ce574749 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-sub-type-select/kube-config-table-sub-type-select.component.spec.ts @@ -0,0 +1,35 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; +import { KubeConfigHelper } from '../../kube-config.helper'; +import { KubeConfigFileCluster } from '../../kube-config.types'; +import { KubeConfigTableSubTypeSelectComponent } from './kube-config-table-sub-type-select.component'; + +describe('KubeConfigTableSubTypeSelectComponent', () => { + let component: KubeConfigTableSubTypeSelectComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + ...KubernetesBaseTestModules + ], + declarations: [KubeConfigTableSubTypeSelectComponent], + providers: [ + KubeConfigHelper + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubeConfigTableSubTypeSelectComponent); + component = fixture.componentInstance; + component.row = {} as KubeConfigFileCluster; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-sub-type-select/kube-config-table-sub-type-select.component.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-sub-type-select/kube-config-table-sub-type-select.component.ts new file mode 100644 index 0000000000..8d487d1bc6 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-sub-type-select/kube-config-table-sub-type-select.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit } from '@angular/core'; + +import { TableCellCustom } from '../../../../../../../core/src/shared/components/list/list.types'; +import { KubeConfigAuthHelper } from '../../kube-config-auth.helper'; +import { KubeConfigHelper } from '../../kube-config.helper'; +import { KubeConfigFileCluster } from '../../kube-config.types'; + +@Component({ + selector: 'app-kube-config-table-sub-type-select', + templateUrl: './kube-config-table-sub-type-select.component.html', + styleUrls: ['./kube-config-table-sub-type-select.component.scss'] +}) +export class KubeConfigTableSubTypeSelectComponent extends TableCellCustom implements OnInit { + + selected: string; + + subTypes: string[]; + + constructor(private helper: KubeConfigHelper) { + super(); + + this.subTypes = new KubeConfigAuthHelper().subTypes; + } + + ngOnInit() { + this.selected = this.row._subType || ''; + } + + valueChanged(value) { + this.row._subType = value; + this.helper.update(this.row); + } +} diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-user-select/kube-config-table-user-select.component.html b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-user-select/kube-config-table-user-select.component.html new file mode 100644 index 0000000000..1c1c629a8a --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-user-select/kube-config-table-user-select.component.html @@ -0,0 +1,9 @@ +
+ + Register Only + {{ user.name }} + +
+
+ No user found, register only +
\ No newline at end of file diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-user-select/kube-config-table-user-select.component.scss b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-user-select/kube-config-table-user-select.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-user-select/kube-config-table-user-select.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-user-select/kube-config-table-user-select.component.spec.ts new file mode 100644 index 0000000000..3ada12009f --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-user-select/kube-config-table-user-select.component.spec.ts @@ -0,0 +1,39 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; +import { KubeConfigHelper } from '../../kube-config.helper'; +import { KubeConfigFileCluster } from '../../kube-config.types'; +import { KubeConfigTableUserSelectComponent } from './kube-config-table-user-select.component'; + +describe('KubeConfigTableUserSelectComponent', () => { + let component: KubeConfigTableUserSelectComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + ...KubernetesBaseTestModules + ], + declarations: [ + KubeConfigTableUserSelectComponent, + ], + providers: [ + KubeConfigHelper + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubeConfigTableUserSelectComponent); + component = fixture.componentInstance; + component.row = { + _users: [] + } as KubeConfigFileCluster; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-user-select/kube-config-table-user-select.component.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-user-select/kube-config-table-user-select.component.ts new file mode 100644 index 0000000000..f39471a321 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-user-select/kube-config-table-user-select.component.ts @@ -0,0 +1,31 @@ +import { Component, OnInit } from '@angular/core'; + +import { TableCellCustom } from '../../../../../shared/components/list/list.types'; +import { KubeConfigHelper } from '../../kube-config.helper'; +import { KubeConfigFileCluster } from '../../kube-config.types'; + +@Component({ + selector: 'app-kube-config-table-user-select', + templateUrl: './kube-config-table-user-select.component.html', + styleUrls: ['./kube-config-table-user-select.component.scss'] +}) +export class KubeConfigTableUserSelectComponent extends TableCellCustom implements OnInit { + + hasUser = false; + selected: string; + + constructor(private helper: KubeConfigHelper) { + super(); + } + + ngOnInit() { + this.selected = this.row._user || ''; + this.hasUser = this.row._users.length > 0; + } + + valueChanged(value) { + this.row._user = value; + this.helper.update(this.row); + } + +} diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config.helper.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config.helper.ts new file mode 100644 index 0000000000..645505ea91 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config.helper.ts @@ -0,0 +1,201 @@ +import { Injectable } from '@angular/core'; +import * as yaml from 'js-yaml'; +import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs'; +import { filter, first, map, tap } from 'rxjs/operators'; + +import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; +import { EndpointsService } from '../../../core/endpoints.service'; +import { createGuid } from '../../../core/utils.service'; +import { getFullEndpointApiUrl } from '../../../features/endpoints/endpoint-helpers'; +import { RowState } from '../../../shared/components/list/data-sources-controllers/list-data-source-types'; +import { KubeConfigAuthHelper } from './kube-config-auth.helper'; +import { KubeConfigFile, KubeConfigFileCluster } from './kube-config.types'; + +/** + * Helper to parse the kubeconfig and transform it into data + * that we can display in a table for selection + * + * Main issue is we only support one credential per endpoint, so need to format the data + * to offer the user ability to select which user to import + */ +@Injectable() +export class KubeConfigHelper { + + authHelper = new KubeConfigAuthHelper(); + + clusters = new BehaviorSubject(null) + clusters$ = this.clusters.asObservable().pipe( + filter(clusters => !!clusters) + ); + + constructor( + public endpointsService: EndpointsService, + ) { + } + + public clustersChanged: () => void; + public update = (cluster: KubeConfigFileCluster) => { + this.checkValidity(cluster).subscribe(() => this.clustersChanged()); + } + + public updateAll(): Observable { + return this.clusters$.pipe( + tap(clusters => clusters.forEach(cluster => this.update(cluster))), + ) + } + + public parse(config: string): Observable { + let doc: KubeConfigFile; + + const clusters: { [name: string]: KubeConfigFileCluster } = {}; + + try { + doc = yaml.safeLoad(config); + } catch (e) { + return of(`${e}`); + } + + // Need contexts, users and clusters + if (!doc || !doc.contexts || !doc.users || !doc.clusters) { + return of(`Configuration must have contexts, users and clusters`); + } + + // Go through all of the contexts and find the clusters + doc.contexts.forEach(ctx => { + const cluster = doc.clusters.find(item => item.name === ctx.context.cluster); + if (cluster) { + // Found the cluster + if (!clusters[cluster.name]) { + const clstr = { + ...cluster, + _users: [] + }; + clusters[cluster.name] = clstr; + clstr._state = new BehaviorSubject({}); + } + + // Get the user + const user = doc.users.find(item => item.name === ctx.context.user); + if (user) { + // Check we don't already have this user (remove duplicates) + const users = clusters[cluster.name]._users; + if (users.findIndex(usr => usr.name === user.name) === -1) { + clusters[cluster.name]._users.push(user); + if (ctx.name === doc['current-context']) { + // Auto-select this cluster/user if it is the current context + clusters[cluster.name]._user = user.name; + clusters[cluster.name]._selected = true; + } + } + } + } + }); + + // Go through all clusters, auto-select the user where this is only 1 and check validity + const clustersArray = Object.values(clusters); + clustersArray.forEach(cluster => { + if (cluster._users.length >= 1) { + cluster._user = cluster._users[0].name; + } + cluster._id = createGuid(); + }); + + // Check validity + return combineLatest( + clustersArray.map(cluster => this.checkValidity(cluster)) + ).pipe( + map(() => { + // Notify cluster changes + this.clustersChanged(); + this.clusters.next(Object.values(clusters)); + return ''; + }) + ); + } + + + // Check the validity of a cluster for import + public checkValidity(cluster: KubeConfigFileCluster): Observable { + // Check endpoint name + return combineLatest([ + this.endpointsService.endpoints$, + this.clusters.asObservable() // Might be called before we've loaded clusters, so used the non-filtered one + ]).pipe( + first(), + map(([eps, clusters]) => this.validate(Object.values(eps), cluster, clusters)) + ); + } + + private validate(endpoints: EndpointModel[], cluster: KubeConfigFileCluster, clusters: KubeConfigFileCluster[]) { + cluster._invalid = false; + let reset = true; + + const found = endpoints.find(item => item.name === cluster.name); + if (found) { + // If the URL is the same, then we will just connect to the existing endpoint + if (getFullEndpointApiUrl(found) === cluster.cluster.server && !!cluster._user) { + cluster._guid = found.guid; + cluster._state.next({ + message: 'This endpoint will be connected and not registered (endpoint is already registered)', + info: true + }); + reset = false; + } else { + // An endpoint with the same name (but different URL) already exists + cluster._invalid = true; + cluster._state.next({ message: 'An endpoint with this name already exists', warning: true }); + } + } else { + // Check endpoint url is not registered with a different name + if (endpoints.find(item => getFullEndpointApiUrl(item) === cluster.cluster.server)) { + cluster._invalid = true; + cluster._state.next({ message: 'An endpoint with this URL already exists', warning: true }); + } + } + + // Check the connection details + if (!cluster._invalid && cluster._user) { + const user = cluster._users.find(item => item.name === cluster._user); + if (user) { + const newState = this.authHelper.parseAuth(cluster, user); + if (!!newState && !!newState.message) { + reset = false; + cluster._invalid = newState.error || newState.warning + cluster._state.next(newState); + } + } + } + + // Register only (_additionalUserInfo.. specific to text warning) is true + // Connect only (endpoint exists) is true + // Show special warning + if (cluster._additionalUserInfo && cluster._guid) { + cluster._invalid = true; + reset = true; + cluster._state.next({ + message: 'This endpoint will not be registered or connected (endpoint is already registered, additional information required to connect)', + warning: true + }); + } + + if (clusters && !!clusters.find(candidate => candidate.name === cluster.name && candidate._id !== cluster._id)) { + cluster._invalid = true; + cluster._state.next({ message: 'An endpoint with this name already exists in the config file', warning: true }); + } + + if (!cluster.name) { + cluster._invalid = true; + cluster._state.next({ message: 'Cluster must have name', warning: true }); + } + + // Cluster is valid, so clear any warning or error message + if (!cluster._invalid && reset) { + cluster._state.next({}); + } + + // Ensure invalid rows aren't selected (user cannot unselect invalid rows) + if (cluster._invalid) { + cluster._selected = false; + } + } +} diff --git a/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config.types.ts b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config.types.ts new file mode 100644 index 0000000000..f2d8b7abde --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kube-config-registration/kube-config.types.ts @@ -0,0 +1,99 @@ +import { Observable, Subject } from 'rxjs'; + +import { EndpointAuthTypeConfig } from '../../../core/extension/extension-types'; +import { RowState } from '../../../shared/components/list/data-sources-controllers/list-data-source-types'; +import { ActionStatus } from './../../../../../store/src/reducers/api-request-reducer/types'; + +// Types for a Kubernetes Configuration file + +export interface KubeConfigFileCluster { + name: string; + cluster: { + 'certificate-authority': string; + 'certificate-authority-data': string; + 'insecure-skip-tls-verify': boolean; + server: string; + }; + // Selected user to import + _user: string; + _users: KubeConfigFileUser[]; + // _onUpdate: (row) => {}; + // Is the cluster selected for import? + _selected: boolean; + // Is this cluster invalid? i.e. requires more information + _invalid: boolean; + // row state + _state: Subject; + // status of import + _status: string; + // guid of the existing endpoint for this cluster + _guid: string; + // subtype + _subType?: string; + // additional info is required in order to connect, hints at register only, though is specific due to warning message + _additionalUserInfo: boolean; + // unique identifier + _id: string; +} + +export interface KubeConfigFileUser { + name: string; + user: KubeConfigFileUserDetail; + _authData: KubeConfigImportAuthConfig; +} + +export interface KubeConfigFileUserDetail { + 'client-certificate'?: string; + 'client-key'?: string; + 'client-certificate-data'?: string; + 'client-key-data'?: string; + token?: string; + exec?: any +} + +export interface KubeConfigFileContext { + name: string; + context: { + cluster: string; + user: string; + }; +} + +export interface KubeConfigFile { + apiVersion: string; + clusters: KubeConfigFileCluster[]; + contexts: KubeConfigFileContext[]; + 'current-context': string; + kind: string; + users: KubeConfigFileUser[]; +} + +export interface KubeConfigImportAction { + action: string; + description: string; + cluster: KubeConfigFileCluster; + user?: KubeConfigFileUser; + status?: ActionStatus; + state: Subject; + actionState$?: Observable; + actionState: Subject; + depends?: KubeConfigImportAction; +} + +export interface KubeImportState { + busy: boolean; + error: boolean; + completed: boolean; + message: string; +} + +export interface EndpointConfig { + type: string; + authTypes: EndpointAuthTypeConfig[]; +} + +export interface KubeConfigImportAuthConfig { + subType: string; + authType: string; + values: { [key: string]: string }; +} diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-entity-generator.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-entity-generator.ts index eac9f993c0..15e0ea101b 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes-entity-generator.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-entity-generator.ts @@ -20,6 +20,7 @@ import { KubernetesConfigAuthFormComponent, } from './auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component'; import { KubernetesGKEAuthFormComponent } from './auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component'; +import { KubeConfigRegistrationComponent } from './kube-config-registration/kube-config-registration.component'; import { kubeEntityCatalog } from './kubernetes-entity-catalog'; import { KUBERNETES_ENDPOINT_TYPE, @@ -129,31 +130,44 @@ export function generateKubernetesEntities(): StratosBaseCatalogEntity[] { urlValidation: undefined, authTypes: [kubeAuthTypeMap[KubeEndpointAuthTypes.CERT_AUTH], kubeAuthTypeMap[KubeEndpointAuthTypes.CONFIG]], renderPriority: 4, - subTypes: [{ - type: 'caasp', - label: 'SUSE CaaS Platform', - authTypes: [kubeAuthTypeMap[KubeEndpointAuthTypes.CONFIG]], - logoUrl: '/core/assets/custom/caasp.png', - renderPriority: 5 - }, { - type: 'aks', - label: 'Azure AKS', - authTypes: [kubeAuthTypeMap[KubeEndpointAuthTypes.CONFIG_AZ]], - logoUrl: '/core/assets/custom/aks.svg', - renderPriority: 6 - }, { - type: 'eks', - label: 'Amazon EKS', - authTypes: [kubeAuthTypeMap[KubeEndpointAuthTypes.AWS_IAM]], - logoUrl: '/core/assets/custom/eks.svg', - renderPriority: 6 - }, { - type: 'gke', - label: 'Google Kubernetes Engine', - authTypes: [kubeAuthTypeMap[KubeEndpointAuthTypes.GKE]], - logoUrl: '/core/assets/custom/gke.svg', - renderPriority: 6 - }], + subTypes: [ + { + type: 'config', + label: 'Import Kubeconfig', + authTypes: [kubeAuthTypeMap[KubeEndpointAuthTypes.CONFIG]], + logoUrl: '/core/assets/custom/kube_import.png', + renderPriority: 3, + registrationComponent: KubeConfigRegistrationComponent, + }, + { + type: 'caasp', + label: 'SUSE CaaS Platform', + labelShort: 'CaaSP', + authTypes: [kubeAuthTypeMap[KubeEndpointAuthTypes.CONFIG]], + logoUrl: '/core/assets/custom/caasp.png', + renderPriority: 5, + }, { + type: 'aks', + label: 'Azure AKS', + labelShort: 'AKS', + authTypes: [kubeAuthTypeMap[KubeEndpointAuthTypes.CONFIG_AZ]], + logoUrl: '/core/assets/custom/aks.svg', + renderPriority: 6 + }, { + type: 'eks', + label: 'Amazon EKS', + labelShort: 'EKS', + authTypes: [kubeAuthTypeMap[KubeEndpointAuthTypes.AWS_IAM]], + logoUrl: '/core/assets/custom/eks.svg', + renderPriority: 6 + }, { + type: 'gke', + label: 'Google Kubernetes Engine', + labelShort: 'GKE', + authTypes: [kubeAuthTypeMap[KubeEndpointAuthTypes.GKE]], + logoUrl: '/core/assets/custom/gke.svg', + renderPriority: 6 + }], }; return [ generateEndpointEntity(endpointDefinition), diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes.module.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes.module.ts index ed05e9af79..0c2111ed22 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes.module.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes.module.ts @@ -1,4 +1,3 @@ -/* tslint:disable:max-line-length */ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { NgxChartsModule } from '@swimlane/ngx-charts'; @@ -93,6 +92,7 @@ import { KubernetesNodesTabComponent } from './tabs/kubernetes-nodes-tab/kuberne import { KubernetesPodsTabComponent } from './tabs/kubernetes-pods-tab/kubernetes-pods-tab.component'; import { KubernetesSummaryTabComponent } from './tabs/kubernetes-summary-tab/kubernetes-summary.component'; +/* tslint:disable:max-line-length */ /* tslint:enable */ @@ -146,7 +146,7 @@ import { KubernetesSummaryTabComponent } from './tabs/kubernetes-summary-tab/kub KubernetesResourceViewerComponent, KubeServiceCardComponent, KubedashConfigurationComponent, - KubernetesPodContainersComponent + KubernetesPodContainersComponent, ], providers: [ KubernetesService, @@ -170,7 +170,7 @@ import { KubernetesSummaryTabComponent } from './tabs/kubernetes-summary-tab/kub KubernetesPodStatusComponent, KubeServiceCardComponent, KubernetesResourceViewerComponent, - KubernetesPodContainersComponent + KubernetesPodContainersComponent, ], exports: [ KubernetesResourceViewerComponent diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes.setup.module.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes.setup.module.ts index 45f33e0292..03989028b4 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes.setup.module.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes.setup.module.ts @@ -14,6 +14,29 @@ import { KubernetesConfigAuthFormComponent, } from './auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component'; import { KubernetesGKEAuthFormComponent } from './auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component'; +import { KubeConfigImportComponent } from './kube-config-registration/kube-config-import/kube-config-import.component'; +import { + KubeConfigTableImportStatusComponent, +} from './kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component'; +import { KubeConfigRegistrationComponent } from './kube-config-registration/kube-config-registration.component'; +import { + KubeConfigSelectionComponent, +} from './kube-config-registration/kube-config-selection/kube-config-selection.component'; +import { + KubeConfigTableCertComponent, +} from './kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component'; +import { + KubeConfigTableName, +} from './kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component'; +import { + KubeConfigTableSelectComponent, +} from './kube-config-registration/kube-config-selection/kube-config-table-select/kube-config-table-select.component'; +import { + KubeConfigTableSubTypeSelectComponent, +} from './kube-config-registration/kube-config-selection/kube-config-table-sub-type-select/kube-config-table-sub-type-select.component'; +import { + KubeConfigTableUserSelectComponent, +} from './kube-config-registration/kube-config-selection/kube-config-table-user-select/kube-config-table-user-select.component'; import { kubeEntityCatalog } from './kubernetes-entity-catalog'; import { KUBERNETES_ENDPOINT_TYPE } from './kubernetes-entity-factory'; import { generateKubernetesEntities } from './kubernetes-entity-generator'; @@ -21,7 +44,6 @@ import { BaseKubeGuid } from './kubernetes-page.types'; import { KubernetesStoreModule } from './kubernetes.store.module'; import { KubernetesEndpointService } from './services/kubernetes-endpoint.service'; - @NgModule({ imports: [ EntityCatalogModule.forFeature(generateKubernetesEntities), @@ -35,6 +57,15 @@ import { KubernetesEndpointService } from './services/kubernetes-endpoint.servic KubernetesAWSAuthFormComponent, KubernetesConfigAuthFormComponent, KubernetesGKEAuthFormComponent, + KubeConfigRegistrationComponent, + KubeConfigSelectionComponent, + KubeConfigImportComponent, + KubeConfigTableSelectComponent, + KubeConfigTableUserSelectComponent, + KubeConfigTableImportStatusComponent, + KubeConfigTableSubTypeSelectComponent, + KubeConfigTableName, + KubeConfigTableCertComponent ], providers: [ BaseKubeGuid, @@ -45,6 +76,13 @@ import { KubernetesEndpointService } from './services/kubernetes-endpoint.servic KubernetesAWSAuthFormComponent, KubernetesConfigAuthFormComponent, KubernetesGKEAuthFormComponent, + KubeConfigRegistrationComponent, + KubeConfigTableSelectComponent, + KubeConfigTableUserSelectComponent, + KubeConfigTableImportStatusComponent, + KubeConfigTableSubTypeSelectComponent, + KubeConfigTableName, + KubeConfigTableCertComponent ] }) export class KubernetesSetupModule { diff --git a/custom-src/frontend/assets/custom/kube_import.png b/custom-src/frontend/assets/custom/kube_import.png new file mode 100644 index 0000000000000000000000000000000000000000..62d9f0bdd2e7037bb9f33580a7829e14dd93d0a1 GIT binary patch literal 13298 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 literal 0 HcmV?d00001 diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-variables/table-cell-edit-variable/table-cell-edit-variable.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-variables/table-cell-edit-variable/table-cell-edit-variable.component.html index 9b9fcb9301..3f634c71e4 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-variables/table-cell-edit-variable/table-cell-edit-variable.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-variables/table-cell-edit-variable/table-cell-edit-variable.component.html @@ -2,4 +2,5 @@ -{{row.value}} \ No newline at end of file +{{row.value}} \ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-variables/table-cell-edit-variable/table-cell-edit-variable.component.scss b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-variables/table-cell-edit-variable/table-cell-edit-variable.component.scss index eb9c8d43a8..6cd7d23bf0 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-variables/table-cell-edit-variable/table-cell-edit-variable.component.scss +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-variables/table-cell-edit-variable/table-cell-edit-variable.component.scss @@ -7,3 +7,7 @@ mat-form-field { .cell-edit-variable { width: 100%; } + +.cell-value-variable { + cursor: pointer; +} diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-quotas/cf-quotas-data-source.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-quotas/cf-quotas-data-source.service.ts index f2bf84af31..d5018ccb88 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-quotas/cf-quotas-data-source.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-quotas/cf-quotas-data-source.service.ts @@ -50,7 +50,7 @@ export class CfQuotasDataSourceService extends ListDataSource { map(requestInfo => ({ deleting: requestInfo.deleting.busy, error: requestInfo.deleting.error, - message: requestInfo.deleting.error ? `Failed to delete quota: ${requestInfo.message}` : null + message: requestInfo.deleting.error ? `Failed to delete quota: ${requestInfo.deleting.message}` : null })) ); }; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-quotas/cf-space-quotas-data-source.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-quotas/cf-space-quotas-data-source.service.ts index 4a880b804b..ccfb93879a 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-quotas/cf-space-quotas-data-source.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-quotas/cf-space-quotas-data-source.service.ts @@ -50,7 +50,7 @@ export class CfOrgSpaceQuotasDataSourceService extends ListDataSource ({ deleting: requestInfo.deleting.busy, error: requestInfo.deleting.error, - message: requestInfo.deleting.error ? `Failed to delete space quota: ${requestInfo.message}` : null + message: requestInfo.deleting.error ? `Failed to delete space quota: ${requestInfo.deleting.message}` : null })) ); }; diff --git a/src/frontend/packages/core/sass/_all-theme.scss b/src/frontend/packages/core/sass/_all-theme.scss index 4f657dbc93..9fc21b86f9 100644 --- a/src/frontend/packages/core/sass/_all-theme.scss +++ b/src/frontend/packages/core/sass/_all-theme.scss @@ -181,6 +181,6 @@ $side-nav-light-active: #484848; $warn: map-get($theme, warn); $primary: map-get($theme, primary); $white: #fff; // Use default palette for status - @return (success: map-get($mat-green, 500), warning: map-get($mat-orange, 500), danger: mat-color($warn), tentative: map-get($mat-grey, 500), busy: mat-color($primary), text: $white, ); + @return (success: map-get($mat-green, 500), warning: map-get($mat-orange, 500), danger: mat-color($warn), tentative: map-get($mat-grey, 500), busy: mat-color($primary), text: $white, info: map-get($mat-blue, 500)); } } diff --git a/src/frontend/packages/core/sass/components/mat-table.scss b/src/frontend/packages/core/sass/components/mat-table.scss index c623288894..6c0d902e78 100644 --- a/src/frontend/packages/core/sass/components/mat-table.scss +++ b/src/frontend/packages/core/sass/components/mat-table.scss @@ -12,7 +12,7 @@ $mat-table-header-paginator-font-size: 13px; .stratos { // Only put right padding between cells - .mat-cell, .mat-header-cell { + .mat-cell, mat-footer-cell, mat-header-cell { padding: 10px 10px 10px 0; } diff --git a/src/frontend/packages/core/src/core/utils.service.ts b/src/frontend/packages/core/src/core/utils.service.ts index 415a8f89c6..83de981544 100644 --- a/src/frontend/packages/core/src/core/utils.service.ts +++ b/src/frontend/packages/core/src/core/utils.service.ts @@ -349,3 +349,11 @@ export const arraysEqual = (a: any[], b: any[]): boolean => { // Falsy/Truthy return false; }; + +export const createGuid = (): string => { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { + var r = Math.random() * 16 | 0, + v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +} diff --git a/src/frontend/packages/core/src/features/endpoints/connect.service.ts b/src/frontend/packages/core/src/features/endpoints/connect.service.ts index 015ff5b14d..d4e9dac73d 100644 --- a/src/frontend/packages/core/src/features/endpoints/connect.service.ts +++ b/src/frontend/packages/core/src/features/endpoints/connect.service.ts @@ -201,6 +201,25 @@ export class ConnectEndpointService { ); } + public getConnectingObservable() { + return this.isBusy$.pipe( + pairwise(), + filter(([oldBusy, newBusy]) => { + return !(oldBusy === true && newBusy === false); + }), + withLatestFrom(this.update$), + map(([, updateSection]) => ({ + ...updateSection, + completed: !updateSection.busy, + })), + startWith({ + busy: true, + completed: false, + error: false + }) + ); + } + public destroy() { safeUnsubscribe(...this.subs); } diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.html b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.html index 0aa360488a..5a3116dce9 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.html +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.html @@ -1,5 +1,5 @@ -

Register a new Endpoint

+

Register Endpoint

diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.ts index 85fedb80c1..e354112437 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.ts @@ -1,16 +1,16 @@ import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; import { first, map } from 'rxjs/operators'; import { RouterNav } from '../../../../../../store/src/actions/router.actions'; import { GeneralEntityAppState } from '../../../../../../store/src/app-state'; -import { selectSessionData } from '../../../../../../store/src/reducers/auth.reducer'; import { entityCatalog } from '../../../../../../store/src/entity-catalog/entity-catalog'; +import { IStratosEndpointDefinition } from '../../../../../../store/src/entity-catalog/entity-catalog.types'; +import { selectSessionData } from '../../../../../../store/src/reducers/auth.reducer'; import { BASE_REDIRECT_QUERY } from '../../../../shared/components/stepper/stepper.types'; import { TileConfigManager } from '../../../../shared/components/tile/tile-selector.helpers'; import { ITileConfig, ITileData } from '../../../../shared/components/tile/tile-selector.types'; -import { Observable } from 'rxjs'; -import { IStratosEndpointDefinition } from '../../../../../../store/src/entity-catalog/entity-catalog.types'; interface ICreateEndpointTilesData extends ITileData { type: string; @@ -103,7 +103,8 @@ export class CreateEndpointBaseStepComponent { }, { type: endpoint.type, - parentType: endpoint.parentType + parentType: endpoint.parentType, + component: endpoint.registrationComponent, } ); }); diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint.component.html b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint.component.html index d4a8beb761..076abff82d 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint.component.html +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint.component.html @@ -1,8 +1,8 @@ -

Register a new Endpoint

+

Register Endpoint

- + @@ -11,4 +11,6 @@

Register a new Endpoint

[finishButtonText]="connect.doConnect ? 'Connect' : 'Finish'">
-
\ No newline at end of file + + + \ No newline at end of file diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint.component.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint.component.ts index 1bd0f9c07c..d44c7af725 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, ViewChild, ViewContainerRef, ComponentRef, OnInit, OnDestroy, ComponentFactory, ComponentFactoryResolver } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; @@ -10,16 +10,40 @@ import { getIdFromRoute } from '../../../core/utils.service'; templateUrl: './create-endpoint.component.html', styleUrls: ['./create-endpoint.component.scss'] }) -export class CreateEndpointComponent { +export class CreateEndpointComponent implements OnInit, OnDestroy { showConnectStep: boolean; - constructor(activatedRoute: ActivatedRoute) { + component: any; + @ViewChild('customComponent', { read: ViewContainerRef, static: true }) customComponentContainer; + componentRef: ComponentRef; + + constructor(activatedRoute: ActivatedRoute, private resolver: ComponentFactoryResolver) { const epType = getIdFromRoute(activatedRoute, 'type'); const epSubType = getIdFromRoute(activatedRoute, 'subtype'); const endpoint = entityCatalog.getEndpoint(epType, epSubType); + + this.component = endpoint.definition.registrationComponent; this.showConnectStep = !endpoint.definition.unConnectable ? endpoint.definition.authTypes && !!endpoint.definition.authTypes.length : false; } + + ngOnInit() { + this.customComponentContainer.clear(); + if (this.componentRef) { + this.componentRef.destroy(); + } + if (this.component) { + const factory: ComponentFactory = this.resolver.resolveComponentFactory(this.component); + this.componentRef = this.customComponentContainer.createComponent(factory); + } + } + + ngOnDestroy() { + if (this.componentRef) { + this.componentRef.destroy(); + } + } + } diff --git a/src/frontend/packages/core/src/shared/components/app-action-monitor-icon/app-action-monitor-icon.component.ts b/src/frontend/packages/core/src/shared/components/app-action-monitor-icon/app-action-monitor-icon.component.ts index 6637ca8748..fc0663320a 100644 --- a/src/frontend/packages/core/src/shared/components/app-action-monitor-icon/app-action-monitor-icon.component.ts +++ b/src/frontend/packages/core/src/shared/components/app-action-monitor-icon/app-action-monitor-icon.component.ts @@ -36,7 +36,8 @@ export class ActionMonitorComponentState { this.currentState = this.getStateObservable(entityMonitor, monitorState); } - private getStateObservable(entityMonitor: EntityMonitor, monitorState: AppMonitorComponentTypes) { + private getStateObservable(entityMonitor: EntityMonitor, monitorState: AppMonitorComponentTypes) + : Observable { switch (monitorState) { case AppMonitorComponentTypes.DELETE: return this.getDeletingState(entityMonitor); @@ -122,6 +123,10 @@ export class ActionMonitorComponentState { }) export class AppActionMonitorIconComponent implements OnInit { + // State observable - use this instead of creating one + @Input() + public state: Observable; + @Input() public entityKey: string; @@ -143,6 +148,9 @@ export class AppActionMonitorIconComponent implements OnInit { constructor(private entityMonitorFactory: EntityMonitorFactory) { } ngOnInit() { + if (this.state) { + this.currentState = this.state; + } else { const state: ActionMonitorComponentState = new ActionMonitorComponentState( this.entityMonitorFactory, this.id, @@ -151,5 +159,6 @@ export class AppActionMonitorIconComponent implements OnInit { this.updateKey ); this.currentState = state.currentState; + } } } diff --git a/src/frontend/packages/core/src/shared/components/app-action-monitor/app-action-monitor.component.html b/src/frontend/packages/core/src/shared/components/app-action-monitor/app-action-monitor.component.html index 6e9a288393..1c9713853c 100644 --- a/src/frontend/packages/core/src/shared/components/app-action-monitor/app-action-monitor.component.html +++ b/src/frontend/packages/core/src/shared/components/app-action-monitor/app-action-monitor.component.html @@ -1 +1,4 @@ - +
+ + +
\ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/app-action-monitor/app-action-monitor.component.ts b/src/frontend/packages/core/src/shared/components/app-action-monitor/app-action-monitor.component.ts index 2895bce18d..345af4a5c6 100644 --- a/src/frontend/packages/core/src/shared/components/app-action-monitor/app-action-monitor.component.ts +++ b/src/frontend/packages/core/src/shared/components/app-action-monitor/app-action-monitor.component.ts @@ -1,8 +1,7 @@ -import { DataSource } from '@angular/cdk/table'; import { Component, Input, OnInit } from '@angular/core'; import { schema } from 'normalizr'; import { never as observableNever, Observable, of as observableOf } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { map, publishReplay, refCount } from 'rxjs/operators'; import { EntitySchema } from '../../../../../store/src/helpers/entity-schema'; import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; @@ -26,7 +25,7 @@ import { ITableColumn } from '../list/list-table/table.types'; export class AppActionMonitorComponent implements OnInit { @Input() - private data$: Observable> = observableNever(); + public data$: Observable> = observableNever(); @Input() public entityKey: string; @@ -58,7 +57,7 @@ export class AppActionMonitorComponent implements OnInit { @Input() public columns: ITableColumn[] = []; - public dataSource: DataSource; + public dataSource: ITableListDataSource; public allColumns: ITableColumn[] = []; @@ -82,9 +81,16 @@ export class AppActionMonitorComponent implements OnInit { cellFlex: '0 0 24px' }; + // Some obs will only ever emit once, once consumed in template this meant table never received emitted data + // so wrap in publish replay + const replayData = this.data$.pipe( + publishReplay(1), + refCount() + ) + this.allColumns = [...this.columns, monitorColumn]; this.dataSource = { - connect: () => this.data$, + connect: () => replayData, disconnect: () => { }, trackBy: (index, item) => { const fn = monitorColumn.cellConfig(item).getId; @@ -117,7 +123,7 @@ export class AppActionMonitorComponent implements OnInit { }) ); } - } as ITableListDataSource; + }; } diff --git a/src/frontend/packages/core/src/shared/components/file-input/file-input.component.html b/src/frontend/packages/core/src/shared/components/file-input/file-input.component.html index 195ae17f56..edef8e7b79 100644 --- a/src/frontend/packages/core/src/shared/components/file-input/file-input.component.html +++ b/src/frontend/packages/core/src/shared/components/file-input/file-input.component.html @@ -1,6 +1,6 @@
- +
No file selected
{{ name }}
@@ -10,4 +10,5 @@
+
\ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/file-input/file-input.component.ts b/src/frontend/packages/core/src/shared/components/file-input/file-input.component.ts index a08b287010..b99327bd0a 100644 --- a/src/frontend/packages/core/src/shared/components/file-input/file-input.component.ts +++ b/src/frontend/packages/core/src/shared/components/file-input/file-input.component.ts @@ -28,8 +28,12 @@ export class FileInputComponent implements OnInit, OnDestroy { @Input() accept: string; @Output() onFileSelect: EventEmitter = new EventEmitter(); + @Output() onFileData: EventEmitter = new EventEmitter(); + @Input() fileFormControlName; + @Input() buttonLabel = ''; + private files: File[]; public name = ''; @@ -65,7 +69,9 @@ export class FileInputComponent implements OnInit, OnDestroy { this.onFileSelect.emit(this.files[0]); if (!!this.formGroupControl) { - this.handleFormControl(this.files[0]); + this.handleFileData(this.files[0], (value) => this.updateFileState(value)); + } else { + this.handleFileData(this.files[0], (value) => this.onFileData.emit(value)); } if (this.files.length > 0) { this.name = this.files[0].name; @@ -79,17 +85,18 @@ export class FileInputComponent implements OnInit, OnDestroy { return false; } - handleFormControl(file) { + handleFileData(file, done) { const reader = new FileReader(); reader.onload = () => { - this.updateFileState(reader.result); + done(reader.result); }; reader.onerror = () => { // Clear the form and thus make it invalid on error - this.updateFileState(null); + done(null); }; reader.readAsText(file); } + private updateFileState(value: string | ArrayBuffer) { this.formGroupControl.control.controls[this.fileFormControlName].setValue(value); } diff --git a/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source-types.ts b/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source-types.ts index 4b1af3bd34..96c4b0443e 100644 --- a/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source-types.ts +++ b/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source-types.ts @@ -64,11 +64,29 @@ interface ICoreListDataSource extends DataSource { trackBy(index: number, item: T); } -export interface ITableListDataSource extends ICoreListDataSource { +interface ICoreTableListDataSource extends ICoreListDataSource { + isTableLoading$?: Observable; + + selectAllChecked?: boolean; // Select items - remove once ng-content can exist in md-table + selectAllIndeterminate?: boolean; // Select all checkbox as indeterminate + selectedRows?: Map; // Select items - remove once ng-content can exist in md-table + selectedRows$?: ReplaySubject>; // Select items - remove once ng-content can exist in md-table + selectAllFilteredRows?: () => void; // Select items - remove once ng-content can exist in md-table + selectedRowToggle?: (row: T, multiMode?: boolean) => void; // Select items - remove once ng-content can exist in md-table + selectClear?: () => void; + + editRow?: T; // Edit items - remove once ng-content can exist in md-table + startEdit?: (row: T) => void; // Edit items - remove once ng-content can exist in md-table + saveEdit?: () => void; // Edit items - remove once ng-content can exist in md-table + cancelEdit?: () => void; // Edit items - remove once ng-content can exist in md-table + getRowUniqueId?: getRowUniqueId; +} + +export interface ITableListDataSource extends ICoreTableListDataSource { isTableLoading$: Observable; } -export interface IListDataSource extends ICoreListDataSource, EntityCatalogEntityConfig { +export interface IListDataSource extends ICoreListDataSource, ICoreTableListDataSource, EntityCatalogEntityConfig { pagination$: Observable; isLocal?: boolean; localDataFunctions?: (( @@ -94,20 +112,11 @@ export interface IListDataSource extends ICoreListDataSource, EntityCatalo filter$: Observable; sort$: Observable; - editRow: T; // Edit items - remove once ng-content can exist in md-table - selectAllChecked: boolean; // Select items - remove once ng-content can exist in md-table - selectedRows: Map; // Select items - remove once ng-content can exist in md-table - selectedRows$: ReplaySubject>; // Select items - remove once ng-content can exist in md-table + getRowUniqueId: getRowUniqueId; entitySelectConfig?: EntitySelectConfig; // For multi action lists, this is used to configure the entity select. - selectAllFilteredRows(); // Select items - remove once ng-content can exist in md-table - selectedRowToggle(row: T, multiMode?: boolean); // Select items - remove once ng-content can exist in md-table - selectClear(); - startEdit(row: T); // Edit items - remove once ng-content can exist in md-table - saveEdit(); // Edit items - remove once ng-content can exist in md-table - cancelEdit(); // Edit items - remove once ng-content can exist in md-table destroy(); /** * Set's data source specific text filter param diff --git a/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-edit/table-cell-edit.component.html b/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-edit/table-cell-edit.component.html index 125c4a88fc..7762b74c15 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-edit/table-cell-edit.component.html +++ b/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-edit/table-cell-edit.component.html @@ -1,11 +1,14 @@ -
- - - -
+ + + + +
\ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-edit/table-cell-edit.component.scss b/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-edit/table-cell-edit.component.scss index 9681b2e526..e964f6220f 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-edit/table-cell-edit.component.scss +++ b/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-edit/table-cell-edit.component.scss @@ -1,4 +1,14 @@ -div { +.edit { display: flex; justify-content: flex-end; + &--subtle { + .mat-icon-button { + font-size: 18px; + height: 25px; + line-height: 25px; + opacity: 60%; + padding-left: 5px; + width: 35px; + } + } } diff --git a/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-edit/table-cell-edit.component.ts b/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-edit/table-cell-edit.component.ts index 5f38b44101..1aa4769a4f 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-edit/table-cell-edit.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-edit/table-cell-edit.component.ts @@ -1,5 +1,6 @@ -/* tslint:disable:no-access-missing-member https://github.com/mgechev/codelyzer/issues/191*/ -import { Component, OnInit } from '@angular/core'; +import { Component, Input } from '@angular/core'; + +import { IListDataSource } from '../../data-sources-controllers/list-data-source-types'; import { TableCellCustom } from '../../list.types'; @Component({ @@ -7,4 +8,20 @@ import { TableCellCustom } from '../../list.types'; templateUrl: './table-cell-edit.component.html', styleUrls: ['./table-cell-edit.component.scss'] }) -export class TableCellEditComponent extends TableCellCustom { } +export class TableCellEditComponent extends TableCellCustom { + + @Input() + row: T; + + @Input() + dataSource: IListDataSource; + + @Input() + subtle: boolean; + + isEditing(): boolean { + return this.dataSource.editRow ? + this.dataSource.getRowUniqueId(this.row) === this.dataSource.getRowUniqueId(this.dataSource.editRow) : + false + } +} diff --git a/src/frontend/packages/core/src/shared/components/list/list-table/table-header-select/table-header-select.component.html b/src/frontend/packages/core/src/shared/components/list/list-table/table-header-select/table-header-select.component.html index c2f860861e..51c00db460 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-table/table-header-select/table-header-select.component.html +++ b/src/frontend/packages/core/src/shared/components/list/list-table/table-header-select/table-header-select.component.html @@ -1,2 +1,3 @@ - - + + \ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/list/list-table/table-row/table-row.component.html b/src/frontend/packages/core/src/shared/components/list/list-table/table-row/table-row.component.html index be2ec31af4..b715c964f2 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-table/table-row/table-row.component.html +++ b/src/frontend/packages/core/src/shared/components/list/list-table/table-row/table-row.component.html @@ -1,5 +1,5 @@
+ [ngClass]="{'table-row-wrapper__blocked': isBlocked$ | async, 'table-row-wrapper__info': inInfoState$ | async, 'table-row-wrapper__warning': inWarningState$ | async,'table-row-wrapper__errored': inErrorState$ | async}">
Deleting
@@ -7,7 +7,7 @@
+ [ngClass]="{'in-expanded-row': !!inExpandedRow, 'has-expanded-row': expandComponent, 'has-error-row': errorMessage$ | async}">
- warning -
+
+
+ warning + info +
\ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/list/list-table/table-row/table-row.component.scss b/src/frontend/packages/core/src/shared/components/list/list-table/table-row/table-row.component.scss index 06621f3b97..535edff498 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-table/table-row/table-row.component.scss +++ b/src/frontend/packages/core/src/shared/components/list/list-table/table-row/table-row.component.scss @@ -16,7 +16,8 @@ } } &__errored, - &__warning { + &__warning, + &__info { .table-row__error { display: flex; } @@ -64,14 +65,23 @@ &__error { align-items: center; display: none; + &-message { flex: 1; line-height: 20px; - margin: 15px 36px; + margin-left: 10px; text-align: left; } - &-icon { - padding-left: 24px; + &-spacer { + align-self: stretch; + flex: 0 0 20px; + &__prominentErrorBar { + flex: 0 0 68px; + } + } + &-content { + flex: 1; + padding: 0 10px 10px 0; } } &__blocker { @@ -93,6 +103,7 @@ } .table-row__inner__expansion.mat-expansion-panel { + border-radius: unset; width: 100%; .table-row__inner__expansion--header { @@ -108,4 +119,9 @@ &.in-expanded-row { border-left-width: 1px; } + + &.has-error-row { + // Remove the bottom border if there's an error underneath it + border-bottom-width: 0; + } } diff --git a/src/frontend/packages/core/src/shared/components/list/list-table/table-row/table-row.component.theme.scss b/src/frontend/packages/core/src/shared/components/list/list-table/table-row/table-row.component.theme.scss index 0ac4538dad..07be004cb3 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-table/table-row/table-row.component.theme.scss +++ b/src/frontend/packages/core/src/shared/components/list/list-table/table-row/table-row.component.theme.scss @@ -2,6 +2,7 @@ $status-colors: map-get($app-theme, status); $error-color: map-get($status-colors, danger); $warn-color: map-get($status-colors, warning); + $info-color: map-get($status-colors, info); $text-color: map-get($status-colors, text); $primary: map-get($theme, primary); $primary-color: mat-color($primary); @@ -22,26 +23,24 @@ } .table-row-wrapper { &__errored { - .table-row { - background-color: transparentize($error-color, .9); - } - .table-row__error { - background-color: $error-color; - color: $text-color; - } - .table-row__error-message { - a { - color: $text-color; + .table-row__error-content { + mat-icon { + color: $error-color; } } } &__warning { - .table-row { - background-color: transparentize($warn-color, .9); + .table-row__error-content { + mat-icon { + color: $warn-color; + } } - .table-row__error { - background-color: $warn-color; - color: $text-color; + } + &__info { + .table-row__error-content { + mat-icon { + color: $info-color; + } } } &__highlighted { diff --git a/src/frontend/packages/core/src/shared/components/list/list-table/table-row/table-row.component.ts b/src/frontend/packages/core/src/shared/components/list/list-table/table-row/table-row.component.ts index 1c3c08d120..2c8325d488 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-table/table-row/table-row.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-table/table-row/table-row.component.ts @@ -38,13 +38,16 @@ export class TableRowComponent extends CdkRow implements OnInit { @Input() minRowHeight: string; @Input() inExpandedRow: boolean; @Input() rowId: string; + @Input() prominentErrorBar: boolean; public inErrorState$: Observable; public inWarningState$: Observable; + public inInfoState$: Observable; public errorMessage$: Observable; public isBlocked$: Observable; public isHighlighted$: Observable; public isDeleting$: Observable; + public isWarningIcon$: Observable; public defaultMinRowHeight = '50px'; private expandedComponentRef: ComponentRef; @@ -64,6 +67,9 @@ export class TableRowComponent extends CdkRow implements OnInit { this.inWarningState$ = this.rowState.pipe( map(state => state.warning) ); + this.inInfoState$ = this.rowState.pipe( + map(state => state.info) + ); this.errorMessage$ = this.rowState.pipe( map(state => state.message) ); @@ -76,6 +82,9 @@ export class TableRowComponent extends CdkRow implements OnInit { this.isDeleting$ = this.rowState.pipe( map(state => state.deleting) ); + this.isWarningIcon$ = this.rowState.pipe( + map(state => state.error || state.warning) + ); } // Ensure we 'register' with the expander service. This also helps with page changes diff --git a/src/frontend/packages/core/src/shared/components/list/list-table/table.component.html b/src/frontend/packages/core/src/shared/components/list/list-table/table.component.html index 4bd2ce8004..fe4fa52887 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-table/table.component.html +++ b/src/frontend/packages/core/src/shared/components/list/list-table/table.component.html @@ -37,7 +37,7 @@ + [rowId]="dataSource.trackBy(null, row)" [prominentErrorBar]="prominentErrorBar"> diff --git a/src/frontend/packages/core/src/shared/components/list/list-table/table.component.scss b/src/frontend/packages/core/src/shared/components/list/list-table/table.component.scss index 8c2db26e14..c2dc1fe36a 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-table/table.component.scss +++ b/src/frontend/packages/core/src/shared/components/list/list-table/table.component.scss @@ -5,7 +5,6 @@ mat-cell, mat-header-cell { flex: 1 1 200px; - padding: 10px; app-table-cell { width: 100%; @@ -88,6 +87,19 @@ mat-header-cell { } } + &--table-centred { + app-table-cell { + align-items: center; + display: flex; + justify-content: center; + text-align: center; + } + } + + &--table-no-v-padding { + padding: 0 10px 0 0; + } + &--table-column-additional-padding { app-table-cell { padding-left: 15px; diff --git a/src/frontend/packages/core/src/shared/components/list/list-table/table.component.ts b/src/frontend/packages/core/src/shared/components/list/list-table/table.component.ts index 10f0637f6a..ab1af731f3 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-table/table.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-table/table.component.ts @@ -64,6 +64,7 @@ export class TableComponent implements OnInit, OnDestroy { public columnNames: string[]; @Input() minRowHeight: string; + @Input() prominentErrorBar: boolean = true; ngOnInit() { if (this.addSelect || this.expandComponent || this.addActions) { diff --git a/src/frontend/packages/core/src/shared/shared.module.ts b/src/frontend/packages/core/src/shared/shared.module.ts index d5ab5af42e..b7cc5252d8 100644 --- a/src/frontend/packages/core/src/shared/shared.module.ts +++ b/src/frontend/packages/core/src/shared/shared.module.ts @@ -47,6 +47,7 @@ import { MetaCardItemComponent } from './components/list/list-cards/meta-card/me import { MetaCardKeyComponent } from './components/list/list-cards/meta-card/meta-card-key/meta-card-key.component'; import { MetaCardTitleComponent } from './components/list/list-cards/meta-card/meta-card-title/meta-card-title.component'; import { MetaCardValueComponent } from './components/list/list-cards/meta-card/meta-card-value/meta-card-value.component'; +import { TableCellEditComponent } from './components/list/list-table/table-cell-edit/table-cell-edit.component'; import { TableCellRequestMonitorIconComponent, } from './components/list/list-table/table-cell-request-monitor-icon/table-cell-request-monitor-icon.component'; @@ -310,7 +311,8 @@ import { UserPermissionDirective } from './user-permission.directive'; SidepanelPreviewComponent, TableCellEndpointNameComponent, CardProgressOverlayComponent, - MaxListMessageComponent + MaxListMessageComponent, + TableCellEditComponent ], entryComponents: [ DialogConfirmComponent, diff --git a/src/frontend/packages/store/src/entity-catalog/entity-catalog.types.ts b/src/frontend/packages/store/src/entity-catalog/entity-catalog.types.ts index 7205f8ff23..7f29f7a188 100644 --- a/src/frontend/packages/store/src/entity-catalog/entity-catalog.types.ts +++ b/src/frontend/packages/store/src/entity-catalog/entity-catalog.types.ts @@ -67,6 +67,7 @@ export interface IStratosBaseEntityDefinition[]; readonly paginationConfig?: PaginationPageIteratorConfig; readonly tableConfig?: EntityTableConfig; + readonly registrationComponent?: any; /** * Hook that will fire before an entity is emitted by an entity service. This could be used, for example, entity validation */ diff --git a/src/frontend/packages/store/src/reducers/api-request-reducer/fail-request.ts b/src/frontend/packages/store/src/reducers/api-request-reducer/fail-request.ts index b62fee8a48..1a3826a515 100644 --- a/src/frontend/packages/store/src/reducers/api-request-reducer/fail-request.ts +++ b/src/frontend/packages/store/src/reducers/api-request-reducer/fail-request.ts @@ -26,8 +26,8 @@ export function failRequest(state, action: IFailedRequestAction) { busy: false, deleted: false, error: true, + message: action.message }; - requestFailedState.message = action.message; } else { requestFailedState.fetching = false; requestFailedState.error = true; diff --git a/src/frontend/packages/store/src/reducers/api-request-reducer/types.ts b/src/frontend/packages/store/src/reducers/api-request-reducer/types.ts index 8b514a0c4a..0f1b724a3e 100644 --- a/src/frontend/packages/store/src/reducers/api-request-reducer/types.ts +++ b/src/frontend/packages/store/src/reducers/api-request-reducer/types.ts @@ -14,6 +14,14 @@ export interface ActionState { message: string; } +// Status of an action +export interface ActionStatus { + busy: boolean; + error: boolean; + message?: string; + completed: boolean; +} + /** * Multi action lists can have different entity types per page * We use schemaKey to track this type diff --git a/src/frontend/packages/store/src/selectors/api.selectors.ts b/src/frontend/packages/store/src/selectors/api.selectors.ts index 4aecb698d7..b1eaa34938 100644 --- a/src/frontend/packages/store/src/selectors/api.selectors.ts +++ b/src/frontend/packages/store/src/selectors/api.selectors.ts @@ -1,8 +1,8 @@ import { compose } from '@ngrx/store'; +import { GeneralEntityAppState, IRequestEntityTypeState as IRequestEntityKeyState, IRequestTypeState } from '../app-state'; import { EntityCatalogHelpers } from '../entity-catalog/entity-catalog.helper'; import { EntityCatalogEntityConfig } from '../entity-catalog/entity-catalog.types'; -import { GeneralEntityAppState, IRequestEntityTypeState as IRequestEntityKeyState, IRequestTypeState } from '../app-state'; import { ActionState, RequestInfoState, UpdatingSection } from '../reducers/api-request-reducer/types'; import { APIResource } from '../types/api.types'; @@ -31,7 +31,7 @@ export const getEntityUpdateSections = ( export const getUpdateSectionById = (guid: string) => ( updating ): ActionState => { - return updating[guid]; + return updating ? updating[guid] : null; }; export function selectUpdateInfo( diff --git a/src/jetstream/default.config.properties b/src/jetstream/default.config.properties index e45f461d9d..2a430adb51 100644 --- a/src/jetstream/default.config.properties +++ b/src/jetstream/default.config.properties @@ -47,8 +47,8 @@ INVITE_USER_CLIENT_SECRET= # Use local admin user rather than UAA users # AUTH_ENDPOINT_TYPE=local -# LOCAL_USER=localuser -# LOCAL_USER_PASSWORD=localuserpass +# LOCAL_USER=admin +# LOCAL_USER_PASSWORD=admin # LOCAL_USER_SCOPE=stratos.admin # MariaDB database for local dev diff --git a/src/jetstream/plugins/kubernetes/main.go b/src/jetstream/plugins/kubernetes/main.go index 0ed9278022..4556391650 100644 --- a/src/jetstream/plugins/kubernetes/main.go +++ b/src/jetstream/plugins/kubernetes/main.go @@ -4,8 +4,10 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net/http" "net/url" "strconv" + "strings" "errors" @@ -135,7 +137,7 @@ func (c *KubernetesSpecification) Init() error { } func (c *KubernetesSpecification) AddAdminGroupRoutes(echoGroup *echo.Group) { - // no-op + echoGroup.GET("/kube/cert", c.RequiresCert) } func (c *KubernetesSpecification) AddSessionGroupRoutes(echoGroup *echo.Group) { @@ -242,3 +244,31 @@ func parseErrorResponse(body []byte) error { func (c *KubernetesSpecification) UpdateMetadata(info *interfaces.Info, userGUID string, echoContext echo.Context) { } + +func (c *KubernetesSpecification) RequiresCert(ec echo.Context) error { + url := ec.QueryParam("url") + + log.Debug("Request Kube API Versions") + var httpClient = c.portalProxy.GetHttpClient(false) + _, err := httpClient.Get(url + "/api") + var response struct { + Status int + Required bool + Error bool + Message string + } + if err != nil { + if strings.Contains(err.Error(), "x509: certificate") { + response.Status = http.StatusOK + response.Required = true + } else { + response.Status = http.StatusInternalServerError + response.Error = true + response.Message = fmt.Sprintf("Failed to validate Kube certificate requirement: %+v", err) + } + } else { + response.Status = http.StatusOK + response.Required = false + } + return ec.JSON(response.Status, response) +} diff --git a/src/test-e2e/endpoints/register-dialog.po.ts b/src/test-e2e/endpoints/register-dialog.po.ts index 30e52790e7..44423a0d97 100644 --- a/src/test-e2e/endpoints/register-dialog.po.ts +++ b/src/test-e2e/endpoints/register-dialog.po.ts @@ -18,7 +18,7 @@ export class RegisterStepper extends Page { } isRegisterDialog(): promise.Promise { - return this.header.getTitleText().then(title => title === 'Register a new Endpoint'); + return this.header.getTitleText().then(title => title === 'Register Endpoint'); } getName = () => this.form.getFormField('name'); From cd0d6d6be53ceb8f6d9b0f57aa602aea15ce1d8a Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 26 Jun 2020 11:41:06 +0100 Subject: [PATCH 551/648] Fix dev helper script --- build/tools/kube-terminal-dev.sh | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/build/tools/kube-terminal-dev.sh b/build/tools/kube-terminal-dev.sh index 2ec5c69c3d..ce1ab1549e 100755 --- a/build/tools/kube-terminal-dev.sh +++ b/build/tools/kube-terminal-dev.sh @@ -22,24 +22,27 @@ sed -i.bak '/\s*app\.kubernetes\.io\/version/d' $TEMPFILE sed -i.bak '/\s*app\.kubernetes\.io\/instance/d' $TEMPFILE sed -i.bak '/\s*{{-/d' $TEMPFILE -kubectl apply -f $TEMPFILE +# Create a namespace +NS="stratos-dev" +kubectl get ns $NS > /dev/null 2>&1 +if [ $? -ne 0 ]; then + kubectl create ns $NS +fi + +kubectl apply -n $NS -f $TEMPFILE +USER=stratos-dev-admin-user +USER=stratos # Service account should be created - now need to get token -SECRET=$(kubectl get sa stratos -o json | jq -r '.secrets[0].name') -TOKEN=$(kubectl get secret $SECRET -o json | jq -r '.data.token') +SECRET=$(kubectl get -n $NS sa $USER -o json | jq -r '.secrets[0].name') +TOKEN=$(kubectl get -n $NS secret $SECRET -o json | jq -r '.data.token') echo "Token secret: $SECRET" +TOKEN=$(echo $TOKEN | base64 -d -) echo "Token $TOKEN" rm -f $TEMPFILE rm -f $TEMPFILE.bak -# Create a namespace -NS="stratos-dev" -kubectl get ns $NS > /dev/null -if [ $? -ne 0 ]; then - kubectl create ns $NS -fi - CFG=${STRATOS_DIR}/src/jetstream/config.properties touch $CFG From 4c206ad49612fccd897ae7e45f05d6baf277ca6e Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 26 Jun 2020 14:41:23 +0100 Subject: [PATCH 552/648] Address PR feedback --- .../plugins/kubernetes/terminal/cleanup.go | 14 ++++++++------ src/jetstream/plugins/kubernetes/terminal/start.go | 3 --- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/jetstream/plugins/kubernetes/terminal/cleanup.go b/src/jetstream/plugins/kubernetes/terminal/cleanup.go index d1d50aa559..621921e1e5 100644 --- a/src/jetstream/plugins/kubernetes/terminal/cleanup.go +++ b/src/jetstream/plugins/kubernetes/terminal/cleanup.go @@ -22,6 +22,8 @@ func (k *KubeTerminal) cleanup() { // Use a random initial wait before cleaning up // If we had more than one backend, this helps to ensure they are not all trying to cleanup at the same time wait := rand.Intn(30) + log.Debug("Kubernetes Terminal cleanup will start in %d minutes", wait) + for { time.Sleep(time.Duration(wait) * time.Minute) log.Debug("Cleaning up stale Kubernetes Terminal pods and secrets ...") @@ -35,7 +37,7 @@ func (k *KubeTerminal) cleanup() { pods, err := podClient.List(options) if err == nil { for _, pod := range pods.Items { - if sessionID, ok := pod.Annotations["stratos-session"]; ok { + if sessionID, ok := pod.Annotations[stratosSessionAnnotation]; ok { i, err := strconv.Atoi(sessionID) if err == nil { isValid, err := k.PortalProxy.GetSessionDataStore().IsValidSession(i) @@ -55,7 +57,7 @@ func (k *KubeTerminal) cleanup() { secrets, err := secretClient.List(options) if err == nil { for _, secret := range secrets.Items { - if sessionID, ok := secret.Annotations["stratos-session"]; ok { + if sessionID, ok := secret.Annotations[stratosSessionAnnotation]; ok { i, err := strconv.Atoi(sessionID) if err == nil { isValid, err := k.PortalProxy.GetSessionDataStore().IsValidSession(i) @@ -67,13 +69,13 @@ func (k *KubeTerminal) cleanup() { } } } else { - log.Debug("Kube Terminal Cleanup: Could not get secrets") - log.Debug(err) + log.Warn("Kube Terminal Cleanup: Could not get secrets") + log.Warn(err) } } else { - log.Debug("Kube Terminal Cleanup: Could not get clients") - log.Debug(err) + log.Warn("Kube Terminal Cleanup: Could not get clients") + log.Warn(err) } wait = waitPeriod diff --git a/src/jetstream/plugins/kubernetes/terminal/start.go b/src/jetstream/plugins/kubernetes/terminal/start.go index d0ac6ac276..63c90fe519 100644 --- a/src/jetstream/plugins/kubernetes/terminal/start.go +++ b/src/jetstream/plugins/kubernetes/terminal/start.go @@ -26,7 +26,6 @@ var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } - // KeyCode - JSON object that is passed from the front-end to notify of a key press or a term resize type KeyCode struct { Key string `json:"key"` @@ -103,11 +102,9 @@ func (k *KubeTerminal) Start(c echo.Context) error { // API Endpoint to SSH/exec into a container target := fmt.Sprintf("%s/api/v1/namespaces/%s/pods/%s/exec?command=/bin/bash&stdin=true&stderr=true&stdout=true&tty=true", k.APIServer, k.Namespace, podData.PodName) - // This dialer does not use the kubeHttpClient - is it unused? dialer := &websocket.Dialer{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, - //Certificates: []tls.Certificate{cert}, }, } From 0bb99986c43ad0e81778945f96c814f035f288d8 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Mon, 29 Jun 2020 10:20:40 +0100 Subject: [PATCH 553/648] Merge upstream (#400) * WIP * Add theme migration * File moves * WIP * Add theme migration * Start to make core a separate library * Tweaks * WIP * Fix package imports * Fix migration script * First pass at permission effects * Fix merge issues * First pass at reducer * Tidy up logging * Fix dark theme support * Final set of cloud-foundry code out of common * TIdy up and get themable packages working * Tidy up custom build code * Tidy ups * Set defaults when no stratos.yaml file is present * FIx migrate script for when stratos.yaml not present * Remove comments * Disable scss linter from code climate * Rejig example extensions to be a package for v4 build * Renames, todos * Minor fixes * Fix frontend unit test * Fix removed e2e target * Fix unit tests * Tweaks * Finish todos * Fix transition from space summary to app summary page - an entity service for a space with no org was cached by guid - an entity service for a space requiring org used cached version - solution is to make cache id include schema key (determines with/without org) * Add comment, tidy up rootUpdatingKey * Fix issue where gitlab summary tab threw errors in console - fixes #4325 * Push combine of permission configs into checkers - overcomes some weird change of permission type * Fix issue where default add/remove setting in change role by username was incorrect - add/remove radio button default value governed by add/remove feature flags - when one is set to false the radio button should be disabled and the other selected * Fix store-test-helper * Update readme following move to Travis-ci.com * Ensure the correct key is used metacard favourite info (#4321) * Fix display of details in endpoint card in endpoint list (#4319) * Clean default/blocked logic * Rename @stratos to @stratosui * FIx references in tsconfig.json * Revert change to @stratosui in code * Revert change to @stratosui in code * Fix endpoint connect * Fix unit test * Fix e2e test * Change following review #1 * Bump github.com/gorilla/websocket from 1.4.1 to 1.4.2 in /src/jetstream (#4199) * Bump github.com/gorilla/websocket from 1.4.1 to 1.4.2 in /src/jetstream Bumps [github.com/gorilla/websocket](https://github.com/gorilla/websocket) from 1.4.1 to 1.4.2. - [Release notes](https://github.com/gorilla/websocket/releases) - [Commits](https://github.com/gorilla/websocket/compare/v1.4.1...v1.4.2) Signed-off-by: dependabot-preview[bot] * Update go.sum Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: Neil MacDougall * Fix errors in console log during setup screens * Helm: Change default image pull policy to Always * Add copy address and edit to endpoint list view * Use icon that is less confusing with refresh * Change following review #2 - Fix cf package module file name - Make CUSTOM_USER_PERMISSION_CHECKERS optional - Remove need to inject CUSTOM_USER_PERMISSION_CHECKERS in multiple cf modules * Add newline at end for codeclimate * Tidy up code and add some more comments * Fix for buildpack filename wrapping on card * Fix code climate issue * Fix compilation issues * Update endpoint-list.helpers.ts * Improve naming * Tidy up * Update .gitignore * Tidy up. Build tools in dist/tools * Change following review * Rename tools to devkit. Add schematics * More improvements and theme schematic added. * Bump gopkg.in/yaml.v2 from 2.2.8 to 2.3.0 in /src/jetstream (#4336) * Bump gopkg.in/yaml.v2 from 2.2.8 to 2.3.0 in /src/jetstream Bumps [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml) from 2.2.8 to 2.3.0. - [Release notes](https://github.com/go-yaml/yaml/releases) - [Commits](https://github.com/go-yaml/yaml/compare/v2.2.8...v2.3.0) Signed-off-by: dependabot-preview[bot] * CI bump Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: Neil MacDougall * Remove unused import * Remove unused import * [Security] Bump websocket-extensions from 0.1.3 to 0.1.4 (#4356) Bumps [websocket-extensions](https://github.com/faye/websocket-extensions-node) from 0.1.3 to 0.1.4. **This update includes a security fix.** - [Release notes](https://github.com/faye/websocket-extensions-node/releases) - [Changelog](https://github.com/faye/websocket-extensions-node/blob/master/CHANGELOG.md) - [Commits](https://github.com/faye/websocket-extensions-node/compare/0.1.3...0.1.4) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Hide deployment info card if not space developer - fixes #4322 * Only space developers can create/unmap/delete routes in app routes table - fixes #4324 * Only Space Developers should be able to change count, terminate or ssh to instances - fixes #4330 * Permissions: Only Space Developers should be able to create/edit/delete an Autoscaler policy - fixes #4323 * Users with no developer roles could click on add app button - fixes #4361 * Fix tests * Fail CI build if imagelist generation fails * Org Managers: Disable org role checkboxes in roles stepper if not admin/org manager - fixes #4332 * Routes List: Filter by org breaks when user is an org auditor - fixes #4343 * Rename the e2e cf helper file * Fix autoscaler tab * Changes following review * A few minor tidy-ups to help with review * Fix several space developer permission bugs (#4362) * Hide deployment info card if not space developer - fixes #4322 * Only space developers can create/unmap/delete routes in app routes table - fixes #4324 * Only Space Developers should be able to change count, terminate or ssh to instances - fixes #4330 * Permissions: Only Space Developers should be able to create/edit/delete an Autoscaler policy - fixes #4323 * Users with no developer roles could click on add app button - fixes #4361 * Fix tests * Fix autoscaler tab * Changes following review * Fix service stepper navigation (#4366) * Fix service stepper navigation on create/edit cancel/sumbit - create service stepper from app service tab, marketplace service summary, service wall - edit service stepper from marketplace service instances, service wall instances, space service instances and app service instances lists - fixes #4052, contrinbutes to #4079, #4051 * Fix subscription leak - fixes #4295 - code no-longer needed * Force return location of service stepper, fix table edit of upsi and other improvements * Fix unit tests * Fix e2e tests * Fix e2e tests, add search to marketplace service instances table * Changes following review * Move base-entity-* to store package * Add snackbar * Remove pathget * Remove more store -> core dependencies * Move extension-types to store * Move favourite mgr and helper from core to store * Move user fav manager and helper to store package * More references * Fix logger service removal * Fix build * Address PR feedback * Fix LoggerService after code moves * Fix logger service ref * Fix build issues * Fix health check import * Move sortStringify (only used once) * NonOptionalKeys - only used once * Move KnownKeys * Move BrowserStandardEncoder * Remove last environment link * Move favourite config mapper * Remove dependency on StratosScopeStrings * Move BaseEndpointAuth * Move StratosTheme * Move ThemeService * Move types from utils.service that are only used once in store * Move PermissionValues * Move http and jetstream helpers * Move StyleService * Move MetricQueryType * Move getFullEndpointApiUrl * Move StratosStatus * Move generateStratosEntities back to core * Finish store -> core sep * Address PR feedback * Fix compilation issue * Fix merge weirdness * Merge downstream (#4369) * src based changes * changes to ./build * ./docs * ./ * Changes following review * Address PR feedback * fix build * whitespace changes * Fix in core public-api after merge * Fix duplicated k8s docs * Typed access to actions and entities via catalog entity: Core Stratos Entities (#4387) * WIP * WIP * WIP * Endpoint changes * Fix failure handling during connect on stepper * Fix favourites * Removed some TODOs * Fixes & todos * Unit test fixes * Fixes following merge * Fix cf unit tests * Changes following review * Theming improvements for header * Tweaks, Migration Script Docs & Notes on others (#4389) Co-authored-by: Neil MacDougall * Build devkit outside of dist folder * Ignore example packages when there's a stratos config file * Add dist-devkit to git ignore * Changes following review * Revert change needed downstream... (only needed when suse extension is included) * Fix after merge Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: Neil MacDougall --- .codeclimate.yml | 2 +- .gitignore | 10 +- CHANGELOG.md | 8 +- angular.json | 101 +++-- build/customize-build.js | 316 -------------- build/fe-build.js | 3 - build/tools/v4-migration/migrate.sh | 156 +++++++ .../tools/v4-migration/templates/_index.scss | 9 + .../v4-migration/templates/ext.package.json | 6 +- .../v4-migration/templates/public-api.ts_ | 3 + .../v4-migration/templates/theme.package.json | 19 + deploy/cloud-foundry/build.sh | 1 - deploy/kubernetes/README.md | 398 +----------------- .../packages/backend/pre_packaging | 1 - .../packages/frontend/pre_packaging | 1 - docs/customizing.md | 35 +- package-lock.json | 223 ++++++++-- package.json | 22 +- .../cf-autoscaler/src/cf-autoscaler.module.ts | 3 +- .../src/core/autoscaler.module.ts | 7 - ...autoscaler-tab-extension.component.spec.ts | 6 +- .../autoscaler-tab-extension.component.ts | 47 ++- ...p-autoscaler-events-config.service.spec.ts | 9 - ...cf-app-autoscaler-events-config.service.ts | 3 +- ...scaler-metric-chart-list-config.service.ts | 3 +- .../src/store/autoscaler-entity-factory.ts | 4 +- .../src/store/autoscaler-entity-generator.ts | 2 +- .../src/store/autoscaler.effects.ts | 2 +- .../packages/cloud-foundry/package.json | 4 + .../cloud-foundry/sass/_all-theme.scss | 12 + .../src/actions/cf-event.actions.ts | 4 +- .../src/actions/cf-metrics.actions.ts | 2 +- .../src/actions/domains.actions.ts | 4 +- .../src/actions/feature-flags.actions.ts | 4 +- .../cloud-foundry/src/actions/stack.action.ts | 4 +- .../actions/user-provided-service.actions.ts | 4 +- .../src/actions/users.actions.ts | 4 +- .../cloud-foundry/src/cf-entity-factory.ts | 36 +- .../cloud-foundry/src/cf-entity-generator.ts | 22 +- .../cloud-foundry/src/cf-error-helpers.ts | 2 +- .../cloud-foundry/src/cf-favorites-helpers.ts | 2 +- .../src/cloud-foundry-test.module.ts | 2 +- .../entity-relations-list.spec.ts | 3 +- .../entity-relations.tree.spec.ts | 2 +- .../application-delete.component.spec.ts | 9 - ...te-instances-routes-list-config.service.ts | 4 +- .../delete-app-instances.component.spec.ts | 9 - .../delete-app-routes.component.spec.ts | 9 - .../applications/application.service.spec.ts | 9 - .../applications/application.service.ts | 28 +- .../application/application-base.component.ts | 24 +- .../application-poll.component.spec.ts | 9 - .../application-polling.service.ts | 15 +- .../application-tabs-base.component.spec.ts | 9 - .../application-tabs-base.component.ts | 15 +- .../tabs/build-tab/build-tab.component.html | 7 +- .../build-tab/build-tab.component.spec.ts | 9 +- .../tabs/build-tab/build-tab.component.ts | 39 +- .../log-stream-tab.component.spec.ts | 9 - .../metrics-tab/metrics-tab.component.spec.ts | 9 - .../cli-info-application.component.spec.ts | 9 - .../cli-info-application.component.ts | 13 +- .../edit-application.component.spec.ts | 9 - .../create-organization-step.component.ts | 4 +- .../features/cloud-foundry/cf-cell.helpers.ts | 2 +- .../src/features/cloud-foundry/cf.helpers.ts | 10 +- .../cli-info-cloud-foundry.component.ts | 2 +- .../cloud-foundry-tabs-base.component.ts | 2 +- .../edit-organization-step.component.ts | 4 +- .../quota-definition-form.component.ts | 4 +- .../cloud-foundry-endpoint.service.ts | 19 +- .../services/cloud-foundry-org-space-quota.ts | 2 +- .../cloud-foundry-organization-quota.ts | 2 +- .../services/cloud-foundry-space-quota.ts | 2 +- .../space-quota-definition-form.component.ts | 4 +- .../space-quota-definition.component.spec.ts | 4 +- .../cloud-foundry-cell-base.component.ts | 2 +- .../cloud-foundry-cell-charts.component.ts | 3 +- ...oud-foundry-cell-summary.component.spec.ts | 2 +- .../cloud-foundry-cell-summary.component.ts | 2 +- .../cloud-foundry-cell.service.ts | 3 +- ...oud-foundry-organization-base.component.ts | 6 +- .../cloud-foundry-space-base.component.ts | 6 +- .../user-invites/user-invite.service.ts | 7 +- .../users/manage-users/cf-roles.service.ts | 4 +- .../service-tabs-base.component.html | 6 +- .../service-tabs-base.component.ts | 33 +- .../service-catalog/services-helper.ts | 2 +- .../service-catalog/services.service.ts | 4 +- .../services-wall.component.html | 2 +- .../services-wall/services-wall.component.ts | 6 + .../services/services-wall.service.ts | 4 +- .../packages/cloud-foundry/src/public_api.ts | 2 + .../src/shared/cf-shared.module.ts | 6 +- ...-service-instance-base-step.component.html | 2 +- ...dd-service-instance-base-step.component.ts | 34 +- .../add-service-instance/csi-mode.service.ts | 51 ++- .../select-plan-step.component.ts | 2 +- .../specify-details-step.component.ts | 18 +- ...specify-user-provided-details.component.ts | 26 +- .../application-instance-chart.component.html | 0 .../application-instance-chart.component.scss | 0 ...plication-instance-chart.component.spec.ts | 8 +- .../application-instance-chart.component.ts | 15 +- .../card-app-instances.component.html | 39 +- .../card-app-instances.component.ts | 24 +- .../card-app-status.component.ts | 2 +- .../card-app-usage.component.ts | 2 +- .../compact-app-card.component.ts | 2 +- .../card-cf-space-details.component.ts | 7 +- .../cf-role-checkbox.component.ts | 3 +- .../cf-app-instances-config.service.spec.ts | 9 - .../cf-app-instances-config.service.ts | 28 +- .../table-cell-cf-cell.component.ts | 2 +- .../cf-app-routes-list-config.service.ts | 20 +- .../app-service-binding-card.component.ts | 14 +- ...app-service-binding-list-config.service.ts | 16 +- ...-app-variables-list-config.service.spec.ts | 9 - .../list-types/app/card/card-app.component.ts | 8 +- .../cf-buildpacks-data-source.ts | 4 +- .../cf-cell-apps-list-config.service.ts | 5 +- .../cf-cell-apps/cf-cell-apps-source.ts | 22 +- .../cf-endpoints/cf-endpoints-data-source.ts | 4 +- .../cf-org-card/cf-org-card.component.ts | 14 +- .../cf-quotas-data-source.service.ts | 9 +- .../cf-security-groups-data-source.ts | 4 +- .../cf-service-instances-list-config.base.ts | 21 +- .../cf-services/cf-services-data-source.ts | 4 +- .../cf-user-service-instances-list-config.ts | 8 +- .../cf-space-quotas-data-source.service.ts | 10 +- ...s-service-instances-list-config.service.ts | 8 +- .../cf-space-card/cf-space-card.component.ts | 14 +- .../table-cell-org-space-role.component.html | 1 + .../service-instances-data-source.ts | 1 + .../service-instances-list-config.service.ts | 16 +- .../service-instance-card.component.ts | 13 +- ...vice-instances-wall-list-config.service.ts | 8 +- ...rovided-service-instance-card.component.ts | 13 +- .../service-plan-public.component.spec.ts | 2 +- .../service-plan-public.component.ts | 2 +- .../shared/data-services/cf-user.service.ts | 4 +- .../data-services/cloud-foundry.service.ts | 12 +- .../long-running-cf-op.service.ts | 19 +- .../services/application-state.service.ts | 2 +- .../services/cf-org-space-label.service.ts | 5 +- .../current-user-permissions.service.spec.ts | 6 +- .../src/store/reducers/cf-users.reducer.ts | 3 +- .../cf-current-user-role.selectors.ts | 2 +- .../cf-user-permissions-checkers.ts | 4 +- .../user-permissions/cf-user-roles-fetch.ts | 7 +- .../application-service-helper.ts | 7 +- .../test-framework/cf-test-helper.ts | 2 +- .../cloud-foundry-endpoint-service.helper.ts | 2 +- .../packages/core/endpoints-health-checks.ts | 13 +- .../misc/custom/custom-src-routing.module.ts_ | 21 - .../core/misc/custom/custom-src.module.ts_ | 16 - .../packages/core/misc/custom/custom.scss | 5 - .../packages/core/misc/custom/custom.yaml | 14 - .../packages/core/misc/custom/eula.html | 7 - src/frontend/packages/core/ng-package.json | 7 + src/frontend/packages/core/package.json | 9 + .../packages/core/sass/_all-theme.scss | 55 +-- src/frontend/packages/core/sass/colors.scss | 7 - src/frontend/packages/core/sass/theme.scss | 65 +-- .../packages/core/src/app.component.ts | 6 +- src/frontend/packages/core/src/app.module.ts | 36 +- .../packages/core/src/base-entity-schemas.ts | 24 -- .../packages/core/src/base-entity-types.ts | 110 ----- .../endpoints => core}/endpoint-auth.ts | 18 +- .../core/src/core/endpoints.service.spec.ts | 4 +- .../core/src/core/endpoints.service.ts | 3 +- .../entity-favorite-star.component.spec.ts | 4 +- .../entity-favorite-star.component.ts | 4 +- .../src/core/extension/extension-service.ts | 12 +- .../core/src/core/logger.service.spec.ts | 2 +- .../current-user-permissions.config.ts | 3 +- .../current-user-permissions.service.spec.ts | 6 +- .../current-user-permissions.service.ts | 24 +- .../stratos-user-permissions.checker.ts | 3 +- .../stateful-icon.component.spec.ts | 2 +- .../stateful-icon/stateful-icon.component.ts | 5 +- .../core/src/core/user-profile.service.ts | 66 +-- .../core/src/core/user.service.spec.ts | 2 +- .../packages/core/src/core/utils.service.ts | 57 --- .../custom-import.module.ts} | 3 +- .../core/src/environments/environment.prod.ts | 5 +- .../core/src/environments/environment.ts | 5 +- .../core/src/features/about/about.module.ts | 4 +- .../about/eula-page/eula-page.component.html | 2 +- .../eula-page/eula-page.component.spec.ts | 8 +- .../about/eula-page/eula-page.component.ts | 21 +- .../dashboard-base.component.scss | 1 - .../dashboard-base.component.ts | 6 +- .../page-side-nav/page-side-nav.component.ts | 8 +- .../side-nav/side-nav.component.theme.scss | 14 +- .../backup-endpoints.service.ts | 2 +- .../backup-endpoints.component.ts | 22 +- .../restore-endpoints.service.ts | 2 +- .../restore-endpoints.component.ts | 6 +- .../credentials-auth-form.component.ts | 5 +- .../auth-forms/none-auth-form.component.ts | 3 +- .../auth-forms/sso-auth-form.component.ts | 3 +- .../connect-endpoint-dialog.component.ts | 10 +- .../connect-endpoint.component.ts | 5 +- .../src/features/endpoints/connect.service.ts | 76 +--- .../create-endpoint-cf-step-1.component.ts | 67 +-- .../create-endpoint-connect.component.ts | 5 +- .../edit-endpoint-step.component.ts | 47 ++- .../features/endpoints/endpoint-helpers.ts | 10 - .../endpoints-page.component.ts | 13 +- .../error-page/error-page.component.ts | 16 +- .../features/home/home/home-page.component.ts | 4 +- .../src/features/metrics/metrics.helpers.ts | 5 +- .../metrics/services/metrics-service.ts | 17 +- .../core/src/features/setup/setup.module.ts | 2 +- .../edit-profile-info.component.ts | 6 +- .../profile-info/profile-info.component.ts | 2 +- .../core/{misc/custom => src}/index.html | 2 +- .../packages/core/src/jetstream.helpers.ts | 70 +-- src/frontend/packages/core/src/public-api.ts | 19 + .../app-action-monitor-icon.component.spec.ts | 4 +- .../application-state-icon.component.ts | 2 +- .../application-state.component.ts | 2 +- .../card-number-metric.component.ts | 2 +- .../card-status/card-status.component.ts | 2 +- .../favorites-entity-list.component.ts | 4 +- .../favorites-global-list.component.ts | 9 +- .../favorites-meta-card.component.ts | 28 +- .../json-viewer/json-viewer.component.ts | 2 +- .../list-data-source.ts | 2 +- .../local-list-controller.ts | 2 +- .../meta-card.component.spec.ts | 8 +- .../meta-card-base/meta-card.component.ts | 22 +- .../table-cell-favorite.component.ts | 2 +- ...ell-request-monitor-icon.component.spec.ts | 4 +- .../endpoint/base-endpoints-data-source.ts | 20 +- .../endpoint-card/endpoint-card.component.ts | 20 +- .../endpoint/endpoint-data-source.helpers.ts | 8 +- .../endpoint/endpoint-list.helpers.ts | 61 +-- .../endpoint/endpoints-data-source.ts | 4 +- .../endpoint/endpoints-list-config.service.ts | 2 +- .../table-cell-endpoint-address.component.ts | 11 +- ...table-cell-endpoint-name.component.spec.ts | 4 +- .../table-cell-endpoint-name.component.ts | 28 +- ...metrics-parent-range-selector.component.ts | 4 +- .../metrics-range-selector.component.ts | 3 +- .../page-header/page-header.component.html | 2 +- .../page-header.component.theme.scss | 12 +- .../page-header/page-header.component.ts | 4 +- .../recent-entities.component.ts | 4 +- .../src/shared/components/user-avatar/md5.ts | 2 +- .../packages/core/src/shared/entity.tokens.ts | 6 +- .../src/shared/global-events.service.spec.ts | 2 +- .../core/src/shared/global-events.service.ts | 2 +- .../metrics-range-selector-manager.service.ts | 5 +- .../metrics-range-selector.service.ts | 3 +- .../services/metrics-range-selector.types.ts | 8 +- .../src/shared/services/snackbar.service.ts | 33 ++ .../packages/core/src/shared/shared.module.ts | 5 - src/frontend/packages/core/src/styles.scss | 1 - .../core/test-framework/core-test.modules.ts | 2 +- .../test-framework/entity-service.helper.ts | 28 -- src/frontend/packages/core/tsconfig.app.json | 3 +- src/frontend/packages/core/tsconfig.lib.json | 10 + src/frontend/packages/devkit/build.sh | 21 + .../packages/devkit/package-lock.json | 14 + src/frontend/packages/devkit/package.json | 18 + .../packages/devkit/src/build/assets.ts | 20 + .../packages/devkit/src/build/extensions.ts | 68 +++ .../devkit/src/build/index.transform.ts | 82 ++++ .../packages/devkit/src/build/main.ts | 42 ++ .../packages/devkit/src/build/sass.ts | 88 ++++ .../devkit/src/builders/builders.json | 10 + .../src/builders/theme.builder.schema.json | 13 + .../devkit/src/builders/theme.builder.ts | 43 ++ .../packages/devkit/src/lib/git.metadata.ts | 37 ++ src/frontend/packages/devkit/src/lib/log.ts | 17 + .../packages/devkit/src/lib/packages.ts | 291 +++++++++++++ .../packages/devkit/src/lib/schematics.ts | 140 ++++++ .../packages/devkit/src/lib/stratos.config.ts | 232 ++++++++++ .../devkit/src/schematics/collection.json | 10 + .../theme/asset-files/README.md.template | 1 + .../theme/asset-files/core}/login-bg.jpg | Bin .../theme/asset-files/core}/logo.png | Bin .../theme/asset-files/core}/nav-logo-icon.png | Bin .../theme/asset-files/core}/nav-logo.png | Bin .../schematics/theme/asset-files}/favicon.ico | Bin .../theme/files/_index.scss.template | 12 + .../theme/files/package.json.template | 18 + .../theme/files/sass/custom.scss.template | 1 + .../files/sass/mat-colors.scss.template} | 0 .../devkit/src/schematics/theme/index.ts | 110 +++++ .../theme/loader-files/loading.css.template | 38 ++ .../theme/loader-files/loading.html.template | 3 + .../devkit/src/schematics/theme/schema.d.ts | 25 ++ .../devkit/src/schematics/theme/schema.json | 32 ++ .../devkit/src/schematics/theme/theme-long.md | 1 + src/frontend/packages/devkit/tsconfig.json | 11 + .../packages/example-extensions/package.json | 8 + .../src}/acme-login/acme-login.component.html | 0 .../src}/acme-login/acme-login.component.scss | 1 - .../acme-login/acme-login.component.spec.ts | 0 .../src}/acme-login/acme-login.component.ts | 7 +- .../acme-support-info.component.html | 0 .../acme-support-info.component.scss | 0 .../acme-support-info.component.spec.ts | 0 .../acme-support-info.component.ts | 0 .../app-action-extension.component.html | 0 .../app-action-extension.component.scss | 0 .../app-action-extension.component.spec.ts | 0 .../app-action-extension.component.ts | 2 +- .../app-tab-extension.component.html | 0 .../app-tab-extension.component.scss | 0 .../app-tab-extension.component.spec.ts | 0 .../app-tab-extension.component.ts | 3 +- .../src/example-routing.module.ts | 4 +- .../example-extensions/src/example.module.ts | 24 +- .../example-component/example.component.html | 0 .../example-component/example.component.scss | 0 .../example-component/example.component.ts | 0 .../nav-extension/nav-extension.module.ts | 9 +- .../nav-extension/nav-extension.routing.ts | 0 .../example-extensions/src/public-api.ts | 4 + .../packages/example-theme/_index.scss | 9 + .../assets/core/custom/acme_logo.png | Bin 0 -> 18891 bytes .../example-theme/assets/core/eula.html | 5 + .../example-theme/assets/core/login-bg.jpg | Bin 0 -> 672633 bytes .../example-theme/assets/core/logo.png | Bin 0 -> 15035 bytes .../packages/example-theme/assets/favicon.ico | Bin 0 -> 1150 bytes .../packages/example-theme/loader/loading.css | 38 ++ .../example-theme/loader/loading.html | 3 + .../packages/example-theme/package.json | 17 + .../packages/example-theme/sass/custom.scss | 18 + .../sass/custom/acme-colors.scss | 7 + .../example-theme/sass/custom/acme.scss | 71 ++++ src/frontend/packages/shared/karma.conf.js | 8 + src/frontend/packages/shared/ng-package.json | 7 + src/frontend/packages/shared/package.json | 11 + .../packages/shared/sass/_all-theme.scss | 9 + .../packages/shared/src/components.module.ts | 17 + .../stratos-title.component.html | 4 + .../stratos-title.component.scss | 20 + .../stratos-title.component.spec.ts | 25 ++ .../stratos-title.component.theme.scss | 6 + .../stratos-title/stratos-title.component.ts | 12 + .../packages/shared/src/public-api.ts | 5 + .../packages/shared/tsconfig.lib.json | 10 + .../packages/shared/tsconfig.spec.json | 17 + src/frontend/packages/shared/tslint.json | 17 + src/frontend/packages/store/ng-package.json | 2 +- src/frontend/packages/store/package.json | 2 +- .../store/src/actions/dashboard-actions.ts | 2 +- .../store/src/actions/endpoint.actions.ts | 196 ++++++--- .../store/src/actions/metrics-api.actions.ts | 3 +- .../store/src/actions/metrics.actions.ts | 8 +- .../store/src/actions/snackBar.actions.ts | 28 -- .../store/src/actions/system.actions.ts | 28 +- .../base-user-favorites-action.ts | 11 - .../get-user-favorites-action.ts | 30 -- .../remove-user-favorite-action.ts | 21 - .../save-user-favorite-action.ts | 25 -- .../toggle-user-favorite-action.ts | 13 - .../update-user-favorite-metadata-action.ts | 21 - .../src/actions/user-favourites.actions.ts | 145 +++++++ .../store/src/actions/user-profile.actions.ts | 39 +- .../src/browser-encoder.ts} | 9 - .../store/src/effects/auth.effects.ts | 13 +- .../store/src/effects/dashboard.effects.ts | 2 +- .../effects/endpoint-api-errors.effects.ts | 4 +- .../store/src/effects/endpoint.effects.ts | 136 +++--- .../store/src/effects/permissions.effect.ts | 10 +- .../src/effects/snackBar.effects.spec.ts | 32 -- .../store/src/effects/snackBar.effects.ts | 42 -- .../store/src/effects/system.effects.ts | 32 +- .../store/src/effects/uaa-setup.effects.ts | 2 +- .../src/effects/user-favorites-effect.ts | 96 +++-- .../store/src/effects/user-profile.effects.ts | 98 ++--- .../packages/store/src/endpoint-utils.ts | 6 + .../entity-catalog-entity-store-helpers.ts | 7 +- .../entity-catalog-entity.ts | 20 +- .../entity-catalog-entity.types.ts | 10 +- .../entity-catalog-entity/type.helpers.ts | 46 ++ .../src/entity-catalog/entity-catalog.spec.ts | 15 +- .../src/entity-catalog/entity-catalog.ts | 6 +- .../entity-catalog/entity-catalog.types.ts | 19 +- .../endpoint-errors.handler.ts | 4 +- .../handle-multi-endpoints.pipe.spec.ts | 2 +- .../handle-multi-endpoints.pipe.ts | 4 +- .../jetstream-error.handler.ts | 4 +- .../entity-request-pipeline.ts | 2 +- .../entity-request-pipeline.types.ts | 4 +- .../pipline-http-client.service.ts | 4 +- .../packages/store/src/entity-service.spec.ts | 23 +- .../src}/extension-types.ts | 0 .../src}/favorite-config-mapper.ts | 29 +- .../store/src/helpers/entity-factory.ts | 32 -- .../src/helpers}/local-list.helpers.ts | 4 +- .../store/src/helpers/store-helpers.ts | 2 +- .../src/helpers/stratos-entity-factory.ts | 40 ++ src/frontend/packages/store/src/jetstream.ts | 78 ++++ .../store/src/monitors/pagination-monitor.ts | 4 +- src/frontend/packages/store/src/public-api.ts | 3 + .../packages/store/src/reducers.module.ts | 37 +- .../api-request-reducer/request-helpers.ts | 3 +- .../recently-visited.reducer.ts | 7 +- .../user-favorites-groups.reducer.spec.ts | 8 +- .../user-favorites-groups.reducer.ts | 8 +- .../store/src/reducers/endpoints.reducer.ts | 13 +- .../store/src/reducers/favorite.reducer.ts | 8 +- .../src/reducers/internal-events.reducer.ts | 20 +- ...agination-reducer-clear-pagination-type.ts | 4 +- ...gination-reducer-create-pagination.spec.ts | 2 +- .../pagination-reducer-max-reached.ts | 4 +- .../pagination-reducer.helper.ts | 9 +- .../pagination.reducer.spec.ts | 6 +- .../selectors/current-user-role.selectors.ts | 5 +- .../store/src/selectors/endpoint.selectors.ts | 5 +- .../selectors/favorite-groups.selectors.ts | 6 +- .../packages/store/src/store.module.ts | 2 - .../store/src/stratos-action-builders.ts | 198 +++++++++ .../store/src/stratos-entity-catalog.ts | 45 ++ .../store/src/stratos-entity-generator.ts | 161 +++++++ .../src/core => store/src}/style.service.ts | 0 .../src/core => store/src}/theme.service.ts | 15 +- .../packages/store/src/types/auth.types.ts | 4 +- .../store/src/types/base-metric.types.ts | 1 - .../store/src/types/endpoint.types.ts | 15 +- .../store/src/types/menu-item.types.ts | 11 + .../packages/store/src/types/metric.types.ts | 5 + .../src/types}/shared.types.ts | 4 +- .../packages/store/src/types/system.types.ts | 13 +- .../packages/store/src/types/theme.types.ts | 5 + .../src/types/user-favorite-manager.types.ts | 27 ++ .../store/src/types/user-favorites.types.ts | 11 +- .../src}/user-favorite-helpers.ts | 8 +- .../src}/user-favorite-manager.ts | 58 +-- .../packages/store/testing/ng-package.json | 4 +- .../store/testing/src/store-test-helper.ts | 4 +- src/frontend/packages/theme/_helper.scss | 112 +++++ src/frontend/packages/theme/_index.scss | 6 + .../packages/theme/assets/core/login-bg.jpg | Bin 0 -> 206493 bytes .../packages/theme/assets/core/logo.png | Bin 0 -> 22715 bytes .../theme/assets/core/nav-logo-icon.png | Bin 0 -> 667 bytes .../packages/theme/assets/core/nav-logo.png | Bin 0 -> 1548 bytes .../packages/theme/assets/favicon.ico | Bin 0 -> 15086 bytes .../misc/custom => theme/loader}/loading.css | 0 .../misc/custom => theme/loader}/loading.html | 0 src/frontend/packages/theme/mat-colors.scss | 28 ++ .../{core/sass => theme}/mat-themes.scss | 0 src/frontend/packages/theme/package.json | 19 + src/jetstream/go.sum | 10 +- .../plugins/userfavorites/favorites.go | 8 +- .../application-deploy-docker-e2e.spec.ts | 2 +- .../application-deploy-e2e.spec.ts | 2 +- .../application/application-e2e-helpers.ts | 2 +- .../application-routes-e2e.spec.ts | 2 +- .../application/application-view-e2e.spec.ts | 5 +- .../applications/application-wall-e2e.spec.ts | 2 +- .../cf-level/manage-org-e2e.spec.ts | 2 +- .../cf-level/manage-quota-e2e.spec.ts | 2 +- .../cloud-foundry-list-cf-e2e.spec.ts | 24 +- .../cloud-foundry/invite-users-e2e.helper.ts | 2 +- ...nage-users-by-username-stepper-e2e.spec.ts | 2 +- .../manage-users-stepper-e2e.spec.ts | 2 +- .../org-level/cf-org-delete-e2e.spec.ts | 5 +- .../org-level/manage-space-quota-e2e.spec.ts | 2 +- .../org-level/mange-space-e2e.spec.ts | 4 +- .../org-level/org-invite-user-e2e.spec.ts | 2 +- .../org-level/org-spaces-e2e.spec.ts | 2 +- .../space-level/cf-space-delete-e2e.spec.ts | 11 +- .../space-level/cf-space-level-e2e.spec.ts | 2 +- .../space-level/space-invite-user-e2e.spec.ts | 2 +- .../space-level/space-routes-e2e.spec.ts | 2 +- .../cloud-foundry/users-list-e2e.helper.ts | 2 +- .../cloud-foundry/users-removal-e2e.helper.ts | 2 +- src/test-e2e/e2e.ts | 8 + src/test-e2e/endpoints/endpoints-e2e.spec.ts | 31 +- .../{cf-helpers.ts => cf-e2e-helpers.ts} | 0 .../create-marketplace-service-instance.po.ts | 10 +- .../create-service-instance-e2e.spec.ts | 13 +- ...reate-service-instance-private-e2e.spec.ts | 10 +- ...-service-instance-space-scoped-e2e.spec.ts | 9 +- .../marketplace/create-service-instance.po.ts | 16 +- ...ate-service-instances-bind-app-e2e.spec.ts | 7 +- .../create-ups-service-instance.po.ts | 9 + .../delete-service-instance-e2e.spec.ts | 13 +- .../delete-ups-service-instance-e2e.spec.ts | 8 +- .../edit-service-instance-e2e.spec.ts | 5 +- ...place-create-service-instances-e2e.spec.ts | 102 ++--- .../marketplace/marketplace-instances.po.ts | 11 + .../marketplace-summary-e2e.spec.ts | 9 +- .../marketplace/marketplace-summary.po.ts | 1 + .../marketplace/services-helper-e2e.ts | 4 +- .../marketplace/services-wall-e2e.spec.ts | 5 +- src/test-e2e/marketplace/services-wall.po.ts | 10 +- src/test-e2e/po/invite-users-stepper.po.ts | 2 +- src/test-e2e/po/page.po.ts | 25 +- src/tsconfig.json | 8 +- 498 files changed, 5151 insertions(+), 3396 deletions(-) delete mode 100644 build/customize-build.js create mode 100755 build/tools/v4-migration/migrate.sh create mode 100644 build/tools/v4-migration/templates/_index.scss rename src/frontend/packages/store/testing/package.json => build/tools/v4-migration/templates/ext.package.json (53%) create mode 100644 build/tools/v4-migration/templates/public-api.ts_ create mode 100644 build/tools/v4-migration/templates/theme.package.json create mode 100644 src/frontend/packages/cloud-foundry/sass/_all-theme.scss rename src/frontend/packages/cloud-foundry/src/{features/applications/application => shared/components}/application-instance-chart/application-instance-chart.component.html (100%) rename src/frontend/packages/cloud-foundry/src/{features/applications/application => shared/components}/application-instance-chart/application-instance-chart.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/{features/applications/application => shared/components}/application-instance-chart/application-instance-chart.component.spec.ts (74%) rename src/frontend/packages/cloud-foundry/src/{features/applications/application => shared/components}/application-instance-chart/application-instance-chart.component.ts (66%) delete mode 100644 src/frontend/packages/core/misc/custom/custom-src-routing.module.ts_ delete mode 100644 src/frontend/packages/core/misc/custom/custom-src.module.ts_ delete mode 100644 src/frontend/packages/core/misc/custom/custom.scss delete mode 100644 src/frontend/packages/core/misc/custom/custom.yaml delete mode 100644 src/frontend/packages/core/misc/custom/eula.html create mode 100644 src/frontend/packages/core/ng-package.json create mode 100644 src/frontend/packages/core/package.json delete mode 100644 src/frontend/packages/core/sass/colors.scss delete mode 100644 src/frontend/packages/core/src/base-entity-schemas.ts delete mode 100644 src/frontend/packages/core/src/base-entity-types.ts rename src/frontend/packages/core/src/{features/endpoints => core}/endpoint-auth.ts (60%) rename src/frontend/packages/core/{misc/custom/custom.module.ts_ => src/custom-import.module.ts} (57%) rename src/frontend/packages/core/{misc/custom => src}/index.html (95%) create mode 100644 src/frontend/packages/core/src/public-api.ts create mode 100644 src/frontend/packages/core/src/shared/services/snackbar.service.ts delete mode 100644 src/frontend/packages/core/test-framework/entity-service.helper.ts create mode 100644 src/frontend/packages/core/tsconfig.lib.json create mode 100755 src/frontend/packages/devkit/build.sh create mode 100644 src/frontend/packages/devkit/package-lock.json create mode 100644 src/frontend/packages/devkit/package.json create mode 100644 src/frontend/packages/devkit/src/build/assets.ts create mode 100644 src/frontend/packages/devkit/src/build/extensions.ts create mode 100644 src/frontend/packages/devkit/src/build/index.transform.ts create mode 100644 src/frontend/packages/devkit/src/build/main.ts create mode 100644 src/frontend/packages/devkit/src/build/sass.ts create mode 100644 src/frontend/packages/devkit/src/builders/builders.json create mode 100644 src/frontend/packages/devkit/src/builders/theme.builder.schema.json create mode 100644 src/frontend/packages/devkit/src/builders/theme.builder.ts create mode 100644 src/frontend/packages/devkit/src/lib/git.metadata.ts create mode 100644 src/frontend/packages/devkit/src/lib/log.ts create mode 100644 src/frontend/packages/devkit/src/lib/packages.ts create mode 100644 src/frontend/packages/devkit/src/lib/schematics.ts create mode 100644 src/frontend/packages/devkit/src/lib/stratos.config.ts create mode 100644 src/frontend/packages/devkit/src/schematics/collection.json create mode 100644 src/frontend/packages/devkit/src/schematics/theme/asset-files/README.md.template rename src/frontend/packages/{core/misc/custom => devkit/src/schematics/theme/asset-files/core}/login-bg.jpg (100%) rename src/frontend/packages/{core/misc/custom => devkit/src/schematics/theme/asset-files/core}/logo.png (100%) rename src/frontend/packages/{core/misc/custom => devkit/src/schematics/theme/asset-files/core}/nav-logo-icon.png (100%) rename src/frontend/packages/{core/misc/custom => devkit/src/schematics/theme/asset-files/core}/nav-logo.png (100%) rename src/frontend/packages/{core/misc/custom => devkit/src/schematics/theme/asset-files}/favicon.ico (100%) create mode 100644 src/frontend/packages/devkit/src/schematics/theme/files/_index.scss.template create mode 100644 src/frontend/packages/devkit/src/schematics/theme/files/package.json.template create mode 100644 src/frontend/packages/devkit/src/schematics/theme/files/sass/custom.scss.template rename src/frontend/packages/{core/sass/mat-colors.scss => devkit/src/schematics/theme/files/sass/mat-colors.scss.template} (100%) create mode 100644 src/frontend/packages/devkit/src/schematics/theme/index.ts create mode 100644 src/frontend/packages/devkit/src/schematics/theme/loader-files/loading.css.template create mode 100644 src/frontend/packages/devkit/src/schematics/theme/loader-files/loading.html.template create mode 100644 src/frontend/packages/devkit/src/schematics/theme/schema.d.ts create mode 100644 src/frontend/packages/devkit/src/schematics/theme/schema.json create mode 100644 src/frontend/packages/devkit/src/schematics/theme/theme-long.md create mode 100644 src/frontend/packages/devkit/tsconfig.json create mode 100644 src/frontend/packages/example-extensions/package.json rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/acme-login/acme-login.component.html (100%) rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/acme-login/acme-login.component.scss (96%) rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/acme-login/acme-login.component.spec.ts (100%) rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/acme-login/acme-login.component.ts (61%) rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/acme-support-info/acme-support-info.component.html (100%) rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/acme-support-info/acme-support-info.component.scss (100%) rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/acme-support-info/acme-support-info.component.spec.ts (100%) rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/acme-support-info/acme-support-info.component.ts (100%) rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/app-action-extension/app-action-extension.component.html (100%) rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/app-action-extension/app-action-extension.component.scss (100%) rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/app-action-extension/app-action-extension.component.spec.ts (100%) rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/app-action-extension/app-action-extension.component.ts (79%) rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/app-tab-extension/app-tab-extension.component.html (100%) rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/app-tab-extension/app-tab-extension.component.scss (100%) rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/app-tab-extension/app-tab-extension.component.spec.ts (100%) rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/app-tab-extension/app-tab-extension.component.ts (83%) rename examples/custom-src/frontend/app/custom/custom-routing.module.ts => src/frontend/packages/example-extensions/src/example-routing.module.ts (80%) rename examples/custom-src/frontend/app/custom/custom.module.ts => src/frontend/packages/example-extensions/src/example.module.ts (61%) rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/nav-extension/example-component/example.component.html (100%) rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/nav-extension/example-component/example.component.scss (100%) rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/nav-extension/example-component/example.component.ts (100%) rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/nav-extension/nav-extension.module.ts (69%) rename {examples/custom-src/frontend/app/custom => src/frontend/packages/example-extensions/src}/nav-extension/nav-extension.routing.ts (100%) create mode 100644 src/frontend/packages/example-extensions/src/public-api.ts create mode 100644 src/frontend/packages/example-theme/_index.scss create mode 100644 src/frontend/packages/example-theme/assets/core/custom/acme_logo.png create mode 100644 src/frontend/packages/example-theme/assets/core/eula.html create mode 100644 src/frontend/packages/example-theme/assets/core/login-bg.jpg create mode 100644 src/frontend/packages/example-theme/assets/core/logo.png create mode 100644 src/frontend/packages/example-theme/assets/favicon.ico create mode 100644 src/frontend/packages/example-theme/loader/loading.css create mode 100644 src/frontend/packages/example-theme/loader/loading.html create mode 100644 src/frontend/packages/example-theme/package.json create mode 100644 src/frontend/packages/example-theme/sass/custom.scss create mode 100644 src/frontend/packages/example-theme/sass/custom/acme-colors.scss create mode 100644 src/frontend/packages/example-theme/sass/custom/acme.scss create mode 100644 src/frontend/packages/shared/karma.conf.js create mode 100644 src/frontend/packages/shared/ng-package.json create mode 100644 src/frontend/packages/shared/package.json create mode 100644 src/frontend/packages/shared/sass/_all-theme.scss create mode 100644 src/frontend/packages/shared/src/components.module.ts create mode 100644 src/frontend/packages/shared/src/components/stratos-title/stratos-title.component.html create mode 100644 src/frontend/packages/shared/src/components/stratos-title/stratos-title.component.scss create mode 100644 src/frontend/packages/shared/src/components/stratos-title/stratos-title.component.spec.ts create mode 100644 src/frontend/packages/shared/src/components/stratos-title/stratos-title.component.theme.scss create mode 100644 src/frontend/packages/shared/src/components/stratos-title/stratos-title.component.ts create mode 100644 src/frontend/packages/shared/src/public-api.ts create mode 100644 src/frontend/packages/shared/tsconfig.lib.json create mode 100644 src/frontend/packages/shared/tsconfig.spec.json create mode 100644 src/frontend/packages/shared/tslint.json delete mode 100644 src/frontend/packages/store/src/actions/snackBar.actions.ts delete mode 100644 src/frontend/packages/store/src/actions/user-favourites-actions/base-user-favorites-action.ts delete mode 100644 src/frontend/packages/store/src/actions/user-favourites-actions/get-user-favorites-action.ts delete mode 100644 src/frontend/packages/store/src/actions/user-favourites-actions/remove-user-favorite-action.ts delete mode 100644 src/frontend/packages/store/src/actions/user-favourites-actions/save-user-favorite-action.ts delete mode 100644 src/frontend/packages/store/src/actions/user-favourites-actions/toggle-user-favorite-action.ts delete mode 100644 src/frontend/packages/store/src/actions/user-favourites-actions/update-user-favorite-metadata-action.ts create mode 100644 src/frontend/packages/store/src/actions/user-favourites.actions.ts rename src/frontend/packages/{core/src/helper.ts => store/src/browser-encoder.ts} (72%) delete mode 100644 src/frontend/packages/store/src/effects/snackBar.effects.spec.ts delete mode 100644 src/frontend/packages/store/src/effects/snackBar.effects.ts create mode 100644 src/frontend/packages/store/src/endpoint-utils.ts create mode 100644 src/frontend/packages/store/src/entity-catalog/entity-catalog-entity/type.helpers.ts rename src/frontend/packages/{core/src/core/extension => store/src}/extension-types.ts (100%) rename src/frontend/packages/{core/src/shared/components/favorites-meta-card => store/src}/favorite-config-mapper.ts (85%) delete mode 100644 src/frontend/packages/store/src/helpers/entity-factory.ts rename src/frontend/packages/{core/src/shared/components/list/data-sources-controllers => store/src/helpers}/local-list.helpers.ts (86%) create mode 100644 src/frontend/packages/store/src/helpers/stratos-entity-factory.ts create mode 100644 src/frontend/packages/store/src/jetstream.ts create mode 100644 src/frontend/packages/store/src/stratos-action-builders.ts create mode 100644 src/frontend/packages/store/src/stratos-entity-catalog.ts create mode 100644 src/frontend/packages/store/src/stratos-entity-generator.ts rename src/frontend/packages/{core/src/core => store/src}/style.service.ts (100%) rename src/frontend/packages/{core/src/core => store/src}/theme.service.ts (92%) create mode 100644 src/frontend/packages/store/src/types/menu-item.types.ts rename src/frontend/packages/{core/src/shared => store/src/types}/shared.types.ts (79%) create mode 100644 src/frontend/packages/store/src/types/theme.types.ts create mode 100644 src/frontend/packages/store/src/types/user-favorite-manager.types.ts rename src/frontend/packages/{core/src/core => store/src}/user-favorite-helpers.ts (75%) rename src/frontend/packages/{core/src/core => store/src}/user-favorite-manager.ts (71%) create mode 100644 src/frontend/packages/theme/_helper.scss create mode 100644 src/frontend/packages/theme/_index.scss create mode 100644 src/frontend/packages/theme/assets/core/login-bg.jpg create mode 100755 src/frontend/packages/theme/assets/core/logo.png create mode 100644 src/frontend/packages/theme/assets/core/nav-logo-icon.png create mode 100644 src/frontend/packages/theme/assets/core/nav-logo.png create mode 100644 src/frontend/packages/theme/assets/favicon.ico rename src/frontend/packages/{core/misc/custom => theme/loader}/loading.css (100%) rename src/frontend/packages/{core/misc/custom => theme/loader}/loading.html (100%) create mode 100644 src/frontend/packages/theme/mat-colors.scss rename src/frontend/packages/{core/sass => theme}/mat-themes.scss (100%) create mode 100644 src/frontend/packages/theme/package.json rename src/test-e2e/helpers/{cf-helpers.ts => cf-e2e-helpers.ts} (100%) create mode 100644 src/test-e2e/marketplace/marketplace-instances.po.ts diff --git a/.codeclimate.yml b/.codeclimate.yml index fd65fd830a..801ada6c4e 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -14,7 +14,7 @@ plugins: markdownlint: enabled: true scss-lint: - enabled: true + enabled: false exclude_patterns: - "config/" - "db/" diff --git a/.gitignore b/.gitignore index 21c2a5101c..b34c80c94b 100644 --- a/.gitignore +++ b/.gitignore @@ -52,7 +52,6 @@ environment.dev.ts #v1 *.iml docs/src -lib tools/test-backend/config/mock.config.json tools/.coverage-karma/ dev-certs/* @@ -81,6 +80,7 @@ deploy/ci/travis/temp/ deploy/kubernetes/console/imagelist.txt .dist/ +dist-devkit/ deploy/uaa/tmp/ src/backend/*/vendor/ .v8flags* @@ -96,8 +96,9 @@ src/jetstream/config.properties src/jetstream/db/dbconf.yml src/jetstream/plugins/monocular/chart-repo/chartrepo -# Customisations - +# Customisations - these can be removed in the future +# Left in for now to prevent these files being checked-in, if they are still present +# from a previous checkout src/frontend/packages/core/favicon.ico src/frontend/packages/core/sass/custom.scss src/frontend/packages/core/assets/eula.html @@ -110,6 +111,9 @@ src/frontend/packages/core/assets/custom src/frontend/packages/core/sass/custom src/frontend/packages/core/src/index.html +# Customisation - generated import module +src/frontend/packages/core/src/_custom-import.module.ts + # Prebuild package stratos-frontend-prebuild.zip diff --git a/CHANGELOG.md b/CHANGELOG.md index 576954b97b..161b42c8ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -155,7 +155,7 @@ Details: ## 2.6.1 -[Full Changelog](https://github.com/cloudfoundry/stratos/compare/2.6.0...2.6.1) +[Full Changelog](https://github.com/SUSE/stratos/compare/2.6.0...2.6.1) This release contains a few fixes: @@ -182,7 +182,7 @@ This release contains two fixes listed below. ## 2.5.3 -[Full Changelog](https://github.com/cloudfoundry-incubator/stratos/compare/2.5.2...2.5.3) +[Full Changelog](https://github.com/suse/stratos/compare/2.5.2...2.5.3) This release contains a number of fixes and improvements: @@ -200,7 +200,7 @@ This release contains a number of fixes and improvements: ## 2.5.2 -[Full Changelog](https://github.com/cloudfoundry-incubator/stratos/compare/2.5.1...2.5.2) +[Full Changelog](https://github.com/suse/stratos/compare/2.5.1...2.5.2) This release contains a number of fixes and improvements: @@ -222,7 +222,7 @@ This release contains a number of fixes and improvements: ## 2.5.1 -[Full Changelog](https://github.com/cloudfoundry-incubator/stratos/compare/2.5.0...2.5.1) +[Full Changelog](https://github.com/suse/stratos/compare/2.5.0...2.5.1) This release contains a number of fixes and improvements: diff --git a/angular.json b/angular.json index 75d6a32748..caa3bceaa9 100644 --- a/angular.json +++ b/angular.json @@ -4,14 +4,17 @@ "newProjectRoot": "src/frontend/packages", "projects": { "stratos": { - "root": "", + "root": "src/frontend/packages", "sourceRoot": "src/frontend/packages", "projectType": "application", "architect": { "build": { - "builder": "@angular-devkit/build-angular:browser", + "builder": "@angular-builders/custom-webpack:browser", "options": { - "aot": true, + "customWebpackConfig": { + "path": "./dist-devkit/build/main.js" + }, + "indexTransform": "./dist-devkit/build/index.transform.js", "preserveSymlinks": true, "outputPath": "dist", "index": "src/frontend/packages/core/src/index.html", @@ -36,10 +39,6 @@ }, "configurations": { "production": { - "budgets": [{ - "type": "anyComponentStyle", - "maximumWarning": "6kb" - }], "optimization": true, "outputHashing": "all", "sourceMap": false, @@ -57,7 +56,7 @@ } }, "serve": { - "builder": "@angular-devkit/build-angular:dev-server", + "builder": "@angular-builders/custom-webpack:dev-server", "options": { "aot": true, "sslCert": "dev-ssl/server.crt", @@ -94,36 +93,37 @@ } } }, + "theme": { + "root": "src/frontend/packages/theme/", + "sourceRoot": "", + "projectType": "library", + "architect": { + "build": { + "builder": "./dist-devkit:stratos-theme", + "options": { + "outputPath": "dist/theme" + } + } + } + }, "core": { "root": "src/frontend/packages/core/", - "sourceRoot": "src/frontend/packages/core/src", - "projectType": "application", + "sourceRoot": "src/frontend/packages/core", + "projectType": "library", "prefix": "app", "schematics": {}, "architect": { "build": { - "builder": "@angular-devkit/build-angular:browser", + "builder": "@angular-devkit/build-ng-packagr:build", "options": { - "aot": true, "preserveSymlinks": true, "outputPath": "dist/core", "index": "src/frontend/packages/core/src/index.html", "main": "src/frontend/packages/core/src/main.ts", "polyfills": "src/frontend/packages/core/src/polyfills.ts", "tsConfig": "src/frontend/packages/core/tsconfig.app.json", - "assets": [ - "src/frontend/packages/core/favicon.ico", - "src/frontend/packages/core/assets", - { - "glob": "**/*", - "input": "custom-src/frontend/assets/custom", - "output": "/core/assets/custom" - } - ], - "styles": [ - "src/frontend/packages/core/src/styles.css", - "src/frontend/packages/cf-autoscaler/src/styles.css" - ], + "assets": [], + "styles": [], "scripts": [] }, "configurations": { @@ -148,23 +148,6 @@ } } }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "options": { - "browserTarget": "core:build" - }, - "configurations": { - "production": { - "browserTarget": "core:build:production" - } - } - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "core:build" - } - }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { @@ -253,6 +236,37 @@ } } }, + "shared": { + "root": "src/frontend/packages/shared", + "sourceRoot": "src/frontend/packages/shared/src", + "projectType": "library", + "prefix": "lib", + "architect": { + "build": { + "builder": "@angular-devkit/build-ng-packagr:build", + "options": { + "tsConfig": "src/frontend/packages/shared/tsconfig.lib.json", + "project": "src/frontend/packages/shared/ng-package.json" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/frontend/packages/shared/src/test.ts", + "tsConfig": "src/frontend/packages/shared/tsconfig.spec.json", + "karmaConfig": "src/frontend/packages/shared/karma.conf.js" + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": ["src/tsconfig.json"], + "tslintConfig": "src/frontend/packages/shared/tslint.json", + "files": ["src/frontend/packages/shared/src/**/*.ts"] + } + } + } + }, "cloud-foundry": { "root": "src/frontend/packages/cloud-foundry", "sourceRoot": "src/frontend/packages/cloud-foundry/src", @@ -349,8 +363,5 @@ "@schematics/angular:directive": { "prefix": "app" } - }, - "cli": { - "_defaultCollection": "@nrwl/angular" } } diff --git a/build/customize-build.js b/build/customize-build.js deleted file mode 100644 index fb98297c18..0000000000 --- a/build/customize-build.js +++ /dev/null @@ -1,316 +0,0 @@ -/** - * Gulp build file for applying cutomizations - */ - -/* eslint-disable angular/log,no-console,no-process-env,angular/json-functions,no-sync */ -(function () { - 'use strict'; - - var gulp = require('gulp'); - var path = require('path'); - var fs = require('fs-extra'); - var yaml = require('js-yaml'); - var replace = require('replace-in-file'); - var execSync = require('child_process').execSync; - - const CUSTOM_YAML_MANIFEST = path.resolve(__dirname, '../src/frontend/packages/core/misc/custom/custom.yaml'); - const INDEX_TEMPLATE = path.resolve(__dirname, '../src/frontend/packages/core/misc/custom/index.html'); - const INDEX_HTML = path.resolve(__dirname, '../src/frontend/packages/core/src/index.html'); - const CUSTOM_METADATA = path.resolve(__dirname, '../custom-src/stratos.yaml'); - const GIT_FOLDER = path.resolve(__dirname, '../.git'); - const GIT_METADATA = path.resolve(__dirname, '../.stratos-git-metadata.json'); - const INDEX_LOADING_HTML_CUSTOM = path.resolve(__dirname, '../custom-src/frontend/loading.html'); - const INDEX_LOADING_HTML_DEFAULT = path.resolve(__dirname, '../src/frontend/packages/core/misc/custom/loading.html'); - const INDEX_LOADING_CSS_CUSTOM = path.resolve(__dirname, '../custom-src/frontend/loading.css'); - const INDEX_LOADING_CSS_DEFAULT = path.resolve(__dirname, '../src/frontend/packages/core/misc/custom/loading.css'); - - // Apply any customizations - // Symlink customizations of the default resources for Stratos - gulp.task('customize', function (cb) { - doShowVersions() - doCustomize(false); - doGenerateIndexHtml(true); - console.log('Finished applying customizations') - cb(); - }); - - // Apply defaults instead of any customizations that are available in custom-src - gulp.task('customize-default', function (cb) { - doCustomize(true); - doGenerateIndexHtml(false); - console.log('Finished applying default customizations') - cb(); - }); - - // Remove all customizations (removes all symlinks as if customize had not been run) - gulp.task('customize-reset', function (cb) { - doCustomize(true, true); - doGenerateIndexHtml(false); - console.log('Finished resetting customizations') - cb(); - }); - - // Store git metadata so we have it when we are running the the non-git world (Docker) - gulp.task('store-git-metadata', function (cb) { - storeGitRepositoryMetadata(); - cb(); - }); - - function doShowVersions() { - console.log('Node Version: ' + process.versions.node || 'N/A'); - try { - var response = execSync('npm --v'); - var npmVersion = response.toString().trim(); - console.log('NPM Version : ' + npmVersion || 'N/A'); - } catch (e) { - console.log('NPM Version : N/A'); - } - } - - function doCustomize(forceDefaults, reset) { - var msg = !forceDefaults ? 'Checking for and applying customizations' : 'Removing customizations and applying defaults'; - var msg = !reset ? msg : 'Removing all customizations'; - console.log(msg); - var customConfig; - - try { - customConfig = yaml.safeLoad(fs.readFileSync(CUSTOM_YAML_MANIFEST, 'utf8')); - } catch (e) { - console.log('Could not read custom.yaml file'); - console.log(e); - process.exit(1); - } - - const baseFolder = path.resolve(__dirname, '../src/frontend/packages/core'); - const customBaseFolder = path.resolve(__dirname, '../custom-src/frontend'); - doCustomizeFiles(forceDefaults, reset, customConfig, baseFolder, customBaseFolder); - doCustomizeFolders(forceDefaults, reset, customConfig, baseFolder, customBaseFolder); - doCustomizeCreateModule(forceDefaults, reset, customConfig, baseFolder, customBaseFolder); - - const backendBaseFolder = path.resolve(__dirname, '../src/jetstream/plugins'); - const backendCustomBaseFolder = path.resolve(__dirname, '../custom-src/jetstream'); - - // There are no defaults for the backend - its the same as removing all of the custom plugins that are there - doCustomizeBackend(forceDefaults || reset, backendBaseFolder, backendCustomBaseFolder); - }; - - function doCustomizeFiles(forceDefaults, reset, customConfig, baseFolder, customBaseFolder) { - // This is where we find the default files, if there are no customizations - const defaultSrcFolder = path.resolve(__dirname, '../src/frontend/packages/core/misc/custom'); - // Symlink custom files - Object.keys(customConfig.files).forEach(file => { - const dest = customConfig.files[file]; - - var srcFile = path.join(defaultSrcFolder, file); - const destFile = path.join(baseFolder, dest); - const customSrcFile = path.join(customBaseFolder, dest); - - // Use the custom file if there is one - if (!forceDefaults && fs.existsSync(customSrcFile)) { - srcFile = customSrcFile; - } - - // Doing an exists check on a symlink will tell if the link dest exists, not the link itself - // So try and delete anyway and catch any exception - try { - const existingLink = fs.readlinkSync(destFile); - fs.unlinkSync(destFile); - } catch (e) { } - - if (!reset) { - fs.symlinkSync(srcFile, destFile); - console.log(' + Linking file : ' + srcFile + ' ==> ' + destFile); - } - }) - - } - - function doCustomizeFolders(forceDefaults, reset, customConfig, baseFolder, customBaseFolder) { - // Symlink custom app folders if they are present - customConfig.folders.forEach(folder => { - var parts = folder.split(':') - var src = parts[0]; - var dest = src; - if (parts.length > 1) { - dest = parts[1]; - } - var destFolder = path.join(baseFolder, dest); - var srcFolder = path.join(customBaseFolder, src); - if (fs.existsSync(destFolder)) { - fs.unlinkSync(destFolder); - } - if (!reset && fs.existsSync(srcFolder)) { - fs.symlinkSync(srcFolder, destFolder); - console.log(' + Linking folder : ' + srcFolder + ' ==> ' + destFolder); - } - }); - } - - // Copy the correct custom module to either import the supplied custom module or provide an empty module - function doCustomizeCreateModule(forceDefaults, reset, customConfig, baseFolder, customBaseFolder) { - const defaultSrcFolder = path.resolve(__dirname, '../src/frontend/packages/core/misc/custom'); - const destFile = path.join(baseFolder, 'src/custom-import.module.ts'); - const customModuleFile = path.join(baseFolder, 'src/custom/custom.module.ts'); - const customRoutingModuleFile = path.join(baseFolder, 'src/custom/custom-routing.module.ts'); - - // Delete the existing file if it exists - if (fs.existsSync(destFile)) { - fs.unlinkSync(destFile) - } - - if (!reset) { - let srcFile = 'custom.module.ts_'; - if (fs.existsSync(customModuleFile)) { - srcFile = 'custom-src.module.ts_'; - if (fs.existsSync(customRoutingModuleFile)) { - srcFile = 'custom-src-routing.module.ts_'; - console.log(' + Found custom module with routing'); - } else { - console.log(' + Found custom module without routing'); - } - } else { - console.log(' + No custom module found - linking empty custom module'); - } - fs.copySync(path.join(defaultSrcFolder, srcFile), destFile); - console.log(' + Copying file : ' + path.join(defaultSrcFolder, srcFile) + ' ==> ' + destFile); - } - } - - function doCustomizeBackend(reset, baseFolder, customBaseFolder) { - // Symlink custom backend plugin folders if they are present - // Get all of the sub-folders in the custom-src/backend folder and symlink - - // Update the git metadata if we can - storeGitRepositoryMetadata(); - - // Remove all existing symlinks first - var existing = fs.readdirSync(baseFolder); - existing.forEach(file => { - var pluginPath = path.join(baseFolder, file); - var stats = fs.lstatSync(pluginPath); - if (stats.isSymbolicLink()) { - fs.unlinkSync(pluginPath); - } - }) - - if (reset) { - // If we are reseting then we are done, just return now - return; - } - - // Check if we have a customization folder - if (!fs.existsSync(customBaseFolder)) { - return; - } - - // Symlink any custom plugins - var plugins = fs.readdirSync(customBaseFolder); - plugins.forEach(file => { - var srcFolder = path.join(customBaseFolder, file); - var stats = fs.statSync(srcFolder); - var destFolder = path.join(baseFolder, file); - if (stats.isDirectory()) { - if (fs.existsSync(destFolder)) { - fs.unlinkSync(destFolder); - } - fs.symlinkSync(srcFolder, destFolder); - } - }) - } - - // Generate index.html from template - function doGenerateIndexHtml(customize) { - - console.log(' + Generating index.html'); - // Copy the default - fs.copySync(INDEX_TEMPLATE, INDEX_HTML); - - // Read custom metadata if we are customizing and the file is present - var metadata = {}; - if (customize && fs.existsSync(CUSTOM_METADATA)) { - try { - metadata = yaml.safeLoad(fs.readFileSync(CUSTOM_METADATA, 'utf8')); - } catch (e) { - console.log('Could not read stratos.yaml file'); - console.log(e); - process.exit(1); - } - } - - if (metadata.title) { - console.log(' + Overridding title to: "' + metadata.title + '"'); - } - - // Patch different page title if there is one - var title = metadata.title || 'Stratos'; - replace.sync({ files: INDEX_HTML, from: /@@TITLE@@/g, to: title }); - - // Read in the stored Git metadata if it is there, default to empty metadata - var gitMetadata = { - project: process.env.project || process.env.STRATOS_PROJECT || '', - branch: process.env.branch || process.env.STRATOS_BRANCH || '', - commit: process.env.commit || process.env.STRATOS_COMMIT || '' - }; - - if (fs.existsSync(GIT_METADATA)) { - gitMetadata = JSON.parse(fs.readFileSync(GIT_METADATA)); - console.log(' + Project Metadata file read OK'); - } else { - console.log(' + Project Metadata file does not exist'); - } - - console.log(" + Project Metadata: " + JSON.stringify(gitMetadata)); - - // Git Information - replace.sync({ files: INDEX_HTML, from: '@@stratos_git_project@@', to: gitMetadata.project }); - replace.sync({ files: INDEX_HTML, from: '@@stratos_git_branch@@', to: gitMetadata.branch }); - replace.sync({ files: INDEX_HTML, from: '@@stratos_git_commit@@', to: gitMetadata.commit }); - - // Date and Time that the build was made (approximately => it is when this script is run) - replace.sync({ files: INDEX_HTML, from: '@@stratos_build_date@@', to: new Date() }); - - // Replace loading indicator - HTML - let loadingHtmlFile = INDEX_LOADING_HTML_DEFAULT; - if (fs.existsSync(INDEX_LOADING_HTML_CUSTOM)) { - loadingHtmlFile = INDEX_LOADING_HTML_CUSTOM - } - const loadingHtml = fs.readFileSync(loadingHtmlFile, 'utf8'); - replace.sync({ files: INDEX_HTML, from: '', to: loadingHtml }); - - // Replace loading indicator - CSS - let loadingCssFile = INDEX_LOADING_CSS_DEFAULT; - if (fs.existsSync(INDEX_LOADING_CSS_CUSTOM)) { - loadingCssFile = INDEX_LOADING_CSS_CUSTOM - } - const loadingCss = fs.readFileSync(loadingCssFile, 'utf8'); - replace.sync({ files: INDEX_HTML, from: '/** @@LOADING_CSS@@ **/', to: loadingCss }); - } - - // We can only do this if we have a git repository checkout - // We'll store this in a file which we will then use - when in environments like Docker, we will run this - // in the host environment so that we can pick it up when we're running in the Docker world - function storeGitRepositoryMetadata() { - // Do we have a git folder? - if (!fs.existsSync(GIT_FOLDER)) { - console.log(' + Unable to store git repository metadata - .git folder not found'); - return; - } - var gitMetadata = { - project: execGit('git config --get remote.origin.url'), - branch: execGit('git rev-parse --abbrev-ref HEAD'), - commit: execGit('git rev-parse HEAD') - }; - - fs.writeFileSync(GIT_METADATA, JSON.stringify(gitMetadata, null, 2)); - } - - function execGit(cmd) { - try { - var response = execSync(cmd + ' 2> /dev/null'); - return response.toString().trim(); - } catch (e) { - return ''; - } - } - -})(); \ No newline at end of file diff --git a/build/fe-build.js b/build/fe-build.js index 0542f8942c..75946dd93c 100644 --- a/build/fe-build.js +++ b/build/fe-build.js @@ -17,9 +17,6 @@ var config = require('./gulp.config'); var paths = config.paths; - // Import customization tasks - require('./customize-build'); - // Clean dist dir gulp.task('clean', function (next) { del(paths.dist + '**/*', { diff --git a/build/tools/v4-migration/migrate.sh b/build/tools/v4-migration/migrate.sh new file mode 100755 index 0000000000..0eb8bfd8c0 --- /dev/null +++ b/build/tools/v4-migration/migrate.sh @@ -0,0 +1,156 @@ +#!/usr/bin/env bash + +# Migrate custom theme and extensions into the new package structure + +set -e +set -o pipefail + +# Script folder +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +STRATOS="`cd "${DIR}/../../..";pwd`" +CUSTOM="${STRATOS}/custom-src" +TEMPLATES=${DIR}/templates + +STRATOS_YML=${STRATOS}/stratos.yaml +PKGS=${STRATOS}/src/frontend/packages + +echo $CUSTOM + +function migrateTitle() { + if [ -f "${CUSTOM}/stratos.yaml" ]; then + DATA=$(cat "${CUSTOM}/stratos.yaml") + + # Make sure we have a Stratos.yaml file + touch ${STRATOS_YML} + + TITLE=$(grep -o 'title: .*' ${CUSTOM}/stratos.yaml) + sed -i.bak -e '/^title:/d' ${STRATOS_YML} + echo -e "${TITLE}" >> ${STRATOS_YML} + fi +} + +function migrateTheme() { + echo "Looking for custom theme" + + # Custom theme if we have custom-src/frontend/sass/custom.scss + CUSTOM_THEME=${CUSTOM}/frontend/sass/custom.scss + if [ ! -f ${CUSTOM_THEME} ]; then + echo "No custom theme found" + return + fi + + echo "Custom theme found ... migrating" + + # Create a new package for the theme + THEME_DIR=${PKGS}/custom_theme + + rm -rf ${THEME_DIR} + mkdir ${THEME_DIR} + mkdir ${THEME_DIR}/sass + mkdir -p ${THEME_DIR}/assets/core + mkdir -p ${THEME_DIR}/assets/custom + mkdir -p ${THEME_DIR}/loader + + cp -R ${CUSTOM}/frontend/sass/* ${THEME_DIR}/sass + + cp ${TEMPLATES}/theme.package.json ${THEME_DIR}/package.json + cp ${TEMPLATES}/_index.scss ${THEME_DIR} + + cp ${CUSTOM}/frontend/loading.* ${THEME_DIR}/loader/ + + # Update the theme in the top-level stratos.yml + sed -i.bak -e 's/theme: .*/theme: \"@custom\/theme\"/g' ${STRATOS_YML} + + # Copy assets + cp -R ${CUSTOM}/frontend/assets/* ${THEME_DIR}/assets/core + # Favicon + cp -R ${CUSTOM}/frontend/favicon.ico ${THEME_DIR}/assets + + # Remove lines from package.json that are not required + if [ ! -f "${THEME_DIR}/assets/favicon.ico" ]; then + sed -i.bak '/"favicon.ico"$/d' ${THEME_DIR}/package.json + fi + + # Loading screen + if [ ! -f "${THEME_DIR}/loader/loading.css" ]; then + sed -i.bak '/loading.css",$/d' ${THEME_DIR}/package.json + fi + + if [ ! -f "${THEME_DIR}/loader/loading.html" ]; then + sed -i.bak '/loading.html"$/d' ${THEME_DIR}/package.json + fi + + rm -rf ${THEME_DIR}/package.json.bak +} + +function migrateExtensions() { + echo "Looking for custom extensions" + + # Custom theme if we have custom-src/frontend/sass/custom.scss + CUSTOM_MODULE=${CUSTOM}/frontend/app/custom/custom.module.ts + if [ ! -f ${CUSTOM_MODULE} ]; then + echo "No custom extensions found" + return + fi + + echo "Custom extensions found ... migrating" + + # Create a new package for the extension(s) + EXT_DIR=${PKGS}/custom_extensions + + rm -rf ${EXT_DIR} + mkdir -p ${EXT_DIR}/src + cp ${TEMPLATES}/ext.package.json ${EXT_DIR}/package.json + + # Copy the source code into the src folder + cp -R ${CUSTOM}/frontend/app/custom/ ${EXT_DIR}/src + cp ${TEMPLATES}/public-api.ts_ ${EXT_DIR}/src/public-api.ts + + #IMPORT_LINE=$(awk '/imports:/{ print NR; exit }' ${CUSTOM_MODULE} + + sed -i '' "s/imports: \[/imports: \[ StratosComponentsModule,/" ${EXT_DIR}/src/custom.module.ts + + echo "import { StratosComponentsModule } from '@stratosui/shared';" | cat - ${EXT_DIR}/src/custom.module.ts > ${EXT_DIR}/temp.ts + mv -f ${EXT_DIR}/temp.ts ${EXT_DIR}/src/custom.module.ts + + if [ -f ${EXT_DIR}/src/custom-routing.module.ts ]; then + echo -e "\nexport * from './custom-routing.module';\n" >> ${EXT_DIR}/src/public-api.ts + + sed -i '' "s/_routingModule/routingModule/g" ${EXT_DIR}/package.json + fi + + # Need to update the import references as things will have moved + pushd ${EXT_DIR}/src + + # Not exhaustive, so extensions developers may need to manually fix imports + find . -name "*.ts" | xargs sed -i '' "s@'../../core@'../../../core/src/core@g" + find . -name "*.ts" | xargs sed -i '' "s@'../core/core.module@'../../core/src/core/core.module@g" + find . -name "*.ts" | xargs sed -i '' "s@'../core/customizations.types@'../../core/src/core/customizations.types@g" + find . -name "*.ts" | xargs sed -i '' 's@../core/md.module@../../core/src/core/md.module@g' + find . -name "*.ts" | xargs sed -i '' "s@'../shared/shared.module@'../../core/src/shared/shared.module@g" + find . -name "*.ts" | xargs sed -i '' "s@'../../../../store/src/app-state@'../../../store/src/app-state@g" + find . -name "*.ts" | xargs sed -i '' "s@'../../features/login/login-page/login-page.component@'../../../core/src/features/login/login-page/login-page.component@g" + + popd +} + +pushd "${STRATOS}" > /dev/null + +# Look for custom-src folder + + +if [ -d "${CUSTOM}" ]; then + echo "Found customizations to migrate" +else + echo "No custom src folder exists - nothing to migrate" + popd > /dev/null + exit 1 +fi + +migrateTitle +migrateTheme +migrateExtensions + +popd > /dev/null + +cat $STRATOS_YML \ No newline at end of file diff --git a/build/tools/v4-migration/templates/_index.scss b/build/tools/v4-migration/templates/_index.scss new file mode 100644 index 0000000000..a8d3f300e1 --- /dev/null +++ b/build/tools/v4-migration/templates/_index.scss @@ -0,0 +1,9 @@ +@import '~@stratosui/theme/helper'; + +// Custom Theme +@import './sass/custom'; + +@function stratos-theme() { + $theme: stratos-theme-helper($stratos-theme); + @return $theme +} \ No newline at end of file diff --git a/src/frontend/packages/store/testing/package.json b/build/tools/v4-migration/templates/ext.package.json similarity index 53% rename from src/frontend/packages/store/testing/package.json rename to build/tools/v4-migration/templates/ext.package.json index 7dd43ac8c1..e22a84b356 100644 --- a/src/frontend/packages/store/testing/package.json +++ b/build/tools/v4-migration/templates/ext.package.json @@ -1,8 +1,12 @@ { - "name": "store/testing", + "name": "@custom/extensions", "version": "0.0.1", "peerDependencies": { "@angular/common": "^6.0.0-rc.0 || ^6.0.0", "@angular/core": "^6.0.0-rc.0 || ^6.0.0" + }, + "stratos": { + "module": "CustomModule", + "_routingModule": "CustomRoutingModule" } } \ No newline at end of file diff --git a/build/tools/v4-migration/templates/public-api.ts_ b/build/tools/v4-migration/templates/public-api.ts_ new file mode 100644 index 0000000000..b78971cc53 --- /dev/null +++ b/build/tools/v4-migration/templates/public-api.ts_ @@ -0,0 +1,3 @@ +// Custom Extensions + +export * from './custom.module'; \ No newline at end of file diff --git a/build/tools/v4-migration/templates/theme.package.json b/build/tools/v4-migration/templates/theme.package.json new file mode 100644 index 0000000000..a588ac2e99 --- /dev/null +++ b/build/tools/v4-migration/templates/theme.package.json @@ -0,0 +1,19 @@ +{ + "name": "@custom/theme", + "version": "0.0.1", + "stratos": { + "assets": { + "assets/core": "core/assets", + "assets/favicon.ico": "favicon.ico" + }, + "theme":{ + "loadingCss": "loader/loading.css", + "loadingHtml": "loader/loading.html" + } + }, + "peerDependencies": { + }, + "scripts": { + "build": "rm -rf ../../../../dist/theme && mkdir -p ../../../../dist/theme && cp -R * ../../../../dist/theme" + } +} diff --git a/deploy/cloud-foundry/build.sh b/deploy/cloud-foundry/build.sh index 0d3f2eb8d3..3d89979b33 100755 --- a/deploy/cloud-foundry/build.sh +++ b/deploy/cloud-foundry/build.sh @@ -39,7 +39,6 @@ else # Build front-end log "Fetching front-end dependencies" $CYAN npm install - npm run customize log "Building front-end" $CYAN npm run build-cf diff --git a/deploy/kubernetes/README.md b/deploy/kubernetes/README.md index 10358b4ad5..6d8d583560 100644 --- a/deploy/kubernetes/README.md +++ b/deploy/kubernetes/README.md @@ -1,401 +1,15 @@ # Deploying in Kubernetes -The following guide details how to deploy Stratos in Kubernetes. +Stratos can be deployed to Kubernetes using [Helm](https://github.com/kubernetes/helm). - -- [Requirements](#requirements) - * [Kubernetes](#kubernetes) - * [Helm](#helm) - * [Storage Class](#storage-class) -- [Deploying Stratos](#deploying-stratos) - * [Deploy using the Helm repository](#deploy-using-the-helm-repository) - * [Deploy using an archive of the Helm Chart](#deploy-using-an-archive-of-the-helm-chart) - * [Deploying using the GitHub repository](#deploying-using-the-github-repository) -- [Accessing the Console](#accessing-the-console) -- [Advanced Topics](#advanced-topics) - * [Using a Load Balancer](#using-a-load-balancer) - * [Using an Ingress Controller](#ingress) - * [Specifying an External IP](#specifying-an-external-ip) - * [Upgrading your deployment](#upgrading-your-deployment) - * [Specifying UAA configuration](#specifying-uaa-configuration) - * [Configuring a local user account](#configuring-a-local-user-account) - * [Specifying a custom Storage Class](#specifying-a-custom-storage-class) - + [Providing Storage Class override](#providing-storage-class-override) - + [Create a default Storage Class](#create-a-default-storage-class) - * [Deploying Stratos with your own TLS certificates](#deploying-stratos-with-your-own-tls-certificates) - * [Using with a Secure Image Repostiory](#using-with-a-secure-image-repository) - * [Installing Nightly Release](#installing-a-nightly-release) - +As part of the Stratos release process, a Helm chart is generated and added to the release artifacts for a given release. In addition, we maintain a Helm Chart repository that can be used to install Stratos from: -## Requirements - -### Kubernetes +`https://cloudfoundry.github.io/stratos` You will need a suitable Kubernetes environment and a machine from which to run the deployment commands. -You will need to have the `kubectl` CLI installed and available on your path. It should be appropriately configured to be able to communicate with your Kubernetes environment. - -### Helm - -We use [Helm](https://github.com/kubernetes/helm) for deploying to Kubernetes. - -You will need the latest Helm client installed on the machine from which you are deploying and you will need to install the Helm Server (Tiller) into you Kubernetes environment. - -- Download the Helm client for your system from https://github.com/kubernetes/helm/releases. -For convenience the guide assumes that the helm client has been added to your PATH. -- To install the Helm server (Tiller) in your Kubernetes environment by running the following command: -``` -helm init -``` - -If you already Helm installed, please make sure it is the latest version. To update your Helm server (Tiller) in your Kubernetes environment after you download the latest Helm release, run the following command: -``` -helm init --upgrade -``` -### Storage Class - -Stratos uses persistent volumes. In order to deploy it in your Kubernetes environment, you must -have a storage class available. - -Without configuration, the Stratos Helm Chart will use the default storage class. If a default storage -class is not available, installation will fail. - -To check if a `default` storage class exists, you can list your configured storage classes with `kubectl get storageclass`. If no storage class has `(default)` after it, then you need to either specify a storage class override or declare a default storage class for your Kubernetes cluster. - -For non-production environments, you may want to use the `hostpath` storage class. See the [SCF instructions](https://github.com/SUSE/scf/wiki/How-to-Install-SCF#choosing-a-storage-class) for details on setting this up. Note that you will need to make this storage class the default storage class, e.g. - -``` -kubectl patch storageclass -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}' -``` -Where `` would be `hostpath` if you follow the SCF instructions. -## Deploying Stratos - -You can deploy Stratos from one of three different sources: - -1. Using our Helm repository -1. Using an archive file containing a given release of our Helm chart -1. Using the latest Helm chart directly from out GitHub repository - -> **Note**: By default each deployment method deploys Stratos with its default user authentication mechanism - which requires a UAA. If you wish to use Stratos without the requirement for a UAA component, use the deployment commands from the following section below: [Configuring a local user account](#configuring-a-local-user-account). All other steps for each deployment method remain the same, as detailed in the following sections. - -### Deploy using the Helm repository - -Add the Helm repository to your helm installation -``` -helm repo add stratos https://cloudfoundry.github.io/stratos -``` -Check the repository was successfully added by searching for the `console` -``` -helm search console -NAME VERSION DESCRIPTION -stratos/console 0.9.0 A Helm chart for deploying Console -``` -To install Stratos. - -``` -helm install stratos/console --namespace=console --name my-console -``` -> **Note**: The previous assumes that a storage class exists in the kubernetes cluster that has been marked as `default`. If no such storage class exists, a specific storage class needs to be specified, please see the following section *Specifying a custom Storage Class*. - -> You can change the namespace (--namespace) and the release name (--name) to values of your choice. - -This will create a Console instance named `my-console` in a namespace called `console` in your Kubernetes cluster. - -After the install, you should be able to access the Console in a web browser by following [the instructions](#accessing-the-console) below. - -### Deploy using an archive of the Helm Chart - -Helm chart archives are available for Stratos releases from our GitHub repository, under releases - see https://github.com/suse/stratos/releases. - -Download the appropriate release `console-helm-chart.X.Y.Z.tgz` from the GitHub repository and unpack the archive to a local folder. The Helm Chart will be extracted to a sub-folder named `console`. - -Deploy Stratos with: - -``` -helm install console --namespace=console --name my-console -``` - -### Deploying using the GitHub repository - -> Note: Deploying using the GitHub repository uses the latest Stratos images that are built nightly (tagged `latest`). While these contain the very latest updates, they may contain bugs or instabilities. - -Clone the Stratos GitHub repository: - -``` -git clone https://github.com/suse/stratos.git -``` - -Open a terminal and cd to the `deploy/kubernetes` directory: - -``` -$ cd deploy/kubernetes -``` - -Run helm install: - -``` -$ helm install console --namespace console --name my-console -``` - -> You can change the namespace (--namespace) and the release name (--name) to values of your choice. - -This will create a Console instance named `my-console` in a namespace called `console` in your Kubernetes cluster. - -You should now be able to access the Console in a web browser by following the instructions below. - -## Accessing the Console - -To check the status of the instance use the following command: -``` -helm status my-console -``` - -> Note: Replace `my-console` with the value you used for the `name` parameter, or if you did not provide one, use the `helm list` command to find the release name that was automatically generated for you. - -Once the instance is in `DEPLOYED` state, find the IP address and port that the console is running on: - -``` -$ helm status my-console | grep ui-ext -console-ui-ext 10.0.0.162 192.168.77.1 80:30933/TCP,443:30941/TCP 1m -``` - -In this example, the IP address is `192.168.77.1` and the node-port is `30941`, so the console is accessible on: - -`https://192.168.77.1:30941` - -The values will be different for your environment. - -You can now access the UI. - -> You may see a certificate warning which you can safely ignore. - -To login use the following credentials detailed [here](../../docs/access.md). - -> Note: For some environments like Minikube, you are not given an IP Address - it may show as ``. In this case, run `kubectl cluster-info` and use the IP address of your node shown in the output of this command. - -## Advanced Topics -### Using a Load Balancer -If your Kubernetes deployment supports automatic configuration of a load balancer (e.g. Google Container Engine), specify the parameters `console.service.type=LoadBalancer` when installing. - -``` -helm install stratos/console --namespace=console --name my-console --set console.service.type=LoadBalancer -``` - -### Using an Ingress Controller - -If your Kubernetes Cluster supports Ingress, you can expose Stratos through Ingress by supplying the appropriate ingress configuration when installing. - -This configuration is described below: - -|Parameter|Description|Default| -|----|---|---| -|console.service.ingress.enabled|Enables ingress|false| -|console.service.ingress.annotations|Annotations to be added to the ingress resource.|{}| -|console.service.ingress.extraLabels|Additional labels to be added to the ingress resource.|{}| -|console.service.ingress.host|The host name that will be used for the Stratos service.|| -|console.service.ingress.secretName|The existing TLS secret that contains the certificate for ingress.|| - -You must provide `console.service.ingress.host` when enabling ingress. - -By default a certificate will be generated for TLS. You can provide your own certificate by creating a secret and specifying this with `console.service.ingress.secretName`. - -> Note: If you do not supply `console.service.ingress.host` but do supply `env.DOMAIN` then the host `console.[env.DOMAIN]` will be used. - -### Specifying an External IP - -If the kubernetes cluster supports external IPs for services (see [ Service External IPs](https://kubernetes.io/docs/concepts/services-networking/service/#external-ips)), then the following arguments can be provided. In this following example the dashboard will be available at `https://192.168.100.100:5000`. - -``` -helm install stratos/console --namespace=console --name my-console --set console.service.externalIPs={192.168.100.100} --set console.service.servicePort=5000 -``` - -### Upgrading your deployment - -To upgrade your instance when using the Helm repository, fetch any updates to the repository: - -``` -$ helm repo update -``` - -To update an instance, the following assumes your instance is called `my-console`, and overrides have been specified in a file called `overrides.yaml`. - -``` -$ helm upgrade -f overrides.yaml my-console stratos/console -``` - -After the upgrade, perform a `helm list` to ensure your console is the latest version. - - - -### Specifying UAA configuration - -When deploying with SCF, the `scf-config-values.yaml` (see [SCF Wiki link](https://github.com/SUSE/scf/wiki/How-to-Install-SCF#configuring-the-deployment)) can be supplied when installing Stratos. -``` -$ helm install stratos/console -f scf-config-values.yaml -``` - -Alternatively, you can supply the following configuration. Edit according to your environment and save to a file called `uaa-config.yaml`. -``` -uaa: - protocol: https:// - port: 2793 - host: uaa.cf-dev.io - consoleClient: cf - consoleClientSecret: - consoleAdminIdentifier: cloud_controller.admin - skipSSLValidation: false -``` - -To install Stratos with the above specified configuration: -``` -$ helm install stratos/console -f uaa-config.yaml -``` - -### Configuring a local user account - -This allows for deployment without a UAA. To enable the local user account, supply a password for the local user in the deployment command, as follows. All other steps for each deployment method should be followed as in the preceding sections above. - -To deploy using our Helm repository: - -``` -helm install stratos/console --namespace=console --name my-console --set console.localAdminPassword= -``` - -To deploy using an archive file containing a given release of our Helm chart - -``` -helm install console --namespace=console --name my-console --set console.localAdminPassword= -``` - -To deploy using the latest Helm chart directly from out GitHub repository - -``` -$ helm install console --namespace console --name my-console --set console.localAdminPassword= -``` - -For console access via the local user account see: [*Accessing the Console*](#accessing-the-console) - -### Specifying a custom Storage Class - -If no default storage class has been defined in the Kubernetes cluster. The Stratos helm chart will fail to deploy successfully. To check if a `default` storage class exists, you can list your configured storage classes with `kubectl`. If no storage class has `(default)` after it, then you need to either specify a storage class override or declare a default storage class for your Kubernetes cluster. - -#### Providing Storage Class override -``` -$ kubectl get storageclass -NAME TYPE -ssd kubernetes.io/host-path -persistent kubernetes.io/host-path -``` - -For instance to use the storage class `persistent` to deploy Console persistent volume claims, store the following to a file called `override.yaml`. - -``` ---- -storageClass: persistent -``` - -If you want MariaDB to use a specific storage class (which can be different to that used for the other components), then specify the following: -``` ---- -storageClass: persistent -mariadb: - persistence: - storageClass: persistent -``` - -Run Helm with the override: -``` -helm install -f override.yaml stratos/console -``` - -#### Create a default Storage Class -Alternatively, you can configure a storage class with `storageclass.kubernetes.io/is-default-class` set to `true`. For instance the following storage class will be declared as the default. If you don't have the `hostpath` provisioner available in your local cluster, please follow the instructions on [link] (https://github.com/kubernetes-incubator/external-storage/tree/master/docs/demo/hostpath-provisioner), to deploy one. - -If the hostpath provisioner is available, save the file to `storageclass.yaml` - -``` ---- -kind: StorageClass -apiVersion: storage.k8s.io/v1beta1 -metadata: - name: default - annotations: - storageclass.kubernetes.io/is-default-class: "true" -provisioner: kubernetes.io/host-path # Or whatever the local hostpath provisioner is called -``` - -To create it in your kubernetes cluster, execute the following. -``` -kubectl create -f storageclass.yaml -``` - -See [Storage Class documentation] ( https://kubernetes.io/docs/tasks/administer-cluster/change-default-storage-class/) for more insformation. - -### Deploying Stratos with your own TLS certificates - -By default the console will generate self-signed certificates for demo purposes. To configure Stratos UI to use your provided TLS certificates set the `consoleCert` and `consoleCertKey` overrides. - -``` -consoleCert: | - -----BEGIN CERTIFICATE----- - MIIDXTCCAkWgAwIBAgIJAJooOiQWl1v1MA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV - ... - -----END CERTIFICATE----- -consoleCertKey: | - -----BEGIN PRIVATE KEY----- - MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDV9+ySh0xZzM41 - .... - -----END PRIVATE KEY----- -``` -Assuming the above is stored in a file called `override-ssl.yaml`, install the chart with the override specified. -``` -helm install -f override-ssl.yaml stratos/console --namespace console -``` - -### Using with a Secure Image Repository -If you are deploying the helm chart against images that are hosted in a secure image repository provide the following parameters ( store the following to a file called `docker-registry-secrets.yaml`). - - -``` -kube: - registry: - hostname: mysecure-dockerregistry.io - username: john.appleseed - password: sup3rs3cur3 - # `email` is an optional field - email: john.appleseed@foobar.com -``` - -Deploy the chart with the provided parameters: -``` -helm install -f docker-registry-secrets.yaml stratos/console -``` - -### Installing a Nightly Release -Nightly releases are pushed with a `dev` tag. These are strictly for development purposes and should be considered unstable and may contain bugs. - -To install the nightly release: - -Update your Helm repositories to ensure you have the latest nightly release information: - -``` -helm repo update -``` - -List all versions of the console, to determine the tag. -``` -helm search console -l -NAME CHART VERSION DESCRIPTION -stratos/console 2.0.0-dev-9a5611dc A Helm chart for deploying Stratos UI Consoles -stratos/console 1.0.2 A Helm chart for deploying Stratos UI Console -stratos/console 1.0.0 A Helm chart for deploying Stratos UI Console -stratos/console 0.9.9 A Helm chart for deploying Stratos UI Console -stratos/console 0.9.8 A Helm chart for deploying Stratos UI Console - -``` -Install +You will need to have both the `kubectl` and `helm` CLIs installed and available on your path. It should be appropriately configured to be able to communicate with your Kubernetes environment. -``` -helm install stratos/console --namespace=console --name my-console --version 2.0.0-dev-9a5611dc -``` +The Stratos Helm chart contains a `README.md` file that contains installation instructions and configuration documentation. +This document is also available in our GitHub repository here: [README.md](https://github.com/cloudfoundry/stratos/blob/master/deploy/kubernetes/console/README.md). diff --git a/deploy/stratos-ui-release/packages/backend/pre_packaging b/deploy/stratos-ui-release/packages/backend/pre_packaging index 99faea2123..ba252553a7 100644 --- a/deploy/stratos-ui-release/packages/backend/pre_packaging +++ b/deploy/stratos-ui-release/packages/backend/pre_packaging @@ -10,7 +10,6 @@ curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh # Build backend npm install -npm run customize export PATH=$PATH:$PWD/node_modules/.bin npm run bosh-build-backend diff --git a/deploy/stratos-ui-release/packages/frontend/pre_packaging b/deploy/stratos-ui-release/packages/frontend/pre_packaging index c2c28d2215..440e323fa2 100644 --- a/deploy/stratos-ui-release/packages/frontend/pre_packaging +++ b/deploy/stratos-ui-release/packages/frontend/pre_packaging @@ -4,7 +4,6 @@ set -e -x cd ${BUILD_DIR}/stratos npm install -npm run customize export PATH=$PATH:$PWD/node_modules/.bin npm run build diff --git a/docs/customizing.md b/docs/customizing.md index 496b7fddb6..e81f9ebabd 100644 --- a/docs/customizing.md +++ b/docs/customizing.md @@ -8,11 +8,44 @@ Stratos provides a mechanism for customization - the following customizations ar - Adding new functionality - Changing the initial loading indicator +# Migrating to Stratos V4 Customization +In V4 there are breaking customization changes. These changes allow a much improved approach to extensions by opening the door to npm style plugins. +To aid in migrating we've provided these instructions. + +1) Before updating to the latest code... + 1) Run `npm run customize-reset` to remove all previously created sym links. + 2) Read through the customization documentation below to get a better understanding of the new process. +1) Update your codebase with the desired v4 code. +1) Run `npm install` (only required first time, this will ensure you have the required version of Angular). +1) Change directory to `./build/tools/v4-migration` and run the migration script `./migrate.sh`. + - This will copy your customizations from `custom-src` to a new Angular package `src/frontend/packages/custom_extensions`. +1) Check that the new package exports your custom module and if applicable your custom-routing module. + - The migrate script should do this in `src/frontend/packages/custom_extensions/src/public-api.ts`. +1) Check that your ts config file defines the public api file. + - `src/tsconfig.json` file's `compilerOptions/paths` section should contain something like `"@custom/extensions": ["frontend/packages/custom_extensions/src/public-api.ts"]`. +1) Check that your new package's package.json defines your custom module and if application custom-routing module. + - See `src/frontend/packages/suse_extensions/package.json` file's `stratos` section. + - Note your `routingModule` entry label should not have a preceding `_`. +1) Build Stratos in your usual way, for instance `npm run build`. + - It could be that this fails due to TypeScript import issues, if so go through these and fix. + - During build time the custom packages will be discovered and output, see section starting `Building with these extensions`. These should contain the modules your require. +1) Run Stratos your usual way. Ensure you can navigate to all your custom parts. +1) Once you are happy everything works as intended remove the old `./custom-src` directory and commit you changes. + ## Approach In order to customize Stratos, you will need to fork the Stratos GitHub repository and apply customizations in your fork. Our aim is to minimize any merge conflicts that might occur when re-basing your fork with the upstream Stratos repository. -All customizations are placed within a top-level folder named `custom-src`. This folder should only exist in forks and will not exist in the main Stratos repository, so any changes made within this folder should be free from merge conflicts. + +Customizations are placed in angular packages in the folder named `src/frontend/packages`. In the future you will be able to host these packages in npm and bring them into Stratos in the usual npm dependency way. + +Each package should contain custom Stratos configuration in it's package.json pointing to the modules it will be required to import. + +stratos.yaml +custom theme +custom styles +custom assets + The Stratos approach to customization uses symbolic links. We maintain a default set of resources in the folder `src/misc/custom`. When you run `npm install` or when you explicitly run `npm run customize`, a gulp task (in the file `build/fe-build.js`) runs and creates symbolic links, linking the required files to their expected locations withing the `src` folder. diff --git a/package-lock.json b/package-lock.json index 200d6f47de..eec8e18f4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,20 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@angular-builders/custom-webpack": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@angular-builders/custom-webpack/-/custom-webpack-9.2.0.tgz", + "integrity": "sha512-0ivkjENONFm0oNy6hdCod4YaT4dUk80KuP9+eDliWuZIA70yKQgIYMLul0bz6/i+Cm24PaZ2tq4w7kW7AuSMoA==", + "dev": true, + "requires": { + "@angular-devkit/architect": ">=0.900.0 < 0.1000.0", + "@angular-devkit/build-angular": ">=0.900.0 < 0.1000.0", + "@angular-devkit/core": "^9.0.0", + "lodash": "^4.17.10", + "ts-node": "^8.5.2", + "webpack-merge": "^4.2.1" + } + }, "@angular-devkit/architect": { "version": "0.901.7", "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.901.7.tgz", @@ -618,6 +632,17 @@ "resolved": "https://registry.npmjs.org/@angular/router/-/router-9.1.9.tgz", "integrity": "sha512-4u+CWMPB4hCkAsFCEzC94YEWT0wVozqGkc/Dortt2hFaqvZpIegg6iJVZlDxuyDjzFYBPnnbTDdgiTTA8ckfuA==" }, + "@apidevtools/json-schema-ref-parser": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.1.tgz", + "integrity": "sha512-Qsdz0W0dyK84BuBh5KZATWXOtVDXIF2EeNRzpyWblPUeAmnIokwWcwrpAm5pTPMjuWoIQt9C67X3Af1OlL6oSw==", + "dev": true, + "requires": { + "@jsdevtools/ono": "^7.1.2", + "call-me-maybe": "^1.0.1", + "js-yaml": "^3.13.1" + } + }, "@babel/code-frame": { "version": "7.5.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", @@ -2912,6 +2937,12 @@ } } }, + "@jsdevtools/ono": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.2.tgz", + "integrity": "sha512-qS/a24RA5FEoiJS9wiv6Pwg2c/kiUo3IVUQcfeM9JvsR6pM8Yx+yl/6xWYLckZCT5jpLNhslgjiA8p/XcGyMRQ==", + "dev": true + }, "@ngrx/effects": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-9.2.0.tgz", @@ -3944,6 +3975,12 @@ "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", "dev": true }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, "anymatch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", @@ -5250,6 +5287,12 @@ } } }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", + "dev": true + }, "caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", @@ -5465,6 +5508,20 @@ "integrity": "sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==", "dev": true }, + "cli-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.0.tgz", + "integrity": "sha512-a0VZ8LeraW0jTuCkuAGMNufareGHhyZU9z8OGsW0gXd1hZGi1SRuNRXdbGkraBBKnhyUhyebFWnRbp+dIn0f0A==", + "dev": true, + "requires": { + "ansi-regex": "^2.1.1", + "d": "^1.0.1", + "es5-ext": "^0.10.51", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.14", + "timers-ext": "^0.1.7" + } + }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -7113,7 +7170,7 @@ }, "duplexer": { "version": "0.1.1", - "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, @@ -7816,7 +7873,7 @@ }, "event-stream": { "version": "3.3.4", - "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", "dev": true, "requires": { @@ -8760,7 +8817,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, "optional": true }, "function-bind": { @@ -10388,6 +10444,12 @@ "isobject": "^3.0.1" } }, + "is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true + }, "is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", @@ -11022,6 +11084,43 @@ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "dev": true }, + "json-schema-ref-parser": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-9.0.1.tgz", + "integrity": "sha512-KLrCjRjW5hMXxsX4osVBWpwixXL9NtICfpyNNS0eHguN5mP/I4UatI7i7PFS8jU94b1NHF4EbirACdCn0RFPBA==", + "dev": true, + "requires": { + "@apidevtools/json-schema-ref-parser": "9.0.1" + } + }, + "json-schema-to-typescript": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-9.1.0.tgz", + "integrity": "sha512-9/yDXQQyqtRDxohQGRCKht4Wjfg73TALi1yzy651EOo71a6aKFtIm2WUbDWSf8OitFGukUn00dx4t1kg0W6O4Q==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.4", + "cli-color": "^2.0.0", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "json-schema-ref-parser": "^9.0.1", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.15", + "minimist": "^1.2.5", + "mkdirp": "^1.0.4", + "mz": "^2.7.0", + "prettier": "^2.0.5", + "stdin": "0.0.1" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } + } + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -11248,13 +11347,6 @@ "path-exists": "^4.0.0" } }, - "fsevents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", - "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", - "dev": true, - "optional": true - }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -12017,6 +12109,15 @@ } } }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "dev": true, + "requires": { + "es5-ext": "~0.10.2" + } + }, "magic-string": { "version": "0.25.7", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", @@ -12140,7 +12241,7 @@ }, "map-stream": { "version": "0.1.0", - "resolved": "http://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", "dev": true }, @@ -12219,12 +12320,6 @@ "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "dev": true }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "optional": true - }, "glob-parent": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", @@ -12331,6 +12426,22 @@ } } }, + "memoizee": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", + "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.45", + "es6-weak-map": "^2.0.2", + "event-emitter": "^0.3.5", + "is-promise": "^2.1", + "lru-queue": "0.1", + "next-tick": "1", + "timers-ext": "^0.1.5" + } + }, "memory-fs": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", @@ -12854,6 +12965,17 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -14933,7 +15055,7 @@ }, "pause-stream": { "version": "0.0.11", - "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", "dev": true, "requires": { @@ -15802,6 +15924,12 @@ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, + "prettier": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", + "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", + "dev": true + }, "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -18516,7 +18644,7 @@ }, "split": { "version": "0.3.3", - "resolved": "http://registry.npmjs.org/split/-/split-0.3.3.tgz", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", "dev": true, "requires": { @@ -18628,6 +18756,12 @@ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "dev": true }, + "stdin": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/stdin/-/stdin-0.0.1.tgz", + "integrity": "sha1-0wQZgarsPf28d6GzjWNy449ftx4=", + "dev": true + }, "stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", @@ -18693,7 +18827,7 @@ }, "stream-combiner": { "version": "0.0.4", - "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", "dev": true, "requires": { @@ -19604,6 +19738,24 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "dev": true, + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -19651,6 +19803,16 @@ "setimmediate": "^1.0.4" } }, + "timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "dev": true, + "requires": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, "timsort": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", @@ -20657,13 +20819,6 @@ "to-regex-range": "^5.0.1" } }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true - }, "glob-parent": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", @@ -20793,13 +20948,6 @@ "to-regex": "^3.0.1" } }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, - "optional": true - }, "readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", @@ -20844,6 +20992,13 @@ "to-regex-range": "^2.1.0" } }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true + }, "is-binary-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", diff --git a/package.json b/package.json index 8e5d3381a3..17dd16aca8 100644 --- a/package.json +++ b/package.json @@ -14,12 +14,12 @@ "build": "node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng build --prod", "build-cf": "node --max_old_space_size=1500 --gc_interval=100 node_modules/@angular/cli/bin/ng build --prod", "build-dev": "ng build --dev", - "prebuild-ui": "npm run customize && npm run build && gulp package-prebuild", + "prebuild-ui": "npm run build && gulp package-prebuild", "ng": "ng", - "start": "npm run customize && ng serve", - "start-high-mem": "npm run customize && node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng serve", - "start-dev-high-mem": "npm run customize && node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng serve --aot=false", - "start-prod-high-mem": "npm run customize && node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng serve --prod", + "start": "ng serve", + "start-high-mem": "node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng serve", + "start-dev-high-mem": "node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng serve --aot=false", + "start-prod-high-mem": "node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng serve --prod", "start-dev": "ng serve --aot=false", "test-clean": "./build/clean-test-reports.sh", "test-reports": "./build/show-test-reports.sh", @@ -38,11 +38,9 @@ "headless-e2e": "xvfb-run --server-args='-screen 0 1920x1080x24' protractor ./protractor.conf.js", "climate": "codeclimate analyze $(git diff --name-only master)", "gate-check": "npm run lint && npm run test-headless", - "customize": "gulp customize", - "customize-default": "gulp customize-default", - "customize-reset": "gulp customize-reset", "store-git-metadata": "gulp store-git-metadata", - "postinstall": "npm run customize && gulp dev-setup" + "postinstall": "gulp dev-setup && npm run build-devkit", + "build-devkit": "cd src/frontend/packages/devkit && npm run build" }, "author": "", "license": "Apache-2.0", @@ -96,12 +94,16 @@ "node": "12.13.0" }, "devDependencies": { + "@angular-builders/custom-webpack": "^9.1.0", + "@angular-devkit/architect": "^0.901.7", "@angular-devkit/build-angular": "~0.901.5", "@angular-devkit/build-ng-packagr": "~0.901.5", + "@angular-devkit/core": "^9.1.7", "@angular-devkit/schematics": "^9.1.5", "@angular/cli": "^9.1.5", "@angular/compiler-cli": "^9.1.6", "@angular/language-service": "^9.1.6", + "@schematics/angular": "^9.1.5", "@types/jasmine": "^3.5.10", "@types/jasminewd2": "~2.0.8", "@types/karma": "^5.0.0", @@ -111,6 +113,7 @@ "browserstack-local": "^1.4.5", "codecov": "^3.6.5", "codelyzer": "^5.1.2", + "copy-webpack-plugin": "5.1.1", "delete": "^1.1.0", "fs-extra": "^9.0.0", "globby": "^11.0.0", @@ -122,6 +125,7 @@ "jasmine-core": "~3.5.0", "jasmine-spec-reporter": "~5.0.1", "js-yaml": "~3.13.1", + "json-schema-to-typescript": "^9.1.0", "karma": "~5.0.1", "karma-chrome-launcher": "~3.1.0", "karma-cli": "~2.0.0", diff --git a/src/frontend/packages/cf-autoscaler/src/cf-autoscaler.module.ts b/src/frontend/packages/cf-autoscaler/src/cf-autoscaler.module.ts index e7463eee23..e470be161e 100644 --- a/src/frontend/packages/cf-autoscaler/src/cf-autoscaler.module.ts +++ b/src/frontend/packages/cf-autoscaler/src/cf-autoscaler.module.ts @@ -52,7 +52,6 @@ const customRoutes: Routes = [ ], declarations: [ AutoscalerTabExtensionComponent - ], - entryComponents: [AutoscalerTabExtensionComponent] + ] }) export class CfAutoscalerModule { } diff --git a/src/frontend/packages/cf-autoscaler/src/core/autoscaler.module.ts b/src/frontend/packages/cf-autoscaler/src/core/autoscaler.module.ts index 2faabe86af..cb76b829f2 100644 --- a/src/frontend/packages/cf-autoscaler/src/core/autoscaler.module.ts +++ b/src/frontend/packages/cf-autoscaler/src/core/autoscaler.module.ts @@ -83,13 +83,6 @@ import { AutoscalerRoutingModule } from './autoscaler.routing'; ], providers: [ ApplicationService - ], - entryComponents: [ - AppAutoscalerMetricChartCardComponent, - AppAutoscalerComboChartComponent, - AppAutoscalerComboSeriesVerticalComponent, - TableCellAutoscalerEventChangeComponent, - TableCellAutoscalerEventStatusComponent ] }) export class AutoscalerModule { } diff --git a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.spec.ts index eff100853b..b96d66bdb5 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.spec.ts @@ -15,6 +15,9 @@ import { import { RunningInstancesComponent, } from '../../../../cloud-foundry/src/shared/components/running-instances/running-instances.component'; +import { + cfCurrentUserPermissionsService, +} from '../../../../cloud-foundry/src/user-permissions/cf-user-permissions-checkers'; import { ApplicationServiceMock } from '../../../../cloud-foundry/test-framework/application-service-helper'; import { CoreModule } from '../../../../core/src/core/core.module'; import { SharedModule } from '../../../../core/src/shared/shared.module'; @@ -48,7 +51,8 @@ describe('AutoscalerTabExtensionComponent', () => { providers: [ DatePipe, { provide: ApplicationService, useClass: ApplicationServiceMock }, - TabNavService + TabNavService, + ...cfCurrentUserPermissionsService ] }) .compileComponents(); diff --git a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.ts b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.ts index dfee79bace..9b3b56c9b2 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.ts @@ -2,15 +2,25 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { MatSnackBar, MatSnackBarRef, SimpleSnackBar } from '@angular/material/snack-bar'; import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; -import { combineLatest, Observable, Subscription } from 'rxjs'; -import { distinctUntilChanged, filter, first, map, pairwise, publishReplay, refCount } from 'rxjs/operators'; +import { combineLatest, Observable, of, Subscription } from 'rxjs'; +import { distinctUntilChanged, filter, first, map, pairwise, publishReplay, refCount, switchMap } from 'rxjs/operators'; -import { applicationEntityType } from '../../../../cloud-foundry/src/cf-entity-types'; -import { createEntityRelationPaginationKey } from '../../../../cloud-foundry/src/entity-relations/entity-relations.types'; +import { cfEntityCatalog } from '../../../../cloud-foundry/src/cf-entity-catalog'; +import { + applicationEntityType, + organizationEntityType, + spaceEntityType, +} from '../../../../cloud-foundry/src/cf-entity-types'; +import { + createEntityRelationKey, + createEntityRelationPaginationKey, +} from '../../../../cloud-foundry/src/entity-relations/entity-relations.types'; import { ApplicationMonitorService } from '../../../../cloud-foundry/src/features/applications/application-monitor.service'; import { ApplicationService } from '../../../../cloud-foundry/src/features/applications/application.service'; import { getGuids } from '../../../../cloud-foundry/src/features/applications/application/application-base.component'; +import { CfCurrentUserPermissions } from '../../../../cloud-foundry/src/user-permissions/cf-user-permissions-checkers'; import { StratosTab, StratosTabType } from '../../../../core/src/core/extension/extension-service'; +import { CurrentUserPermissionsService } from '../../../../core/src/core/permissions/current-user-permissions.service'; import { safeUnsubscribe } from '../../../../core/src/core/utils.service'; import { ConfirmationDialogConfig } from '../../../../core/src/shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from '../../../../core/src/shared/components/confirmation-dialog.service'; @@ -52,9 +62,32 @@ import { appAutoscalerAppMetricEntityType, autoscalerEntityFactory } from '../.. link: 'autoscale', icon: 'meter', iconFont: 'stratos-icons', - hidden: (store: Store, esf: EntityServiceFactory, activatedRoute: ActivatedRoute) => { + hidden: (store: Store, esf: EntityServiceFactory, activatedRoute: ActivatedRoute, cups: CurrentUserPermissionsService) => { const endpointGuid = getGuids('cf')(activatedRoute) || window.location.pathname.split('/')[2]; - return isAutoscalerEnabled(endpointGuid, esf).pipe(map(enabled => !enabled)); + const appGuid = getGuids()(activatedRoute) || window.location.pathname.split('/')[3]; + const appEntService = cfEntityCatalog.application.store.getEntityService(appGuid, endpointGuid, { + includeRelations: [ + createEntityRelationKey(applicationEntityType, spaceEntityType), + createEntityRelationKey(spaceEntityType, organizationEntityType), + ], + populateMissing: true + }) + + const canEditApp$ = appEntService.waitForEntity$.pipe( + switchMap(app => cups.can( + CfCurrentUserPermissions.APPLICATION_EDIT, + endpointGuid, + app.entity.entity.space.entity.organization_guid, + app.entity.entity.space.metadata.guid + )), + ) + + const autoscalerEnabled = isAutoscalerEnabled(endpointGuid, esf); + + return canEditApp$.pipe( + switchMap(canEditSpace => canEditSpace ? autoscalerEnabled : of(false)), + map(can => !can) + ) } }) @Component({ @@ -130,7 +163,7 @@ export class AutoscalerTabExtensionComponent implements OnInit, OnDestroy { private paginationMonitorFactory: PaginationMonitorFactory, private appAutoscalerPolicySnackBar: MatSnackBar, private appAutoscalerScalingHistorySnackBar: MatSnackBar, - private confirmDialog: ConfirmationDialogService, + private confirmDialog: ConfirmationDialogService ) { } ngOnInit() { diff --git a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-config.service.spec.ts b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-config.service.spec.ts index 56fe3b5865..fcfbc0bf81 100644 --- a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-config.service.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-config.service.spec.ts @@ -5,9 +5,6 @@ import { inject, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { createEmptyStoreModule } from '@stratosui/store/testing'; -import { GetApplication } from '../../../../../cloud-foundry/src/actions/application.actions'; -import { cfEntityFactory } from '../../../../../cloud-foundry/src/cf-entity-factory'; -import { applicationEntityType } from '../../../../../cloud-foundry/src/cf-entity-types'; import { ApplicationsModule } from '../../../../../cloud-foundry/src/features/applications/applications.module'; import { generateTestApplicationServiceProvider, @@ -15,7 +12,6 @@ import { import { CoreModule } from '../../../../../core/src/core/core.module'; import { SharedModule } from '../../../../../core/src/shared/shared.module'; import { AppTestModule } from '../../../../../core/test-framework/core-test.helper'; -import { generateTestEntityServiceProvider } from '../../../../../core/test-framework/entity-service.helper'; import { EntityCatalogHelper } from '../../../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog.service'; import { EntityServiceFactory } from '../../../../../store/src/entity-service-factory.service'; import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; @@ -35,11 +31,6 @@ describe('CfAppAutoscalerEventsConfigService', () => { EntityServiceFactory, EntityMonitorFactory, EntityCatalogHelper, - generateTestEntityServiceProvider( - appGuid, - cfEntityFactory(applicationEntityType), - new GetApplication(appGuid, cfGuid) - ), generateTestApplicationServiceProvider(appGuid, cfGuid), HttpClient, ], diff --git a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-config.service.ts b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-config.service.ts index 98d5b15a48..8732d20bfb 100644 --- a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-config.service.ts +++ b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-config.service.ts @@ -8,8 +8,9 @@ import { ApplicationService } from '../../../../../cloud-foundry/src/features/ap import { ITableColumn } from '../../../../../core/src/shared/components/list/list-table/table.types'; import { IListConfig, ListConfig, ListViewTypes } from '../../../../../core/src/shared/components/list/list.component.types'; import { MetricsRangeSelectorService } from '../../../../../core/src/shared/services/metrics-range-selector.service'; -import { ITimeRange, MetricQueryType } from '../../../../../core/src/shared/services/metrics-range-selector.types'; +import { ITimeRange } from '../../../../../core/src/shared/services/metrics-range-selector.types'; import { APIResource } from '../../../../../store/src/types/api.types'; +import { MetricQueryType } from '../../../../../store/src/types/metric.types'; import { AppAutoscalerEvent } from '../../../store/app-autoscaler.types'; import { CfAppAutoscalerEventsDataSource } from './cf-app-autoscaler-events-data-source'; import { diff --git a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-list-config.service.ts b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-list-config.service.ts index 05e19cc82a..a5c3fd8aef 100644 --- a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-list-config.service.ts +++ b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-list-config.service.ts @@ -10,9 +10,10 @@ import { import { ITableColumn } from '../../../../../core/src/shared/components/list/list-table/table.types'; import { ListViewTypes } from '../../../../../core/src/shared/components/list/list.component.types'; import { MetricsRangeSelectorService } from '../../../../../core/src/shared/services/metrics-range-selector.service'; -import { ITimeRange, MetricQueryType } from '../../../../../core/src/shared/services/metrics-range-selector.types'; +import { ITimeRange } from '../../../../../core/src/shared/services/metrics-range-selector.types'; import { ListView } from '../../../../../store/src/actions/list.actions'; import { APIResource } from '../../../../../store/src/types/api.types'; +import { MetricQueryType } from '../../../../../store/src/types/metric.types'; import { AutoscalerConstants } from '../../../core/autoscaler-helpers/autoscaler-util'; import { AppScalingTrigger } from '../../../store/app-autoscaler.types'; import { diff --git a/src/frontend/packages/cf-autoscaler/src/store/autoscaler-entity-factory.ts b/src/frontend/packages/cf-autoscaler/src/store/autoscaler-entity-factory.ts index ecdddc9b8c..13c1ff8089 100644 --- a/src/frontend/packages/cf-autoscaler/src/store/autoscaler-entity-factory.ts +++ b/src/frontend/packages/cf-autoscaler/src/store/autoscaler-entity-factory.ts @@ -1,8 +1,8 @@ import { Schema, schema } from 'normalizr'; import { getAPIResourceGuid } from '../../../cloud-foundry/src/store/selectors/api.selectors'; -import { metricEntityType } from '../../../core/src/base-entity-schemas'; import { EntitySchema } from '../../../store/src/helpers/entity-schema'; +import { metricEntityType } from '../../../store/src/helpers/stratos-entity-factory'; export const appAutoscalerInfoEntityType = 'autoscalerInfo'; export const appAutoscalerHealthEntityType = 'autoscalerHealth'; @@ -10,7 +10,7 @@ export const appAutoscalerPolicyEntityType = 'autoscalerPolicy'; export const appAutoscalerPolicyTriggerEntityType = 'autoscalerPolicyTrigger'; export const appAutoscalerScalingHistoryEntityType = 'autoscalerScalingHistory'; export const appAutoscalerAppMetricEntityType = 'autoscalerAppMetric'; -export const appAutoscalerCredentialEntityType = 'autoscalerCredential' +export const appAutoscalerCredentialEntityType = 'autoscalerCredential'; export const AUTOSCALER_ENDPOINT_TYPE = 'autoscaler'; diff --git a/src/frontend/packages/cf-autoscaler/src/store/autoscaler-entity-generator.ts b/src/frontend/packages/cf-autoscaler/src/store/autoscaler-entity-generator.ts index 9e946fca20..4e71099074 100644 --- a/src/frontend/packages/cf-autoscaler/src/store/autoscaler-entity-generator.ts +++ b/src/frontend/packages/cf-autoscaler/src/store/autoscaler-entity-generator.ts @@ -1,10 +1,10 @@ import { IOrgFavMetadata } from '../../../cloud-foundry/src/cf-metadata-types'; -import { metricEntityType } from '../../../core/src/base-entity-schemas'; import { StratosBaseCatalogEntity, StratosCatalogEntity, } from '../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; import { IStratosEndpointDefinition } from '../../../store/src/entity-catalog/entity-catalog.types'; +import { metricEntityType } from '../../../store/src/helpers/stratos-entity-factory'; import { APIResource } from '../../../store/src/types/api.types'; import { IFavoriteMetadata } from '../../../store/src/types/user-favorites.types'; import { AppAutoscalerEvent, AppAutoscalerHealth, AppAutoscalerPolicy, AppScalingTrigger } from './app-autoscaler.types'; diff --git a/src/frontend/packages/cf-autoscaler/src/store/autoscaler.effects.ts b/src/frontend/packages/cf-autoscaler/src/store/autoscaler.effects.ts index e85527a5cd..98c2e1e7c1 100644 --- a/src/frontend/packages/cf-autoscaler/src/store/autoscaler.effects.ts +++ b/src/frontend/packages/cf-autoscaler/src/store/autoscaler.effects.ts @@ -7,9 +7,9 @@ import { catchError, mergeMap, withLatestFrom } from 'rxjs/operators'; import { PaginationResponse } from '../../../cloud-foundry/src/store/types/cf-api.types'; import { environment } from '../../../core/src/environments/environment'; -import { isHttpErrorResponse } from '../../../core/src/jetstream.helpers'; import { AppState } from '../../../store/src/app-state'; import { entityCatalog } from '../../../store/src/entity-catalog/entity-catalog'; +import { isHttpErrorResponse } from '../../../store/src/jetstream'; import { ApiRequestTypes } from '../../../store/src/reducers/api-request-reducer/request-helpers'; import { resultPerPageParam, diff --git a/src/frontend/packages/cloud-foundry/package.json b/src/frontend/packages/cloud-foundry/package.json index 989436cb0d..844bc32e3a 100644 --- a/src/frontend/packages/cloud-foundry/package.json +++ b/src/frontend/packages/cloud-foundry/package.json @@ -4,5 +4,9 @@ "peerDependencies": { "@angular/common": "^6.0.0-rc.0 || ^6.0.0", "@angular/core": "^6.0.0-rc.0 || ^6.0.0" + }, + "stratos": { + "theming": "sass/_all-theme#apply-theme-stratos-cloud-foundry" } + } \ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/sass/_all-theme.scss b/src/frontend/packages/cloud-foundry/sass/_all-theme.scss new file mode 100644 index 0000000000..fd92ae1354 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/sass/_all-theme.scss @@ -0,0 +1,12 @@ +// Theming for the copmponents in the Cloud Foundry package + +@import '../src/features/applications/application-wall/application-wall.component.theme'; +@import '../src/shared/components/list/list-types/cf-security-groups/cf-security-groups-card/cf-security-groups-card.component.theme'; + +@mixin apply-theme-stratos-cloud-foundry($stratos-theme) { + + $theme: map-get($stratos-theme, theme); + $app-theme: map-get($stratos-theme, app-theme); + + @include cf-security-group-theme($theme); +} diff --git a/src/frontend/packages/cloud-foundry/src/actions/cf-event.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/cf-event.actions.ts index ad2daf059f..c6bb0e3aff 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/cf-event.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/cf-event.actions.ts @@ -1,6 +1,6 @@ import { HttpParams, HttpRequest } from '@angular/common/http'; -import { endpointSchemaKey } from '../../../store/src/helpers/entity-factory'; +import { endpointEntityType } from '../../../store/src/helpers/stratos-entity-factory'; import { PaginatedAction } from '../../../store/src/types/pagination.types'; import { cfEntityFactory } from '../cf-entity-factory'; import { cfEventEntityType } from '../cf-entity-types'; @@ -18,7 +18,7 @@ export class GetAllCfEvents extends CFStartAction implements PaginatedAction { constructor(public paginationKey: string, public endpointGuid) { super(); - this.paginationKey = this.paginationKey || createEntityRelationPaginationKey(endpointSchemaKey, endpointGuid); + this.paginationKey = this.paginationKey || createEntityRelationPaginationKey(endpointEntityType, endpointGuid); this.options = new HttpRequest( 'GET', 'events', diff --git a/src/frontend/packages/cloud-foundry/src/actions/cf-metrics.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/cf-metrics.actions.ts index 4422c703f6..e57065e7f0 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/cf-metrics.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/cf-metrics.actions.ts @@ -1,5 +1,5 @@ -import { MetricQueryType } from '../../../core/src/shared/services/metrics-range-selector.types'; import { MetricQueryConfig, MetricsAction, MetricsChartAction } from '../../../store/src/actions/metrics.actions'; +import { MetricQueryType } from '../../../store/src/types/metric.types'; import { PaginatedAction } from '../../../store/src/types/pagination.types'; import { CF_ENDPOINT_TYPE } from '../cf-types'; diff --git a/src/frontend/packages/cloud-foundry/src/actions/domains.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/domains.actions.ts index 13d4f6d59b..d593428b5f 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/domains.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/domains.actions.ts @@ -1,6 +1,6 @@ import { HttpRequest } from '@angular/common/http'; -import { endpointSchemaKey } from '../../../store/src/helpers/entity-factory'; +import { endpointEntityType } from '../../../store/src/helpers/stratos-entity-factory'; import { PaginatedAction } from '../../../store/src/types/pagination.types'; import { ICFAction } from '../../../store/src/types/request.types'; import { cfEntityFactory } from '../cf-entity-factory'; @@ -36,7 +36,7 @@ export class FetchAllDomains extends CFStartAction implements PaginatedAction { 'GET', 'domains', ); - this.paginationKey = this.paginationKey || createEntityRelationPaginationKey(endpointSchemaKey, endpointGuid); + this.paginationKey = this.paginationKey || createEntityRelationPaginationKey(endpointEntityType, endpointGuid); } actions = [GET_ALL_DOMAIN, GET_ALL_DOMAIN_SUCCESS, GET_ALL_DOMAIN_FAILED]; entity = [cfEntityFactory(domainEntityType)]; diff --git a/src/frontend/packages/cloud-foundry/src/actions/feature-flags.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/feature-flags.actions.ts index 6ec461e70b..5c1967a0c1 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/feature-flags.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/feature-flags.actions.ts @@ -1,7 +1,7 @@ import { HttpRequest } from '@angular/common/http'; import { getActions } from '../../../store/src/actions/action.helper'; -import { endpointSchemaKey } from '../../../store/src/helpers/entity-factory'; +import { endpointEntityType } from '../../../store/src/helpers/stratos-entity-factory'; import { PaginatedAction } from '../../../store/src/types/pagination.types'; import { RequestEntityLocation } from '../../../store/src/types/request.types'; import { cfEntityFactory } from '../cf-entity-factory'; @@ -12,7 +12,7 @@ import { CFStartAction } from './cf-action.types'; export class GetAllFeatureFlags extends CFStartAction implements PaginatedAction { constructor(public endpointGuid: string, public paginationKey: string = null) { super(); - this.paginationKey = this.paginationKey || createEntityRelationPaginationKey(endpointSchemaKey, this.endpointGuid); + this.paginationKey = this.paginationKey || createEntityRelationPaginationKey(endpointEntityType, this.endpointGuid); this.options = new HttpRequest( 'GET', `config/feature_flags` diff --git a/src/frontend/packages/cloud-foundry/src/actions/stack.action.ts b/src/frontend/packages/cloud-foundry/src/actions/stack.action.ts index b4a274ee38..ac77391430 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/stack.action.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/stack.action.ts @@ -2,7 +2,7 @@ import { HttpRequest } from '@angular/common/http'; import { getActions } from '../../../store/src/actions/action.helper'; import { entityCatalog } from '../../../store/src/entity-catalog/entity-catalog'; -import { endpointSchemaKey } from '../../../store/src/helpers/entity-factory'; +import { endpointEntityType } from '../../../store/src/helpers/stratos-entity-factory'; import { PaginatedAction } from '../../../store/src/types/pagination.types'; import { ICFAction } from '../../../store/src/types/request.types'; import { stackEntityType } from '../cf-entity-types'; @@ -38,7 +38,7 @@ export class GetAllStacks extends CFStartAction implements PaginatedAction { 'GET', 'stacks' ); - this.paginationKey = createEntityRelationKey(endpointSchemaKey, endpointGuid); + this.paginationKey = createEntityRelationKey(endpointEntityType, endpointGuid); } paginationKey: string; actions = getActions('Stack', 'Fetch all'); diff --git a/src/frontend/packages/cloud-foundry/src/actions/user-provided-service.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/user-provided-service.actions.ts index 0080cf6962..6ef888f96d 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/user-provided-service.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/user-provided-service.actions.ts @@ -2,7 +2,7 @@ import { HttpRequest } from '@angular/common/http'; import { getActions } from '../../../store/src/actions/action.helper'; import { EntityCatalogEntityConfig } from '../../../store/src/entity-catalog/entity-catalog.types'; -import { endpointSchemaKey } from '../../../store/src/helpers/entity-factory'; +import { endpointEntityType } from '../../../store/src/helpers/stratos-entity-factory'; import { PaginatedAction } from '../../../store/src/types/pagination.types'; import { ICFAction } from '../../../store/src/types/request.types'; import { cfEntityFactory } from '../cf-entity-factory'; @@ -39,7 +39,7 @@ export class GetAllUserProvidedServices extends CFStartAction implements Paginat ) { super(); this.paginationKey = paginationKey || (spaceGuid ? createEntityRelationPaginationKey(spaceEntityType, spaceGuid) : - createEntityRelationPaginationKey(endpointSchemaKey, endpointGuid)); + createEntityRelationPaginationKey(endpointEntityType, endpointGuid)); this.options = new HttpRequest( 'GET', `user_provided_service_instances`, diff --git a/src/frontend/packages/cloud-foundry/src/actions/users.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/users.actions.ts index 40f7d57afc..73d008753b 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/users.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/users.actions.ts @@ -1,8 +1,8 @@ import { HttpRequest } from '@angular/common/http'; import { getActions } from '../../../store/src/actions/action.helper'; -import { endpointSchemaKey } from '../../../store/src/helpers/entity-factory'; import { EntitySchema } from '../../../store/src/helpers/entity-schema'; +import { endpointEntityType } from '../../../store/src/helpers/stratos-entity-factory'; import { PaginatedAction } from '../../../store/src/types/pagination.types'; import { EntityRequestAction } from '../../../store/src/types/request.types'; import { cfEntityFactory } from '../cf-entity-factory'; @@ -51,7 +51,7 @@ export class GetAllCfUsersAsAdmin extends CFStartAction implements PaginatedActi paginationKey?: string ) { super(); - this.paginationKey = paginationKey || createEntityRelationPaginationKey(endpointSchemaKey, endpointGuid); + this.paginationKey = paginationKey || createEntityRelationPaginationKey(endpointEntityType, endpointGuid); this.options = new HttpRequest( 'GET', 'users' diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts index 1112888031..33bbedf4f7 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts @@ -1,5 +1,5 @@ -import { metricEntityType } from '../../core/src/base-entity-schemas'; import { EntitySchema } from '../../store/src/helpers/entity-schema'; +import { metricEntityType } from '../../store/src/helpers/stratos-entity-factory'; import { APIResource } from '../../store/src/types/api.types'; import { CFApplicationEntitySchema, @@ -200,24 +200,24 @@ const CFUserSchema = new CFUserEntitySchema({ audited_spaces: [createUserOrgSpaceSchema(spaceEntityType, {}, CfUserRoleParams.AUDITED_SPACES)], } }, { - idAttribute: getAPIResourceGuid, - processStrategy: (user: APIResource) => { - if (user.entity.username) { - return user; - } - const entity = { - ...user.entity, - username: user.metadata.guid - }; - - return user.metadata ? { - entity, - metadata: user.metadata - } : { - entity - }; + idAttribute: getAPIResourceGuid, + processStrategy: (user: APIResource) => { + if (user.entity.username) { + return user; } - }); + const entity = { + ...user.entity, + username: user.metadata.guid + }; + + return user.metadata ? { + entity, + metadata: user.metadata + } : { + entity + }; + } +}); entityCache[cfUserEntityType] = CFUserSchema; const coreSpaceSchemaParams = { diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts index beba13c732..89727e826e 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts @@ -3,10 +3,8 @@ import * as moment from 'moment'; import { combineLatest, Observable, of } from 'rxjs'; import { first, map } from 'rxjs/operators'; -import { EndpointHealthCheck } from '../../core/endpoints-health-checks'; -import { metricEntityType } from '../../core/src/base-entity-schemas'; +import { BaseEndpointAuth } from '../../core/src/core/endpoint-auth'; import { urlValidationExpression } from '../../core/src/core/utils.service'; -import { BaseEndpointAuth } from '../../core/src/features/endpoints/endpoint-auth'; import { AppState, GeneralEntityAppState } from '../../store/src/app-state'; import { StratosBaseCatalogEntity, @@ -14,6 +12,7 @@ import { StratosCatalogEntity, } from '../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; import { + EndpointHealthCheck, IStratosEntityDefinition, StratosEndpointExtensionDefinition, } from '../../store/src/entity-catalog/entity-catalog.types'; @@ -22,6 +21,7 @@ import { } from '../../store/src/entity-request-pipeline/entity-request-base-handlers/handle-multi-endpoints.pipe'; import { ActionDispatcher, JetstreamResponse } from '../../store/src/entity-request-pipeline/entity-request-pipeline.types'; import { EntitySchema } from '../../store/src/helpers/entity-schema'; +import { metricEntityType } from '../../store/src/helpers/stratos-entity-factory'; import { RequestInfoState } from '../../store/src/reducers/api-request-reducer/types'; import { selectSessionData } from '../../store/src/reducers/auth.reducer'; import { APIResource, EntityInfo } from '../../store/src/types/api.types'; @@ -409,7 +409,9 @@ function generateCFQuotaDefinitionEntity(endpointDefinition: StratosEndpointExte const definition: IStratosEntityDefinition = { type: quotaDefinitionEntityType, schema: cfEntityFactory(quotaDefinitionEntityType), - endpoint: endpointDefinition + endpoint: endpointDefinition, + label: 'Organization Quota', + labelPlural: 'Organization Quotas', }; cfEntityCatalog.quotaDefinition = new StratosCatalogEntity< IBasicCFMetaData, @@ -451,6 +453,8 @@ function generateCFAppEnvVarEntity(endpointDefinition: StratosEndpointExtensionD } }; }, + label: 'App Env Var', + labelPlural: 'App Env Vars', }; cfEntityCatalog.appEnvVar = new StratosCatalogEntity< IBasicCFMetaData, @@ -478,6 +482,8 @@ function generateCFAppSummaryEntity(endpointDefinition: StratosEndpointExtension type: appSummaryEntityType, schema: cfEntityFactory(appSummaryEntityType), endpoint: endpointDefinition, + label: 'App Summary', + labelPlural: 'App Summaries', }; cfEntityCatalog.appSummary = new StratosCatalogEntity(definition, { dataReducers: [ @@ -500,7 +506,9 @@ function generateCFSpaceQuotaEntity(endpointDefinition: StratosEndpointExtension const definition: IStratosEntityDefinition = { type: spaceQuotaEntityType, schema: cfEntityFactory(spaceQuotaEntityType), - endpoint: endpointDefinition + endpoint: endpointDefinition, + label: 'Space Quota', + labelPlural: 'Space Quotas', }; cfEntityCatalog.spaceQuota = new StratosCatalogEntity< IBasicCFMetaData, @@ -518,7 +526,9 @@ function generateCFPrivateDomainEntity(endpointDefinition: StratosEndpointExtens const definition: IStratosEntityDefinition = { type: privateDomainsEntityType, schema: cfEntityFactory(privateDomainsEntityType), - endpoint: endpointDefinition + endpoint: endpointDefinition, + label: 'Private Domain', + labelPlural: 'Private Domains', }; cfEntityCatalog.privateDomain = new StratosCatalogEntity>(definition, { dataReducers: [ diff --git a/src/frontend/packages/cloud-foundry/src/cf-error-helpers.ts b/src/frontend/packages/cloud-foundry/src/cf-error-helpers.ts index 73c4b0c0d4..da3efe70ec 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-error-helpers.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-error-helpers.ts @@ -1,4 +1,4 @@ -import { JetStreamErrorResponse, jetStreamErrorResponseToSafeString } from '../../core/src/jetstream.helpers'; +import { JetStreamErrorResponse, jetStreamErrorResponseToSafeString } from '../../store/src/jetstream'; export interface CfErrorObject { code: number; diff --git a/src/frontend/packages/cloud-foundry/src/cf-favorites-helpers.ts b/src/frontend/packages/cloud-foundry/src/cf-favorites-helpers.ts index ec23f4125f..8f19d39a21 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-favorites-helpers.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-favorites-helpers.ts @@ -1,5 +1,5 @@ -import { FavoritesConfigMapper } from '../../core/src/shared/components/favorites-meta-card/favorite-config-mapper'; import { IEntityMetadata } from '../../store/src/entity-catalog/entity-catalog.types'; +import { FavoritesConfigMapper } from '../../store/src/favorite-config-mapper'; import { UserFavorite } from '../../store/src/types/user-favorites.types'; import { CfAPIResource } from './store/types/cf-api.types'; diff --git a/src/frontend/packages/cloud-foundry/src/cloud-foundry-test.module.ts b/src/frontend/packages/cloud-foundry/src/cloud-foundry-test.module.ts index 11769b81a4..99bfc37bbd 100644 --- a/src/frontend/packages/cloud-foundry/src/cloud-foundry-test.module.ts +++ b/src/frontend/packages/cloud-foundry/src/cloud-foundry-test.module.ts @@ -3,11 +3,11 @@ import { NgModule } from '@angular/core'; import { EffectsModule } from '@ngrx/effects'; import { generateASEntities } from '../../cf-autoscaler/src/store/autoscaler-entity-generator'; -import { generateStratosEntities } from '../../core/src/base-entity-types'; import { getGitHubAPIURL, GITHUB_API_URL } from '../../core/src/core/github.helpers'; import { LoggerService } from '../../core/src/core/logger.service'; import { CATALOGUE_ENTITIES, EntityCatalogFeatureModule } from '../../store/src/entity-catalog.module'; import { entityCatalog, TestEntityCatalog } from '../../store/src/entity-catalog/entity-catalog'; +import { generateStratosEntities } from '../../store/src/stratos-entity-generator'; import { testSCFEndpointGuid } from '../../store/testing/public-api'; import { BaseCfOrgSpaceRouteMock } from '../test-framework/cloud-foundry-endpoint-service.helper'; import { generateCFEntities } from './cf-entity-generator'; diff --git a/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relations-list.spec.ts b/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relations-list.spec.ts index 73851810cc..35679dc510 100644 --- a/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relations-list.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relations-list.spec.ts @@ -1,6 +1,7 @@ +import { EntitySchema } from '../../../store/src/helpers/entity-schema'; import { listEntityRelations } from './entity-relations'; import { createEntityRelationKey, EntityInlineParentAction } from './entity-relations.types'; -import { EntitySchema } from '../../../store/src/helpers/entity-schema'; + const endpointType = 'endpointtype1'; describe('Entity Relations - List relations', () => { diff --git a/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relations.tree.spec.ts b/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relations.tree.spec.ts index 6ce1b9126a..5383e22fbe 100644 --- a/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relations.tree.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relations.tree.spec.ts @@ -1,5 +1,5 @@ -import { CF_ENDPOINT_TYPE } from '../cf-types'; import { CFEntitySchema } from '../cf-entity-schema-types'; +import { CF_ENDPOINT_TYPE } from '../cf-types'; import { fetchEntityTree } from './entity-relations.tree'; import { createEntityRelationKey, EntityInlineParentAction } from './entity-relations.types'; diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/application-delete.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/application-delete.component.spec.ts index 7e8ac98783..52aa6ff434 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/application-delete.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/application-delete.component.spec.ts @@ -1,12 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { GetApplication } from '../../../../../cloud-foundry/src/actions/application.actions'; -import { cfEntityFactory } from '../../../../../cloud-foundry/src/cf-entity-factory'; import { TabNavService } from '../../../../../core/tab-nav.service'; -import { generateTestEntityServiceProvider } from '../../../../../core/test-framework/entity-service.helper'; import { generateTestApplicationServiceProvider } from '../../../../test-framework/application-service-helper'; import { generateCfBaseTestModules } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { applicationEntityType } from '../../../cf-entity-types'; import { ApplicationsModule } from '../applications.module'; import { ApplicationDeleteComponent } from './application-delete.component'; @@ -22,11 +18,6 @@ describe('ApplicationDeleteComponent', () => { ApplicationsModule ], providers: [ - generateTestEntityServiceProvider( - appId, - cfEntityFactory(applicationEntityType), - new GetApplication(appId, cfId) - ), generateTestApplicationServiceProvider(cfId, appId), TabNavService ] diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-instances/app-delete-instances-routes-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-instances/app-delete-instances-routes-list-config.service.ts index 91f805f77c..f8b961dfba 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-instances/app-delete-instances-routes-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-instances/app-delete-instances-routes-list-config.service.ts @@ -12,7 +12,7 @@ import { import { CurrentUserPermissionsService } from '../../../../../../core/src/core/permissions/current-user-permissions.service'; import { RowState } from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; import { ListViewTypes } from '../../../../../../core/src/shared/components/list/list.component.types'; -import { endpointSchemaKey } from '../../../../../../store/src/helpers/entity-factory'; +import { endpointEntityType } from '../../../../../../store/src/helpers/stratos-entity-factory'; import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; import { APIResource } from '../../../../../../store/src/types/api.types'; import { IServiceBinding } from '../../../../cf-api-svc.types'; @@ -59,7 +59,7 @@ export class AppDeleteServiceInstancesListConfigService extends AppServiceBindin const action = cfEntityCatalog.serviceBinding.actions.getAllForServiceInstance( serviceBinding.entity.service_instance_guid, appService.cfGuid, - createEntityRelationPaginationKey(endpointSchemaKey, serviceBindingEntityType), + createEntityRelationPaginationKey(endpointEntityType, serviceBindingEntityType), { includeRelations: [], } diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-instances/delete-app-instances.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-instances/delete-app-instances.component.spec.ts index cc133e9d3b..184b50070a 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-instances/delete-app-instances.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-instances/delete-app-instances.component.spec.ts @@ -1,12 +1,8 @@ import { DatePipe } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { GetApplication } from '../../../../../../cloud-foundry/src/actions/application.actions'; -import { cfEntityFactory } from '../../../../../../cloud-foundry/src/cf-entity-factory'; -import { generateTestEntityServiceProvider } from '../../../../../../core/test-framework/entity-service.helper'; import { generateTestApplicationServiceProvider } from '../../../../../test-framework/application-service-helper'; import { generateCfBaseTestModules } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { applicationEntityType } from '../../../../cf-entity-types'; import { ServiceActionHelperService } from '../../../../shared/data-services/service-action-helper.service'; import { ApplicationEnvVarsHelper, @@ -24,11 +20,6 @@ describe('DeleteAppInstancesComponent', () => { declarations: [DeleteAppServiceInstancesComponent], imports: generateCfBaseTestModules(), providers: [ - generateTestEntityServiceProvider( - appId, - cfEntityFactory(applicationEntityType), - new GetApplication(appId, cfId) - ), generateTestApplicationServiceProvider(cfId, appId), ApplicationEnvVarsHelper, DatePipe, diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-routes/delete-app-routes.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-routes/delete-app-routes.component.spec.ts index 28b04b412d..7e6b4e0ff6 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-routes/delete-app-routes.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-routes/delete-app-routes.component.spec.ts @@ -1,12 +1,8 @@ import { DatePipe } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { GetApplication } from '../../../../../../cloud-foundry/src/actions/application.actions'; -import { cfEntityFactory } from '../../../../../../cloud-foundry/src/cf-entity-factory'; -import { generateTestEntityServiceProvider } from '../../../../../../core/test-framework/entity-service.helper'; import { generateTestApplicationServiceProvider } from '../../../../../test-framework/application-service-helper'; import { generateCfBaseTestModules } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { applicationEntityType } from '../../../../cf-entity-types'; import { ApplicationEnvVarsHelper, } from '../../application/application-tabs-base/tabs/build-tab/application-env-vars.service'; @@ -23,11 +19,6 @@ describe('DeleteAppRoutesComponent', () => { declarations: [DeleteAppRoutesComponent], imports: generateCfBaseTestModules(), providers: [ - generateTestEntityServiceProvider( - appId, - cfEntityFactory(applicationEntityType), - new GetApplication(appId, cfId) - ), generateTestApplicationServiceProvider(cfId, appId), ApplicationEnvVarsHelper, DatePipe, diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.spec.ts index aba0ea3567..23282a262b 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.spec.ts @@ -1,18 +1,14 @@ import { inject, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { GetApplication } from '../../../../cloud-foundry/src/actions/application.actions'; -import { cfEntityFactory } from '../../../../cloud-foundry/src/cf-entity-factory'; import { CoreModule } from '../../../../core/src/core/core.module'; import { ExtensionService } from '../../../../core/src/core/extension/extension-service'; import { getGitHubAPIURL, GITHUB_API_URL } from '../../../../core/src/core/github.helpers'; -import { generateTestEntityServiceProvider } from '../../../../core/test-framework/entity-service.helper'; import { EntityMonitorFactory } from '../../../../store/src/monitors/entity-monitor.factory.service'; import { PaginationMonitorFactory } from '../../../../store/src/monitors/pagination-monitor.factory'; import { AppStoreModule } from '../../../../store/src/store.module'; import { generateTestApplicationServiceProvider } from '../../../test-framework/application-service-helper'; import { generateCfStoreModules } from '../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { applicationEntityType } from '../../cf-entity-types'; import { LongRunningCfOperationsService } from '../../shared/data-services/long-running-cf-op.service'; import { GitSCMService } from '../../shared/data-services/scm/scm.service'; import { ApplicationStateService } from '../../shared/services/application-state.service'; @@ -33,11 +29,6 @@ describe('ApplicationService', () => { generateCfStoreModules() ], providers: [ - generateTestEntityServiceProvider( - appId, - cfEntityFactory(applicationEntityType), - new GetApplication(appId, cfId) - ), generateTestApplicationServiceProvider(cfId, appId), ApplicationStateService, ApplicationEnvVarsHelper, diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts index d918209f4f..628b8560a4 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts @@ -44,13 +44,13 @@ export function createGetApplicationAction(guid: string, endpointGuid: string) { return new GetApplication( guid, endpointGuid, [ - createEntityRelationKey(applicationEntityType, routeEntityType), - createEntityRelationKey(applicationEntityType, spaceEntityType), - createEntityRelationKey(applicationEntityType, stackEntityType), - createEntityRelationKey(applicationEntityType, serviceBindingEntityType), - createEntityRelationKey(routeEntityType, domainEntityType), - createEntityRelationKey(spaceEntityType, organizationEntityType), - ] + createEntityRelationKey(applicationEntityType, routeEntityType), + createEntityRelationKey(applicationEntityType, spaceEntityType), + createEntityRelationKey(applicationEntityType, stackEntityType), + createEntityRelationKey(applicationEntityType, serviceBindingEntityType), + createEntityRelationKey(routeEntityType, domainEntityType), + createEntityRelationKey(spaceEntityType, organizationEntityType), + ] ); } @@ -64,7 +64,7 @@ export interface ApplicationData { @Injectable() export class ApplicationService { - private appEntityService: EntityService>; + public entityService: EntityService>; private appSummaryEntityService: EntityService; constructor( @@ -74,7 +74,7 @@ export class ApplicationService { private appStateService: ApplicationStateService, private appEnvVarsService: ApplicationEnvVarsHelper, ) { - this.appEntityService = cfEntityCatalog.application.store.getEntityService( + this.entityService = cfEntityCatalog.application.store.getEntityService( appGuid, cfGuid, { @@ -138,7 +138,7 @@ export class ApplicationService { private constructCoreObservables() { // First set up all the base observables - this.app$ = this.appEntityService.waitForEntity$; + this.app$ = this.entityService.waitForEntity$; const moreWaiting$ = this.app$.pipe( filter(entityInfo => !!(entityInfo.entity && entityInfo.entity.entity && entityInfo.entity.entity.cfGuid)), map(entityInfo => entityInfo.entity.entity)); @@ -162,9 +162,9 @@ export class ApplicationService { filter(org => !!org) ); - this.isDeletingApp$ = this.appEntityService.isDeletingEntity$.pipe(publishReplay(1), refCount()); + this.isDeletingApp$ = this.entityService.isDeletingEntity$.pipe(publishReplay(1), refCount()); - this.waitForAppEntity$ = this.appEntityService.waitForEntity$.pipe(publishReplay(1), refCount()); + this.waitForAppEntity$ = this.entityService.waitForEntity$.pipe(publishReplay(1), refCount()); this.appSummary$ = this.waitForAppEntity$.pipe( switchMap(() => this.appSummaryEntityService.entityObs$), @@ -231,9 +231,9 @@ export class ApplicationService { } private constructStatusObservables() { - this.isFetchingApp$ = this.appEntityService.isFetchingEntity$; + this.isFetchingApp$ = this.entityService.isFetchingEntity$; - this.isUpdatingApp$ = this.appEntityService.entityObs$.pipe(map(a => { + this.isUpdatingApp$ = this.entityService.entityObs$.pipe(map(a => { const updatingRoot = a.entityRequestInfo.updating[rootUpdatingKey] || { busy: false }; const updatingSection = a.entityRequestInfo.updating[UpdateExistingApplication.updateKey] || { busy: false }; return !!updatingRoot.busy || !!updatingSection.busy; diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-base.component.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-base.component.ts index a95f69f887..ba7fea321a 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-base.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-base.component.ts @@ -3,10 +3,9 @@ import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; -import { APP_GUID, CF_GUID, ENTITY_SERVICE } from '../../../../../core/src/shared/entity.tokens'; -import { EntityServiceFactory } from '../../../../../store/src/entity-service-factory.service'; +import { APP_GUID, CF_GUID } from '../../../../../core/src/shared/entity.tokens'; import { ApplicationStateService } from '../../../shared/services/application-state.service'; -import { ApplicationService, createGetApplicationAction } from '../application.service'; +import { ApplicationService } from '../application.service'; import { ApplicationEnvVarsHelper } from './application-tabs-base/tabs/build-tab/application-env-vars.service'; export function applicationServiceFactory( @@ -25,17 +24,6 @@ export function applicationServiceFactory( ); } -export function cfApplicationEntityServiceFactory( - cfId: string, - id: string, - esf: EntityServiceFactory -) { - return esf.create( - id, - createGetApplicationAction(id, cfId) - ); -} - export function getGuids(type?: string) { return (activatedRoute: ActivatedRoute) => { const { id, endpointId } = activatedRoute.snapshot.params; @@ -72,13 +60,7 @@ export function getGuids(type?: string) { ApplicationStateService, ApplicationEnvVarsHelper, ] - }, - { - provide: ENTITY_SERVICE, - useFactory: cfApplicationEntityServiceFactory, - deps: [CF_GUID, APP_GUID, EntityServiceFactory] - }, - + } ] }) export class ApplicationBaseComponent { diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-poll/application-poll.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-poll/application-poll.component.spec.ts index 096eebe3f7..d20348cc9f 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-poll/application-poll.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-poll/application-poll.component.spec.ts @@ -1,11 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { generateTestEntityServiceProvider } from '../../../../../../../core/test-framework/entity-service.helper'; import { generateTestApplicationServiceProvider } from '../../../../../../test-framework/application-service-helper'; import { generateCfBaseTestModules } from '../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { GetApplication } from '../../../../../actions/application.actions'; -import { cfEntityFactory } from '../../../../../cf-entity-factory'; -import { applicationEntityType } from '../../../../../cf-entity-types'; import { ApplicationPollingService } from '../application-polling.service'; import { ApplicationEnvVarsHelper } from '../tabs/build-tab/application-env-vars.service'; import { ApplicationStateService } from './../../../../../shared/services/application-state.service'; @@ -23,11 +19,6 @@ describe('ApplicationPollComponent', () => { declarations: [ApplicationPollComponent], providers: [ ApplicationPollingService, - generateTestEntityServiceProvider( - appId, - cfEntityFactory(applicationEntityType), - new GetApplication(appId, cfId) - ), generateTestApplicationServiceProvider(cfId, appId), ApplicationEnvVarsHelper, ApplicationStateService, diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-polling.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-polling.service.ts index 39fd632931..046af5a750 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-polling.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-polling.service.ts @@ -1,15 +1,11 @@ -import { Inject, Injectable, NgZone } from '@angular/core'; +import { Injectable, NgZone } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable, Subscription } from 'rxjs'; import { first, map, tap } from 'rxjs/operators'; import { safeUnsubscribe } from '../../../../../../core/src/core/utils.service'; -import { ENTITY_SERVICE } from '../../../../../../core/src/shared/entity.tokens'; import { AppState } from '../../../../../../store/src/app-state'; -import { EntityService } from '../../../../../../store/src/entity-service'; import { selectDashboardState } from '../../../../../../store/src/selectors/dashboard.selectors'; -import { APIResource } from '../../../../../../store/src/types/api.types'; -import { IApp } from '../../../../cf-api.types'; import { cfEntityCatalog } from '../../../../cf-entity-catalog'; import { ApplicationService } from '../../application.service'; @@ -19,7 +15,7 @@ export class ApplicationPollingService { private pollingSub: Subscription; private autoRefreshString = 'auto-refresh'; - public isPolling$ = this.entityService.updatingSection$.pipe(map( + public isPolling$ = this.applicationService.entityService.updatingSection$.pipe(map( update => update[this.autoRefreshString] && update[this.autoRefreshString].busy )); @@ -27,7 +23,6 @@ export class ApplicationPollingService { constructor( public applicationService: ApplicationService, - @Inject(ENTITY_SERVICE) private entityService: EntityService>, private store: Store, private ngZone: NgZone, ) { @@ -54,7 +49,7 @@ export class ApplicationPollingService { // Auto refresh this.ngZone.runOutsideAngular(() => { - this.pollingSub = this.entityService + this.pollingSub = this.applicationService.entityService .poll(10000, this.autoRefreshString).pipe( tap(() => this.ngZone.run(() => this.poll(false)))) .subscribe(); @@ -69,12 +64,12 @@ export class ApplicationPollingService { const { cfGuid, appGuid } = this.applicationService; if (withApp) { const updatingApp = { - ...this.entityService.action, + ...this.applicationService.entityService.action, updatingKey: this.autoRefreshString }; this.store.dispatch(updatingApp); } - this.entityService.entityObs$.pipe( + this.applicationService.entityService.entityObs$.pipe( first(), ).subscribe(resource => { cfEntityCatalog.appSummary.api.get(appGuid, cfGuid); diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-tabs-base.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-tabs-base.component.spec.ts index 55f591b111..19c49370d4 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-tabs-base.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-tabs-base.component.spec.ts @@ -5,17 +5,13 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; import { StoreModule } from '@ngrx/store'; -import { GetApplication } from '../../../../../../cloud-foundry/src/actions/application.actions'; -import { cfEntityFactory } from '../../../../../../cloud-foundry/src/cf-entity-factory'; import { CoreModule } from '../../../../../../core/src/core/core.module'; import { getGitHubAPIURL, GITHUB_API_URL } from '../../../../../../core/src/core/github.helpers'; import { MDAppModule } from '../../../../../../core/src/core/md.module'; import { SharedModule } from '../../../../../../core/src/shared/shared.module'; import { TabNavService } from '../../../../../../core/tab-nav.service'; -import { generateTestEntityServiceProvider } from '../../../../../../core/test-framework/entity-service.helper'; import { generateTestApplicationServiceProvider } from '../../../../../test-framework/application-service-helper'; import { generateCfStoreModules } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { applicationEntityType } from '../../../../cf-entity-types'; import { ApplicationStateService } from '../../../../shared/services/application-state.service'; import { ApplicationTabsBaseComponent } from './application-tabs-base.component'; import { ApplicationEnvVarsHelper } from './tabs/build-tab/application-env-vars.service'; @@ -44,11 +40,6 @@ describe('ApplicationTabsBaseComponent', () => { HttpClientTestingModule ], providers: [ - generateTestEntityServiceProvider( - appId, - cfEntityFactory(applicationEntityType), - new GetApplication(appId, cfId) - ), generateTestApplicationServiceProvider(cfId, appId), ApplicationStateService, ApplicationEnvVarsHelper, diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-tabs-base.component.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-tabs-base.component.ts index ca2194ba6e..362c6c37fd 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-tabs-base.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-tabs-base.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, NgZone, OnDestroy, OnInit } from '@angular/core'; +import { Component, NgZone, OnDestroy, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs'; import { filter, first, map, startWith, switchMap, withLatestFrom } from 'rxjs/operators'; @@ -15,22 +15,18 @@ import { StratosTabType, } from '../../../../../../core/src/core/extension/extension-service'; import { CurrentUserPermissionsService } from '../../../../../../core/src/core/permissions/current-user-permissions.service'; -import { getFavoriteFromEntity } from '../../../../../../core/src/core/user-favorite-helpers'; import { safeUnsubscribe } from '../../../../../../core/src/core/utils.service'; import { IPageSideNavTab } from '../../../../../../core/src/features/dashboard/page-side-nav/page-side-nav.component'; -import { - FavoritesConfigMapper, -} from '../../../../../../core/src/shared/components/favorites-meta-card/favorite-config-mapper'; import { IHeaderBreadcrumb } from '../../../../../../core/src/shared/components/page-header/page-header.types'; -import { ENTITY_SERVICE } from '../../../../../../core/src/shared/entity.tokens'; import { RouterNav } from '../../../../../../store/src/actions/router.actions'; import { entityCatalog } from '../../../../../../store/src/entity-catalog/entity-catalog'; -import { EntityService } from '../../../../../../store/src/entity-service'; +import { FavoritesConfigMapper } from '../../../../../../store/src/favorite-config-mapper'; import { EntitySchema } from '../../../../../../store/src/helpers/entity-schema'; import { ActionState } from '../../../../../../store/src/reducers/api-request-reducer/types'; import { endpointEntitiesSelector } from '../../../../../../store/src/selectors/endpoint.selectors'; import { APIResource } from '../../../../../../store/src/types/api.types'; import { EndpointModel } from '../../../../../../store/src/types/endpoint.types'; +import { getFavoriteFromEntity } from '../../../../../../store/src/user-favorite-helpers'; import { UpdateExistingApplication } from '../../../../actions/application.actions'; import { IApp, IOrganization, ISpace } from '../../../../cf-api.types'; import { CF_ENDPOINT_TYPE } from '../../../../cf-types'; @@ -61,7 +57,6 @@ export class ApplicationTabsBaseComponent implements OnInit, OnDestroy { constructor( public applicationService: ApplicationService, - @Inject(ENTITY_SERVICE) private entityService: EntityService, private store: Store, private endpointsService: EndpointsService, private ngZone: NgZone, @@ -246,7 +241,7 @@ export class ApplicationTabsBaseComponent implements OnInit, OnDestroy { } ngOnInit() { - this.appSub$ = this.entityService.entityMonitor.entityRequest$.subscribe(requestInfo => { + this.appSub$ = this.applicationService.entityService.entityMonitor.entityRequest$.subscribe(requestInfo => { if ( requestInfo.deleting.deleted || requestInfo.error @@ -257,7 +252,7 @@ export class ApplicationTabsBaseComponent implements OnInit, OnDestroy { this.isFetching$ = this.applicationService.isFetchingApp$; - this.isBusyUpdating$ = this.entityService.updatingSection$.pipe( + this.isBusyUpdating$ = this.applicationService.entityService.updatingSection$.pipe( map(updatingSection => { const updating = this.updatingSectionBusy(updatingSection.restaging) || this.updatingSectionBusy(updatingSection[UpdateExistingApplication.updateKey]); diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/build-tab/build-tab.component.html b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/build-tab/build-tab.component.html index a1ceaa6f53..de18350435 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/build-tab/build-tab.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/build-tab/build-tab.component.html @@ -149,12 +149,12 @@ - + Deployment Info - +
- - None - diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/build-tab/build-tab.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/build-tab/build-tab.component.spec.ts index 71731cb35b..58a38edbe5 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/build-tab/build-tab.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/build-tab/build-tab.component.spec.ts @@ -6,17 +6,15 @@ import { RouterTestingModule } from '@angular/router/testing'; import { CoreModule } from '../../../../../../../../core/src/core/core.module'; import { GITHUB_API_URL } from '../../../../../../../../core/src/core/github.helpers'; -import { APP_GUID, CF_GUID, ENTITY_SERVICE } from '../../../../../../../../core/src/shared/entity.tokens'; +import { APP_GUID, CF_GUID } from '../../../../../../../../core/src/shared/entity.tokens'; import { SharedModule } from '../../../../../../../../core/src/shared/shared.module'; import { TabNavService } from '../../../../../../../../core/tab-nav.service'; -import { EntityServiceFactory } from '../../../../../../../../store/src/entity-service-factory.service'; import { AppStoreModule } from '../../../../../../../../store/src/store.module'; import { ApplicationServiceMock } from '../../../../../../../test-framework/application-service-helper'; import { generateCfStoreModules } from '../../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CloudFoundrySharedModule } from '../../../../../../shared/cf-shared.module'; import { ApplicationStateService } from '../../../../../../shared/services/application-state.service'; import { ApplicationService } from '../../../../application.service'; -import { cfApplicationEntityServiceFactory } from '../../../application-base.component'; import { ApplicationPollComponent } from '../../application-poll/application-poll.component'; import { ApplicationPollingService } from '../../application-polling.service'; import { ApplicationEnvVarsHelper } from './application-env-vars.service'; @@ -53,11 +51,6 @@ describe('BuildTabComponent', () => { TabNavService, { provide: CF_GUID, useValue: '' }, { provide: APP_GUID, useValue: '' }, - { - provide: ENTITY_SERVICE, - useFactory: cfApplicationEntityServiceFactory, - deps: [CF_GUID, APP_GUID, EntityServiceFactory] - }, ApplicationPollingService ] }) diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/build-tab/build-tab.component.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/build-tab/build-tab.component.ts index 57c88faeb4..5f3574c8d7 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/build-tab/build-tab.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/build-tab/build-tab.component.ts @@ -1,20 +1,21 @@ -import { Component, Inject, OnInit } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { Store } from '@ngrx/store'; -import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; -import { combineLatest, delay, distinct, filter, first, map, mergeMap, startWith, tap } from 'rxjs/operators'; +import { combineLatest as observableCombineLatest, Observable, of as observableOf, of } from 'rxjs'; +import { combineLatest, delay, distinct, filter, first, map, mergeMap, startWith, switchMap, tap } from 'rxjs/operators'; import { AppMetadataTypes } from '../../../../../../../../cloud-foundry/src/actions/app-metadata.actions'; import { UpdateExistingApplication } from '../../../../../../../../cloud-foundry/src/actions/application.actions'; import { CFAppState } from '../../../../../../../../cloud-foundry/src/cf-app-state'; -import { getFullEndpointApiUrl } from '../../../../../../../../core/src/features/endpoints/endpoint-helpers'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ConfirmationDialogConfig } from '../../../../../../../../core/src/shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from '../../../../../../../../core/src/shared/components/confirmation-dialog.service'; -import { ENTITY_SERVICE } from '../../../../../../../../core/src/shared/entity.tokens'; import { ResetPagination } from '../../../../../../../../store/src/actions/pagination.actions'; -import { EntityService } from '../../../../../../../../store/src/entity-service'; +import { getFullEndpointApiUrl } from '../../../../../../../../store/src/endpoint-utils'; import { ActionState } from '../../../../../../../../store/src/reducers/api-request-reducer/types'; -import { APIResource, EntityInfo } from '../../../../../../../../store/src/types/api.types'; +import { EntityInfo } from '../../../../../../../../store/src/types/api.types'; import { IAppSummary } from '../../../../../../cf-api.types'; import { cfEntityCatalog } from '../../../../../../cf-entity-catalog'; import { GitSCMService, GitSCMType } from '../../../../../../shared/data-services/scm/scm.service'; @@ -62,11 +63,10 @@ export class BuildTabComponent implements OnInit { public applicationService: ApplicationService, private scmService: GitSCMService, private store: Store, - @Inject(ENTITY_SERVICE) private entityService: EntityService, private route: ActivatedRoute, private router: Router, private confirmDialog: ConfirmationDialogService, - + private cups: CurrentUserPermissionsService ) { } cardTwoFetching$: Observable; @@ -88,7 +88,7 @@ export class BuildTabComponent implements OnInit { return app.fetching || appSummary.entityRequestInfo.fetching; }), distinct()); - this.isBusyUpdating$ = this.entityService.updatingSection$.pipe( + this.isBusyUpdating$ = this.applicationService.entityService.updatingSection$.pipe( map(updatingSection => { const updating = this.updatingSectionBusy(updatingSection.restaging) || this.updatingSectionBusy(updatingSection[UpdateExistingApplication.updateKey]); @@ -108,8 +108,17 @@ export class BuildTabComponent implements OnInit { }) ); - this.deploySource$ = this.applicationService.applicationStratProject$.pipe( - combineLatest(this.applicationService.application$) + const canSeeEnvVars$ = this.applicationService.appSpace$.pipe( + switchMap(space => this.cups.can( + CfCurrentUserPermissions.APPLICATION_VIEW_ENV_VARS, + this.applicationService.cfGuid, + space.metadata.guid) + ) + ) + + const deploySource$ = observableCombineLatest( + this.applicationService.applicationStratProject$, + this.applicationService.application$ ).pipe( map(([project, app]) => { if (!!project) { @@ -149,6 +158,10 @@ export class BuildTabComponent implements OnInit { } }), startWith({ type: 'loading' }) + ) + + this.deploySource$ = canSeeEnvVars$.pipe( + switchMap(canSeeEnvVars => canSeeEnvVars ? deploySource$ : of(null)), ); } @@ -249,7 +262,7 @@ export class BuildTabComponent implements OnInit { } pollEntityService(state, stateString): Observable { - return this.entityService + return this.applicationService.entityService .poll(1000, state).pipe( delay(1), filter(({ resource }) => { diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/log-stream-tab/log-stream-tab.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/log-stream-tab/log-stream-tab.component.spec.ts index b300ad4f84..03b62309d1 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/log-stream-tab/log-stream-tab.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/log-stream-tab/log-stream-tab.component.spec.ts @@ -3,18 +3,14 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; import { StoreModule } from '@ngrx/store'; -import { GetApplication } from '../../../../../../../../cloud-foundry/src/actions/application.actions'; -import { cfEntityFactory } from '../../../../../../../../cloud-foundry/src/cf-entity-factory'; import { CoreModule } from '../../../../../../../../core/src/core/core.module'; import { MDAppModule } from '../../../../../../../../core/src/core/md.module'; import { LogViewerComponent } from '../../../../../../../../core/src/shared/components/log-viewer/log-viewer.component'; -import { generateTestEntityServiceProvider } from '../../../../../../../../core/test-framework/entity-service.helper'; import { EntityMonitorFactory } from '../../../../../../../../store/src/monitors/entity-monitor.factory.service'; import { PaginationMonitorFactory } from '../../../../../../../../store/src/monitors/pagination-monitor.factory'; import { AppStoreModule } from '../../../../../../../../store/src/store.module'; import { generateTestApplicationServiceProvider } from '../../../../../../../test-framework/application-service-helper'; import { generateCfStoreModules } from '../../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { applicationEntityType } from '../../../../../../cf-entity-types'; import { ApplicationStateService } from '../../../../../../shared/services/application-state.service'; import { ApplicationEnvVarsHelper } from '../build-tab/application-env-vars.service'; import { LogStreamTabComponent } from './log-stream-tab.component'; @@ -41,11 +37,6 @@ describe('LogStreamTabComponent', () => { LogStreamTabComponent ], providers: [ - generateTestEntityServiceProvider( - appId, - cfEntityFactory(applicationEntityType), - new GetApplication(appId, cfId) - ), generateTestApplicationServiceProvider(cfId, appId), AppStoreModule, ApplicationStateService, diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/metrics-tab/metrics-tab.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/metrics-tab/metrics-tab.component.spec.ts index 6bb64cb28b..42bf58fa38 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/metrics-tab/metrics-tab.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/metrics-tab/metrics-tab.component.spec.ts @@ -1,14 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { GetApplication } from '../../../../../../../../cloud-foundry/src/actions/application.actions'; -import { cfEntityFactory } from '../../../../../../../../cloud-foundry/src/cf-entity-factory'; import { MDAppModule } from '../../../../../../../../core/src/core/md.module'; import { SharedModule } from '../../../../../../../../core/src/shared/shared.module'; -import { generateTestEntityServiceProvider } from '../../../../../../../../core/test-framework/entity-service.helper'; import { generateTestApplicationServiceProvider } from '../../../../../../../test-framework/application-service-helper'; import { generateCfStoreModules } from '../../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { applicationEntityType } from '../../../../../../cf-entity-types'; import { ApplicationStateService } from '../../../../../../shared/services/application-state.service'; import { ApplicationEnvVarsHelper } from '../build-tab/application-env-vars.service'; import { MetricsTabComponent } from './metrics-tab.component'; @@ -30,11 +26,6 @@ describe('MetricsTabComponent', () => { providers: [ ApplicationStateService, ApplicationEnvVarsHelper, - generateTestEntityServiceProvider( - appId, - cfEntityFactory(applicationEntityType), - new GetApplication(appId, cfId) - ), generateTestApplicationServiceProvider(cfId, appId), ] }) diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/cli-info-application/cli-info-application.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/cli-info-application/cli-info-application.component.spec.ts index c3ead1317a..ff36e161d2 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/cli-info-application/cli-info-application.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/cli-info-application/cli-info-application.component.spec.ts @@ -1,16 +1,12 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { GetApplication } from '../../../../../cloud-foundry/src/actions/application.actions'; -import { cfEntityFactory } from '../../../../../cloud-foundry/src/cf-entity-factory'; import { CoreModule } from '../../../../../core/src/core/core.module'; import { MDAppModule } from '../../../../../core/src/core/md.module'; import { SharedModule } from '../../../../../core/src/shared/shared.module'; import { TabNavService } from '../../../../../core/tab-nav.service'; -import { generateTestEntityServiceProvider } from '../../../../../core/test-framework/entity-service.helper'; import { generateTestApplicationServiceProvider } from '../../../../test-framework/application-service-helper'; import { generateCfStoreModules } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { applicationEntityType } from '../../../cf-entity-types'; import { CloudFoundrySharedModule } from '../../../shared/cf-shared.module'; import { ApplicationStateService } from '../../../shared/services/application-state.service'; import { ApplicationEnvVarsHelper } from '../application/application-tabs-base/tabs/build-tab/application-env-vars.service'; @@ -35,11 +31,6 @@ describe('CliInfoApplicationComponent', () => { CloudFoundrySharedModule ], providers: [ - generateTestEntityServiceProvider( - appId, - cfEntityFactory(applicationEntityType), - new GetApplication(appId, cfId) - ), generateTestApplicationServiceProvider(cfId, appId), ApplicationStateService, ApplicationEnvVarsHelper, diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/cli-info-application/cli-info-application.component.ts b/src/frontend/packages/cloud-foundry/src/features/applications/cli-info-application/cli-info-application.component.ts index 9785603d75..1681bae4ad 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/cli-info-application/cli-info-application.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/cli-info-application/cli-info-application.component.ts @@ -2,11 +2,10 @@ import { Component, OnInit } from '@angular/core'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { filter, first, map } from 'rxjs/operators'; -import { EntityService } from '../../../../../store/src/entity-service'; -import { EntityServiceFactory } from '../../../../../store/src/entity-service-factory.service'; -import { getFullEndpointApiUrl } from '../../../../../core/src/features/endpoints/endpoint-helpers'; import { IHeaderBreadcrumb } from '../../../../../core/src/shared/components/page-header/page-header.types'; -import { GetAllEndpoints } from '../../../../../store/src/actions/endpoint.actions'; +import { getFullEndpointApiUrl } from '../../../../../store/src/endpoint-utils'; +import { EntityService } from '../../../../../store/src/entity-service'; +import { stratosEntityCatalog } from '../../../../../store/src/stratos-entity-catalog'; import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; import { CFAppCLIInfoContext } from '../../../shared/components/cli-info/cli-info.component'; import { ApplicationService } from '../application.service'; @@ -29,7 +28,6 @@ export class CliInfoApplicationComponent implements OnInit { constructor( private applicationService: ApplicationService, - private entityServiceFactory: EntityServiceFactory ) { this.breadcrumbs$ = new BehaviorSubject([]); } @@ -41,10 +39,7 @@ export class CliInfoApplicationComponent implements OnInit { } private setupObservables(cfGuid: string) { - this.cfEndpointEntityService = this.entityServiceFactory.create( - cfGuid, - new GetAllEndpoints() - ); + this.cfEndpointEntityService = stratosEntityCatalog.endpoint.store.getEntityService(cfGuid); this.context$ = combineLatest( this.applicationService.application$, diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/edit-application/edit-application.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/edit-application/edit-application.component.spec.ts index f25b7fa87f..e5de781a2a 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/edit-application/edit-application.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/edit-application/edit-application.component.spec.ts @@ -4,18 +4,14 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { GetApplication } from '../../../../../cloud-foundry/src/actions/application.actions'; -import { cfEntityFactory } from '../../../../../cloud-foundry/src/cf-entity-factory'; import { CoreModule } from '../../../../../core/src/core/core.module'; import { SharedModule } from '../../../../../core/src/shared/shared.module'; import { TabNavService } from '../../../../../core/tab-nav.service'; -import { generateTestEntityServiceProvider } from '../../../../../core/test-framework/entity-service.helper'; import { ApplicationServiceMock, generateTestApplicationServiceProvider, } from '../../../../test-framework/application-service-helper'; import { generateCfStoreModules } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { applicationEntityType } from '../../../cf-entity-types'; import { ApplicationStateService } from '../../../shared/services/application-state.service'; import { ApplicationService } from '../application.service'; import { ApplicationEnvVarsHelper } from '../application/application-tabs-base/tabs/build-tab/application-env-vars.service'; @@ -42,11 +38,6 @@ describe('EditApplicationComponent', () => { ], providers: [ { provide: ApplicationService, useClass: ApplicationServiceMock }, - generateTestEntityServiceProvider( - appId, - cfEntityFactory(applicationEntityType), - new GetApplication(appId, cfId) - ), generateTestApplicationServiceProvider(cfId, appId), ApplicationStateService, ApplicationEnvVarsHelper, diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/create-organization-step/create-organization-step.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/create-organization-step/create-organization-step.component.ts index c5e99f073c..acc4f55a0c 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/create-organization-step/create-organization-step.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/create-organization-step/create-organization-step.component.ts @@ -14,7 +14,7 @@ import { import { selectCfRequestInfo } from '../../../../../../cloud-foundry/src/store/selectors/api.selectors'; import { StepOnNextFunction } from '../../../../../../core/src/shared/components/stepper/step/step.component'; import { entityCatalog } from '../../../../../../store/src/entity-catalog/entity-catalog'; -import { endpointSchemaKey } from '../../../../../../store/src/helpers/entity-factory'; +import { endpointEntityType } from '../../../../../../store/src/helpers/stratos-entity-factory'; import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; import { getPaginationObservables } from '../../../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper'; import { APIResource } from '../../../../../../store/src/types/api.types'; @@ -75,7 +75,7 @@ export class CreateOrganizationStepComponent implements OnInit, OnDestroy { tap((o) => this.allOrgs = o) ); - const quotaPaginationKey = createEntityRelationPaginationKey(endpointSchemaKey, this.cfGuid); + const quotaPaginationKey = createEntityRelationPaginationKey(endpointEntityType, this.cfGuid); this.quotaDefinitions$ = cfEntityCatalog.quotaDefinition.store.getPaginationService( quotaPaginationKey, this.cfGuid, { includeRelations: [] } ).entities$.pipe( diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cf-cell.helpers.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cf-cell.helpers.ts index c0673a5c49..375a28581a 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cf-cell.helpers.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cf-cell.helpers.ts @@ -3,12 +3,12 @@ import { Observable, of } from 'rxjs'; import { filter, first, map, publishReplay, refCount, switchMap } from 'rxjs/operators'; import { endpointHasMetricsByAvailable } from '../../../../core/src/features/endpoints/endpoint-helpers'; -import { MetricQueryType } from '../../../../core/src/shared/services/metrics-range-selector.types'; import { MetricQueryConfig } from '../../../../store/src/actions/metrics.actions'; import { AppState } from '../../../../store/src/app-state'; import { PaginationMonitorFactory } from '../../../../store/src/monitors/pagination-monitor.factory'; import { getPaginationObservables } from '../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper'; import { IMetrics } from '../../../../store/src/types/base-metric.types'; +import { MetricQueryType } from '../../../../store/src/types/metric.types'; import { FetchCFCellMetricsPaginatedAction } from '../../actions/cf-metrics.actions'; import { CFEntityConfig } from '../../cf-types'; import { CellMetrics } from './tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cf.helpers.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cf.helpers.ts index 675a1f4758..e6e64456f7 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cf.helpers.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cf.helpers.ts @@ -28,9 +28,10 @@ import { APIResource } from '../../../../store/src/types/api.types'; import { EndpointModel } from '../../../../store/src/types/endpoint.types'; import { PaginatedAction, PaginationEntityState } from '../../../../store/src/types/pagination.types'; import { IServiceInstance, IUserProvidedServiceInstance } from '../../cf-api-svc.types'; -import { CFFeatureFlagTypes, ISpace } from '../../cf-api.types'; +import { CFFeatureFlagTypes, IApp, ISpace } from '../../cf-api.types'; import { cfEntityFactory } from '../../cf-entity-factory'; import { CFEntityConfig } from '../../cf-types'; +import { ListCfRoute } from '../../shared/components/list/list-types/cf-routes/cf-routes-data-source-base'; import { CfUser, CfUserRoleParams, @@ -350,12 +351,17 @@ export function fetchTotalResults( ); } +type CfOrgSpaceFilterTypes = IApp | ListCfRoute | IServiceInstance; export const cfOrgSpaceFilter = (entities: APIResource[], paginationState: PaginationEntityState) => { // Filtering is done remotely when maxedResults are hit (see `setMultiFilter`) if (!!paginationState.maxedState.isMaxedMode && !paginationState.maxedState.ignoreMaxed) { return entities; } + const fetchOrgGuid = (e: APIResource): string => { + return e.entity.space ? e.entity.space.entity.organization_guid : null; + } + // Filter by cf/org/space const cfGuid = paginationState.clientPagination.filter.items.cf; const orgGuid = paginationState.clientPagination.filter.items.org; @@ -363,7 +369,7 @@ export const cfOrgSpaceFilter = (entities: APIResource[], paginationState: Pagin return !cfGuid && !orgGuid && !spaceGuid ? entities : entities.filter(e => { e = extractActualListEntity(e); const validCF = !(cfGuid && cfGuid !== e.entity.cfGuid); - const validOrg = !(orgGuid && orgGuid !== e.entity.space.entity.organization_guid); + const validOrg = !(orgGuid && orgGuid !== fetchOrgGuid(e)); const validSpace = !(spaceGuid && spaceGuid !== e.entity.space_guid); return validCF && validOrg && validSpace; }); diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.ts index 82ddcf4295..3449967904 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.ts @@ -5,9 +5,9 @@ import { first, map } from 'rxjs/operators'; import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; import { CFAppCLIInfoContext } from '../../../../../cloud-foundry/src/shared/components/cli-info/cli-info.component'; -import { getFullEndpointApiUrl } from '../../../../../core/src/features/endpoints/endpoint-helpers'; import { IHeaderBreadcrumb } from '../../../../../core/src/shared/components/page-header/page-header.types'; import { RouterNav } from '../../../../../store/src/actions/router.actions'; +import { getFullEndpointApiUrl } from '../../../../../store/src/endpoint-utils'; import { APIResource, EntityInfo } from '../../../../../store/src/types/api.types'; import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; import { getPreviousRoutingState } from '../../../../../store/src/types/routing.type'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.ts index 57ddd512a7..c086f9cd86 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.ts @@ -13,7 +13,7 @@ import { import { CurrentUserPermissionsService } from '../../../../../core/src/core/permissions/current-user-permissions.service'; import { environment } from '../../../../../core/src/environments/environment.prod'; import { IPageSideNavTab } from '../../../../../core/src/features/dashboard/page-side-nav/page-side-nav.component'; -import { FavoritesConfigMapper } from '../../../../../core/src/shared/components/favorites-meta-card/favorite-config-mapper'; +import { FavoritesConfigMapper } from '../../../../../store/src/favorite-config-mapper'; import { UserFavoriteEndpoint } from '../../../../../store/src/types/user-favorites.types'; import { CfCurrentUserPermissions } from '../../../user-permissions/cf-user-permissions-checkers'; import { CloudFoundryEndpointService } from '../services/cloud-foundry-endpoint.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.ts index cc3e90927d..5a91738c56 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.ts @@ -11,7 +11,7 @@ import { } from '../../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; import { safeUnsubscribe } from '../../../../../../core/src/core/utils.service'; import { StepOnNextFunction } from '../../../../../../core/src/shared/components/stepper/step/step.component'; -import { endpointSchemaKey } from '../../../../../../store/src/helpers/entity-factory'; +import { endpointEntityType } from '../../../../../../store/src/helpers/stratos-entity-factory'; import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; import { ActionState } from '../../../../../../store/src/reducers/api-request-reducer/types'; import { getPaginationObservables } from '../../../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper'; @@ -114,7 +114,7 @@ export class EditOrganizationStepComponent implements OnInit, OnDestroy { ); this.fetchOrgsSub = this.allOrgsInEndpoint$.subscribe(); - const quotaPaginationKey = createEntityRelationPaginationKey(endpointSchemaKey, this.cfGuid); + const quotaPaginationKey = createEntityRelationPaginationKey(endpointEntityType, this.cfGuid); this.quotaDefinitions$ = cfEntityCatalog.quotaDefinition.store.getPaginationService( quotaPaginationKey, this.cfGuid, { includeRelations: [] } ).entities$.pipe( diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition-form/quota-definition-form.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition-form/quota-definition-form.component.ts index f271b4ec36..d330a31aec 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition-form/quota-definition-form.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition-form/quota-definition-form.component.ts @@ -8,7 +8,7 @@ import { createEntityRelationPaginationKey } from '../../../../../cloud-foundry/ import { ActiveRouteCfOrgSpace } from '../../../../../cloud-foundry/src/features/cloud-foundry/cf-page.types'; import { getActiveRouteCfOrgSpaceProvider } from '../../../../../cloud-foundry/src/features/cloud-foundry/cf.helpers'; import { safeUnsubscribe } from '../../../../../core/src/core/utils.service'; -import { endpointSchemaKey } from '../../../../../store/src/helpers/entity-factory'; +import { endpointEntityType } from '../../../../../store/src/helpers/stratos-entity-factory'; import { IQuotaDefinition } from '../../../cf-api.types'; export interface QuotaFormValues { @@ -71,7 +71,7 @@ export class QuotaDefinitionFormComponent implements OnInit, OnDestroy { } fetchQuotasDefinitions() { - const quotaPaginationKey = createEntityRelationPaginationKey(endpointSchemaKey, this.cfGuid); + const quotaPaginationKey = createEntityRelationPaginationKey(endpointEntityType, this.cfGuid); const quotaDefinitions$ = cfEntityCatalog.quotaDefinition.store.getPaginationService(quotaPaginationKey, this.cfGuid, {}) .entities$.pipe( filter(o => !!o), diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts index 7f489dbfb6..eec79aba70 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts @@ -17,13 +17,12 @@ import { createEntityRelationPaginationKey, } from '../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; import { CfApplicationState } from '../../../../../cloud-foundry/src/store/types/application.types'; -import { GetAllEndpoints } from '../../../../../store/src/actions/endpoint.actions'; import { EntityService } from '../../../../../store/src/entity-service'; -import { EntityServiceFactory } from '../../../../../store/src/entity-service-factory.service'; -import { endpointSchemaKey } from '../../../../../store/src/helpers/entity-factory'; +import { endpointEntityType } from '../../../../../store/src/helpers/stratos-entity-factory'; import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; import { getPaginationObservables } from '../../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper'; import { PaginationObservables } from '../../../../../store/src/reducers/pagination-reducer/pagination-reducer.types'; +import { stratosEntityCatalog } from '../../../../../store/src/stratos-entity-catalog'; import { APIResource, EntityInfo } from '../../../../../store/src/types/api.types'; import { EndpointModel, EndpointUser } from '../../../../../store/src/types/endpoint.types'; import { PaginatedAction } from '../../../../../store/src/types/pagination.types'; @@ -69,8 +68,8 @@ export class CloudFoundryEndpointService { static createGetAllOrganizations(cfGuid: string) { const paginationKey = cfGuid ? - createEntityRelationPaginationKey(endpointSchemaKey, cfGuid) - : createEntityRelationPaginationKey(endpointSchemaKey); + createEntityRelationPaginationKey(endpointEntityType, cfGuid) + : createEntityRelationPaginationKey(endpointEntityType); const getAllOrganizationsAction = cfEntityCatalog.org.actions.getMultiple(cfGuid, paginationKey, { includeRelations: [ @@ -84,8 +83,8 @@ export class CloudFoundryEndpointService { } static createGetAllOrganizationsLimitedSchema(cfGuid: string) { const paginationKey = cfGuid ? - createEntityRelationPaginationKey(endpointSchemaKey, cfGuid) - : createEntityRelationPaginationKey(endpointSchemaKey); + createEntityRelationPaginationKey(endpointEntityType, cfGuid) + : createEntityRelationPaginationKey(endpointEntityType); const getAllOrganizationsAction = cfEntityCatalog.org.actions.getMultiple(cfGuid, paginationKey, { includeRelations: [ @@ -138,16 +137,12 @@ export class CloudFoundryEndpointService { constructor( public activeRouteCfOrgSpace: ActiveRouteCfOrgSpace, private store: Store, - private entityServiceFactory: EntityServiceFactory, private cfUserService: CfUserService, private pmf: PaginationMonitorFactory, ) { this.cfGuid = activeRouteCfOrgSpace.cfGuid; - this.cfEndpointEntityService = this.entityServiceFactory.create( - this.cfGuid, - new GetAllEndpoints() - ); + this.cfEndpointEntityService = stratosEntityCatalog.endpoint.store.getEntityService(this.cfGuid); this.cfInfoEntityService = cfEntityCatalog.cfInfo.store.getEntityService(this.cfGuid) this.constructCoreObservables(); diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-org-space-quota.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-org-space-quota.ts index 0e89121107..e02ed1ae30 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-org-space-quota.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-org-space-quota.ts @@ -3,9 +3,9 @@ import { filter, first, map, switchMap } from 'rxjs/operators'; import { truthyIncludingZero } from '../../../../../core/src/core/utils.service'; import { determineCardStatus } from '../../../../../core/src/shared/components/cards/card-status/card-status.component'; -import { StratosStatus } from '../../../../../core/src/shared/shared.types'; import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; import { APIResource } from '../../../../../store/src/types/api.types'; +import { StratosStatus } from '../../../../../store/src/types/shared.types'; import { IApp, IOrganization, ISpace } from '../../../cf-api.types'; import { CFEntityConfig } from '../../../cf-types'; import { CloudFoundryEndpointService } from './cloud-foundry-endpoint.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization-quota.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization-quota.ts index 932894115e..9c3bcbf845 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization-quota.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization-quota.ts @@ -1,9 +1,9 @@ import { Observable } from 'rxjs'; import { organizationEntityType } from '../../../../../cloud-foundry/src/cf-entity-types'; -import { StratosStatus } from '../../../../../core/src/shared/shared.types'; import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; import { APIResource } from '../../../../../store/src/types/api.types'; +import { StratosStatus } from '../../../../../store/src/types/shared.types'; import { IApp, IOrganization } from '../../../cf-api.types'; import { getEntityFlattenedList, getStartedAppInstanceCount } from '../../../cf.helpers'; import { CloudFoundryEndpointService } from './cloud-foundry-endpoint.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space-quota.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space-quota.ts index 1be45b3e30..692fad26d8 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space-quota.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space-quota.ts @@ -1,9 +1,9 @@ import { Observable } from 'rxjs'; import { spaceEntityType } from '../../../../../cloud-foundry/src/cf-entity-types'; -import { StratosStatus } from '../../../../../core/src/shared/shared.types'; import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; import { APIResource } from '../../../../../store/src/types/api.types'; +import { StratosStatus } from '../../../../../store/src/types/shared.types'; import { IApp, ISpace } from '../../../cf-api.types'; import { getStartedAppInstanceCount } from '../../../cf.helpers'; import { CloudFoundryEndpointService } from './cloud-foundry-endpoint.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition-form/space-quota-definition-form.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition-form/space-quota-definition-form.component.ts index a6bacfc2c6..5e648aba80 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition-form/space-quota-definition-form.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition-form/space-quota-definition-form.component.ts @@ -9,7 +9,7 @@ import { createEntityRelationPaginationKey } from '../../../../../cloud-foundry/ import { ActiveRouteCfOrgSpace } from '../../../../../cloud-foundry/src/features/cloud-foundry/cf-page.types'; import { getActiveRouteCfOrgSpaceProvider } from '../../../../../cloud-foundry/src/features/cloud-foundry/cf.helpers'; import { safeUnsubscribe } from '../../../../../core/src/core/utils.service'; -import { endpointSchemaKey } from '../../../../../store/src/helpers/entity-factory'; +import { endpointEntityType } from '../../../../../store/src/helpers/stratos-entity-factory'; import { IQuotaDefinition } from '../../../cf-api.types'; @@ -65,7 +65,7 @@ export class SpaceQuotaDefinitionFormComponent implements OnInit, OnDestroy { this.spaceQuotaDefinitions$ = cfEntityCatalog.spaceQuota.store.getAllInOrganization.getPaginationService( this.orgGuid, this.cfGuid, - createEntityRelationPaginationKey(endpointSchemaKey, this.cfGuid) + createEntityRelationPaginationKey(endpointEntityType, this.cfGuid) ).entities$ .pipe( filter(o => !!o), diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition/space-quota-definition.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition/space-quota-definition.component.spec.ts index 6ef73eb96f..ee79e073f2 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition/space-quota-definition.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition/space-quota-definition.component.spec.ts @@ -3,10 +3,10 @@ import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; import { testSCFEndpoint, testSCFEndpointGuid } from '@stratosui/store/testing'; -import { endpointEntitySchema } from '../../../../../core/src/base-entity-schemas'; import { TabNavService } from '../../../../../core/tab-nav.service'; import { EntityCatalogHelpers } from '../../../../../store/src/entity-catalog/entity-catalog.helper'; import { EntityCatalogEntityConfig } from '../../../../../store/src/entity-catalog/entity-catalog.types'; +import { endpointEntityType, stratosEntityFactory } from '../../../../../store/src/helpers/stratos-entity-factory'; import { NormalizedResponse } from '../../../../../store/src/types/api.types'; import { WrapperRequestActionSuccess } from '../../../../../store/src/types/request.types'; import { @@ -51,7 +51,7 @@ describe('SpaceQuotaDefinitionComponent', () => { .compileComponents(); - const stratosEndpointEntityConfig: EntityCatalogEntityConfig = endpointEntitySchema; + const stratosEndpointEntityConfig: EntityCatalogEntityConfig = stratosEntityFactory(endpointEntityType); const stratosEndpointEntityKey = EntityCatalogHelpers.buildEntityKey( stratosEndpointEntityConfig.entityType, stratosEndpointEntityConfig.endpointType diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.ts index 07a77cddd6..cd137d502e 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.ts @@ -2,9 +2,9 @@ import { Component } from '@angular/core'; import { Observable } from 'rxjs'; import { first, map } from 'rxjs/operators'; -import { metricEntityType } from '../../../../../../../../core/src/base-entity-schemas'; import { IPageSideNavTab } from '../../../../../../../../core/src/features/dashboard/page-side-nav/page-side-nav.component'; import { IHeaderBreadcrumb } from '../../../../../../../../core/src/shared/components/page-header/page-header.types'; +import { metricEntityType } from '../../../../../../../../store/src/helpers/stratos-entity-factory'; import { cfEntityFactory } from '../../../../../../cf-entity-factory'; import { getActiveRouteCfCellProvider } from '../../../../cf.helpers'; import { CloudFoundryEndpointService } from '../../../../services/cloud-foundry-endpoint.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.ts index 081ec14430..e43d9ca244 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.ts @@ -4,9 +4,8 @@ import { MetricsConfig } from '../../../../../../../../core/src/shared/component import { MetricsLineChartConfig, } from '../../../../../../../../core/src/shared/components/metrics-chart/metrics-chart.types'; -import { MetricQueryType } from '../../../../../../../../core/src/shared/services/metrics-range-selector.types'; import { IMetricMatrixResult } from '../../../../../../../../store/src/types/base-metric.types'; -import { IMetricCell } from '../../../../../../../../store/src/types/metric.types'; +import { IMetricCell, MetricQueryType } from '../../../../../../../../store/src/types/metric.types'; import { CloudFoundryCellService } from '../cloud-foundry-cell.service'; @Component({ diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.spec.ts index e8c160cbcf..669e75977b 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.spec.ts @@ -10,8 +10,8 @@ import { import { MetricsChartHelpers, } from '../../../../../../../../core/src/shared/components/metrics-chart/metrics.component.helpers'; -import { MetricQueryType } from '../../../../../../../../core/src/shared/services/metrics-range-selector.types'; import { MetricQueryConfig } from '../../../../../../../../store/src/actions/metrics.actions'; +import { MetricQueryType } from '../../../../../../../../store/src/types/metric.types'; import { generateCfBaseTestModules } from '../../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { FetchCFCellMetricsAction } from '../../../../../../actions/cf-metrics.actions'; import { ActiveRouteCfCell } from '../../../../cf-page.types'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.ts index e3427cd79b..2109f6dee5 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.ts @@ -3,7 +3,7 @@ import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ListConfig } from '../../../../../../../../core/src/shared/components/list/list.component.types'; -import { StratosStatus } from '../../../../../../../../core/src/shared/shared.types'; +import { StratosStatus } from '../../../../../../../../store/src/types/shared.types'; import { CfCellHealthListConfigService, } from '../../../../../../shared/components/list/list-types/cf-cell-health/cf-cell-health-list-config.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell.service.ts index fb6c1e76ec..708a438723 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell.service.ts @@ -8,13 +8,12 @@ import { MetricsLineChartConfig } from '../../../../../../../core/src/shared/com import { MetricsChartHelpers, } from '../../../../../../../core/src/shared/components/metrics-chart/metrics.component.helpers'; -import { MetricQueryType } from '../../../../../../../core/src/shared/services/metrics-range-selector.types'; import { MetricQueryConfig } from '../../../../../../../store/src/actions/metrics.actions'; import { AppState } from '../../../../../../../store/src/app-state'; import { EntityServiceFactory } from '../../../../../../../store/src/entity-service-factory.service'; import { PaginationMonitorFactory } from '../../../../../../../store/src/monitors/pagination-monitor.factory'; import { IMetricMatrixResult, IMetrics, IMetricVectorResult } from '../../../../../../../store/src/types/base-metric.types'; -import { IMetricCell } from '../../../../../../../store/src/types/metric.types'; +import { IMetricCell, MetricQueryType } from '../../../../../../../store/src/types/metric.types'; import { FetchCFCellMetricsAction } from '../../../../../actions/cf-metrics.actions'; import { CfCellHelper } from '../../../cf-cell.helpers'; import { ActiveRouteCfCell } from '../../../cf-page.types'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-base/cloud-foundry-organization-base.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-base/cloud-foundry-organization-base.component.ts index 0c83c22d0f..f9a24ac961 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-base/cloud-foundry-organization-base.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-base/cloud-foundry-organization-base.component.ts @@ -11,15 +11,13 @@ import { StratosActionType, StratosTabType, } from '../../../../../../../core/src/core/extension/extension-service'; -import { getFavoriteFromEntity } from '../../../../../../../core/src/core/user-favorite-helpers'; import { environment } from '../../../../../../../core/src/environments/environment.prod'; import { IPageSideNavTab } from '../../../../../../../core/src/features/dashboard/page-side-nav/page-side-nav.component'; -import { - FavoritesConfigMapper, -} from '../../../../../../../core/src/shared/components/favorites-meta-card/favorite-config-mapper'; import { IHeaderBreadcrumb } from '../../../../../../../core/src/shared/components/page-header/page-header.types'; +import { FavoritesConfigMapper } from '../../../../../../../store/src/favorite-config-mapper'; import { EntitySchema } from '../../../../../../../store/src/helpers/entity-schema'; import { UserFavorite } from '../../../../../../../store/src/types/user-favorites.types'; +import { getFavoriteFromEntity } from '../../../../../../../store/src/user-favorite-helpers'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; import { CF_ENDPOINT_TYPE } from '../../../../../cf-types'; import { CfUserService } from '../../../../../shared/data-services/cf-user.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.ts index c8c42c5c5a..5ae190a43b 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.ts @@ -13,16 +13,14 @@ import { StratosActionType, StratosTabType, } from '../../../../../../../../core/src/core/extension/extension-service'; -import { getFavoriteFromEntity } from '../../../../../../../../core/src/core/user-favorite-helpers'; import { environment } from '../../../../../../../../core/src/environments/environment.prod'; import { IPageSideNavTab } from '../../../../../../../../core/src/features/dashboard/page-side-nav/page-side-nav.component'; import { ConfirmationDialogService } from '../../../../../../../../core/src/shared/components/confirmation-dialog.service'; -import { - FavoritesConfigMapper, -} from '../../../../../../../../core/src/shared/components/favorites-meta-card/favorite-config-mapper'; import { IHeaderBreadcrumb } from '../../../../../../../../core/src/shared/components/page-header/page-header.types'; import { RouterNav } from '../../../../../../../../store/src/actions/router.actions'; +import { FavoritesConfigMapper } from '../../../../../../../../store/src/favorite-config-mapper'; import { UserFavorite } from '../../../../../../../../store/src/types/user-favorites.types'; +import { getFavoriteFromEntity } from '../../../../../../../../store/src/user-favorite-helpers'; import { cfEntityFactory } from '../../../../../../cf-entity-factory'; import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; import { CfUserService } from '../../../../../../shared/data-services/cf-user.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/user-invite.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/user-invite.service.ts index 755128bd1b..7331193e11 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/user-invite.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/user-invite.service.ts @@ -10,7 +10,7 @@ import { CurrentUserPermissionsService } from '../../../../../core/src/core/perm import { environment } from '../../../../../core/src/environments/environment.prod'; import { ConfirmationDialogConfig } from '../../../../../core/src/shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from '../../../../../core/src/shared/components/confirmation-dialog.service'; -import { GetSystemInfo } from '../../../../../store/src/actions/system.actions'; +import { stratosEntityCatalog } from '../../../../../store/src/stratos-entity-catalog'; import { CfCurrentUserPermissions } from '../../../user-permissions/cf-user-permissions-checkers'; import { ActiveRouteCfOrgSpace } from '../cf-page.types'; import { waitForCFPermissions } from '../cf.helpers'; @@ -57,7 +57,6 @@ interface UserInviteSend { export class UserInviteConfigureService { constructor( - private store: Store, private http: HttpClient, private snackBar: MatSnackBar, private confirmDialog: ConfirmationDialogService, @@ -70,7 +69,7 @@ export class UserInviteConfigureService { const url = `/pp/${proxyAPIVersion}/invite/${cfGUID}`; const obs$ = this.http.post(url, formData).pipe( map(v => { - this.store.dispatch(new GetSystemInfo()); + stratosEntityCatalog.systemInfo.api.getSystemInfo() return { error: false }; @@ -99,7 +98,7 @@ export class UserInviteConfigureService { const url = `/pp/${proxyAPIVersion}/invite/${cfGUID}`; this.http.delete(url).pipe( map(v => { - this.store.dispatch(new GetSystemInfo()); + stratosEntityCatalog.systemInfo.api.getSystemInfo() return { error: false, errorMessage: '' diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.ts index 56a88ed334..faa4668b08 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.ts @@ -18,7 +18,7 @@ import { createEntityRelationPaginationKey, } from '../../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; import { CurrentUserPermissionsService } from '../../../../../../core/src/core/permissions/current-user-permissions.service'; -import { endpointSchemaKey } from '../../../../../../store/src/helpers/entity-factory'; +import { endpointEntityType } from '../../../../../../store/src/helpers/stratos-entity-factory'; import { APIResource, EntityInfo } from '../../../../../../store/src/types/api.types'; import { UsersRolesSetChanges } from '../../../../actions/users-roles.actions'; import { IOrganization, ISpace } from '../../../../cf-api.types'; @@ -239,7 +239,7 @@ export class CfRolesService { fetchOrgs(cfGuid: string): Observable[]> { if (!this.cfOrgs[cfGuid]) { - const paginationKey = createEntityRelationPaginationKey(endpointSchemaKey, cfGuid); + const paginationKey = createEntityRelationPaginationKey(endpointEntityType, cfGuid); const orgs$ = cfEntityCatalog.org.store.getPaginationService( cfGuid, paginationKey, diff --git a/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.html b/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.html index f6b4c53c11..4a7bb72774 100644 --- a/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.html @@ -1,11 +1,11 @@
- {{ getServiceLabel() | async }} + {{ serviceLabel$ | async }}
- diff --git a/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.ts b/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.ts index 817f55a6e2..763af35529 100644 --- a/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.ts @@ -6,6 +6,7 @@ import { map, publishReplay, refCount } from 'rxjs/operators'; import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; import { IPageSideNavTab } from '../../../../../core/src/features/dashboard/page-side-nav/page-side-nav.component'; import { IHeaderBreadcrumb } from '../../../../../core/src/shared/components/page-header/page-header.types'; +import { CSI_CANCEL_URL } from '../../../shared/components/add-service-instance/csi-mode.service'; import { CfCurrentUserPermissions } from '../../../user-permissions/cf-user-permissions-checkers'; import { getServiceName } from '../services-helper'; import { ServicesService } from '../services.service'; @@ -20,6 +21,9 @@ export class ServiceTabsBaseComponent { toolTipText$: Observable; hasVisiblePlans$: Observable; servicesSubscription: Subscription; + isServiceSpaceScoped$: Observable; + addServiceInstanceLink: string[]; + serviceLabel$: Observable; tabLinks: IPageSideNavTab[] = [ { @@ -59,24 +63,23 @@ export class ServiceTabsBaseComponent { return 'Cannot create service instance (no public or visible plans exist for service)'; } })); - - } - - addServiceInstanceLink = () => [ - '/marketplace', - this.servicesService.cfGuid, - this.servicesService.serviceGuid, - 'create' - ] - - isServiceSpaceScoped = () => this.servicesService.isSpaceScoped$; - - getServiceLabel = (): Observable => { - return this.servicesService.service$.pipe( + this.isServiceSpaceScoped$ = this.servicesService.isSpaceScoped$.pipe( + map(queryParams => ({ + ...queryParams, + [CSI_CANCEL_URL]: `/marketplace/${this.servicesService.cfGuid}/${this.servicesService.serviceGuid}/instances` + })) + ) + this.addServiceInstanceLink = [ + '/marketplace', + this.servicesService.cfGuid, + this.servicesService.serviceGuid, + 'create' + ] + this.serviceLabel$ = this.servicesService.service$.pipe( map(getServiceName), publishReplay(1), refCount() - ); + ) } } diff --git a/src/frontend/packages/cloud-foundry/src/features/service-catalog/services-helper.ts b/src/frontend/packages/cloud-foundry/src/features/service-catalog/services-helper.ts index 9f55596f21..d3fce2628e 100644 --- a/src/frontend/packages/cloud-foundry/src/features/service-catalog/services-helper.ts +++ b/src/frontend/packages/cloud-foundry/src/features/service-catalog/services-helper.ts @@ -5,10 +5,10 @@ import { combineLatest, filter, first, map, share, switchMap } from 'rxjs/operat import { createEntityRelationPaginationKey } from '../../../../cloud-foundry/src/entity-relations/entity-relations.types'; import { getIdFromRoute, safeStringToObj } from '../../../../core/src/core/utils.service'; -import { StratosStatus } from '../../../../core/src/shared/shared.types'; import { EntityService } from '../../../../store/src/entity-service'; import { PaginationMonitorFactory } from '../../../../store/src/monitors/pagination-monitor.factory'; import { APIResource } from '../../../../store/src/types/api.types'; +import { StratosStatus } from '../../../../store/src/types/shared.types'; import { IService, IServiceBroker, diff --git a/src/frontend/packages/cloud-foundry/src/features/service-catalog/services.service.ts b/src/frontend/packages/cloud-foundry/src/features/service-catalog/services.service.ts index a550800a57..05d65754d3 100644 --- a/src/frontend/packages/cloud-foundry/src/features/service-catalog/services.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/service-catalog/services.service.ts @@ -132,6 +132,7 @@ export class ServicesService { map(o => (o.length === 0 ? null : o[0])) ); this.isSpaceScoped$ = this.serviceBroker$.pipe( + first(), map(o => o ? o.entity.space_guid : null), switchMap(spaceGuid => { if (!spaceGuid) { @@ -151,7 +152,8 @@ export class ServicesService { })), ); } - }) + }), + first() ); this.serviceInstances$ = this.allServiceInstances$.pipe( map(instances => instances.filter(instance => instance.entity.service_guid === this.serviceGuid)) diff --git a/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.html b/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.html index cf6322f5e7..b47b7c7e12 100644 --- a/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.html @@ -3,7 +3,7 @@

Services

- diff --git a/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.ts b/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.ts index 96d806b0c5..be3e78240d 100644 --- a/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.ts @@ -14,6 +14,7 @@ import { } from '../../../../../cloud-foundry/src/shared/data-services/cf-org-space-service.service'; import { CloudFoundryService } from '../../../../../cloud-foundry/src/shared/data-services/cloud-foundry.service'; import { ListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; +import { CSI_CANCEL_URL } from '../../../shared/components/add-service-instance/csi-mode.service'; import { CfCurrentUserPermissions } from '../../../user-permissions/cf-user-permissions-checkers'; @Component({ @@ -35,6 +36,7 @@ export class ServicesWallComponent implements OnDestroy { canCreateServiceInstance: CfCurrentUserPermissions; initCfOrgSpaceService: Subscription; cfIds$: Observable; + location: { [CSI_CANCEL_URL]: string }; constructor( public cloudFoundryService: CloudFoundryService, @@ -57,6 +59,10 @@ export class ServicesWallComponent implements OnDestroy { this.haveConnectedCf$ = cloudFoundryService.connectedCFEndpoints$.pipe( map(endpoints => !!endpoints && endpoints.length > 0) ); + + this.location = { + [CSI_CANCEL_URL]: `/services` + } } ngOnDestroy(): void { diff --git a/src/frontend/packages/cloud-foundry/src/features/services/services/services-wall.service.ts b/src/frontend/packages/cloud-foundry/src/features/services/services/services-wall.service.ts index c6ebd18e85..eadc97f032 100644 --- a/src/frontend/packages/cloud-foundry/src/features/services/services/services-wall.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/services/services/services-wall.service.ts @@ -3,7 +3,7 @@ import { Observable } from 'rxjs'; import { filter, map, publishReplay, refCount } from 'rxjs/operators'; import { serviceEntityType } from '../../../../../cloud-foundry/src/cf-entity-types'; -import { endpointSchemaKey } from '../../../../../store/src/helpers/entity-factory'; +import { endpointEntityType } from '../../../../../store/src/helpers/stratos-entity-factory'; import { APIResource } from '../../../../../store/src/types/api.types'; import { IService } from '../../../cf-api-svc.types'; import { cfEntityCatalog } from '../../../cf-entity-catalog'; @@ -18,7 +18,7 @@ export class ServicesWallService { } initServicesObservable = () => { - const paginationKey = createEntityRelationPaginationKey(endpointSchemaKey); + const paginationKey = createEntityRelationPaginationKey(endpointEntityType); return cfEntityCatalog.service.store.getPaginationService(null, paginationKey, {}).entities$ } diff --git a/src/frontend/packages/cloud-foundry/src/public_api.ts b/src/frontend/packages/cloud-foundry/src/public_api.ts index eef53f2d79..dbe7a16cbd 100644 --- a/src/frontend/packages/cloud-foundry/src/public_api.ts +++ b/src/frontend/packages/cloud-foundry/src/public_api.ts @@ -5,3 +5,5 @@ // export * from './lib/cloud-foundry.service'; export * from './lib/cloud-foundry.component'; export * from './lib/cloud-foundry.module'; + +export * from './cf-api-svc.types'; \ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/shared/cf-shared.module.ts b/src/frontend/packages/cloud-foundry/src/shared/cf-shared.module.ts index 7da0831fa8..aeef92fd78 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/cf-shared.module.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/cf-shared.module.ts @@ -5,9 +5,6 @@ import { MaterialDesignFrameworkModule } from '@cfstratos/ajsf-material'; import { CoreModule } from '../../../core/src/core/core.module'; import { CardCell, TableCellCustom } from '../../../core/src/shared/components/list/list.types'; import { SharedModule } from '../../../core/src/shared/shared.module'; -import { - ApplicationInstanceChartComponent, -} from '../features/applications/application/application-instance-chart/application-instance-chart.component'; import { AddServiceInstanceBaseStepComponent, } from './components/add-service-instance/add-service-instance-base-step/add-service-instance-base-step.component'; @@ -23,6 +20,9 @@ import { import { SpecifyUserProvidedDetailsComponent, } from './components/add-service-instance/specify-user-provided-details/specify-user-provided-details.component'; +import { + ApplicationInstanceChartComponent, +} from './components/application-instance-chart/application-instance-chart.component'; import { CardAppInstancesComponent } from './components/cards/card-app-instances/card-app-instances.component'; import { CardAppStatusComponent } from './components/cards/card-app-status/card-app-status.component'; import { CardAppUptimeComponent } from './components/cards/card-app-uptime/card-app-uptime.component'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/add-service-instance-base-step/add-service-instance-base-step.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/add-service-instance-base-step/add-service-instance-base-step.component.html index bdb623280b..20a2ba742b 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/add-service-instance-base-step/add-service-instance-base-step.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/add-service-instance-base-step/add-service-instance-base-step.component.html @@ -1,7 +1,7 @@ Choose Service Type - +

Choose service type to {{ bindApp ? 'bind' : 'create' }}

diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/add-service-instance-base-step/add-service-instance-base-step.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/add-service-instance-base-step/add-service-instance-base-step.component.ts index 06f9214b75..2daa17cb4a 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/add-service-instance-base-step/add-service-instance-base-step.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/add-service-instance-base-step/add-service-instance-base-step.component.ts @@ -1,12 +1,14 @@ import { Component } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; import { CFAppState } from '../../../../../../cloud-foundry/src/cf-app-state'; +import { getIdFromRoute } from '../../../../../../core/src/core/utils.service'; import { BASE_REDIRECT_QUERY } from '../../../../../../core/src/shared/components/stepper/stepper.types'; import { TileConfigManager } from '../../../../../../core/src/shared/components/tile/tile-selector.helpers'; import { ITileConfig, ITileData } from '../../../../../../core/src/shared/components/tile/tile-selector.types'; import { RouterNav } from '../../../../../../store/src/actions/router.actions'; +import { CSI_CANCEL_URL } from '../csi-mode.service'; import { SERVICE_INSTANCE_TYPES } from './add-service-instance.types'; interface ICreateServiceTilesData extends ITileData { @@ -21,6 +23,7 @@ interface ICreateServiceTilesData extends ITileData { export class AddServiceInstanceBaseStepComponent { private tileManager = new TileConfigManager(); public serviceType: string; + public cancelUrl = '/services'; public tileSelectorConfig = [ this.tileManager.getNextTileConfig( @@ -44,16 +47,39 @@ export class AddServiceInstanceBaseStepComponent { this.serviceType = tile ? tile.data.type : null; this.pSelectedTile = tile; if (tile) { - const baseUrl = this.bindApp ? this.router.routerState.snapshot.url : '/services/new'; + const baseUrl = this.createServiceTileUrl(); this.store.dispatch(new RouterNav({ path: `${baseUrl}/${this.serviceType}`, query: { - [BASE_REDIRECT_QUERY]: baseUrl + [BASE_REDIRECT_QUERY]: baseUrl, // 'previous' destination + [CSI_CANCEL_URL]: this.cancelUrl // 'cancel' + 'success' destination } })); } } - constructor(private route: ActivatedRoute, private router: Router, public store: Store) { + + private cfId: string; + private appId: string; + + constructor( + private route: ActivatedRoute, + public store: Store + ) { this.bindApp = !!this.route.snapshot.data.bind; + if (this.bindApp) { + this.cfId = getIdFromRoute(this.route, 'endpointId'); + this.appId = getIdFromRoute(this.route, 'id'); + } + this.cancelUrl = this.createCancelUrl(); + } + + private createServiceTileUrl(): string { + return this.bindApp ? `/applications/${this.cfId}/${this.appId}/bind` : '/services/new' } + + private createCancelUrl(): string { + return this.bindApp ? `/applications/${this.cfId}/${this.appId}/services` : '/services' + } + + } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/csi-mode.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/csi-mode.service.ts index 3c4fa72df5..ad65ce4410 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/csi-mode.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/csi-mode.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { filter, map, pairwise } from 'rxjs/operators'; import { SpaceScopedService } from '../../../../../cloud-foundry/src/features/service-catalog/services.service'; @@ -19,8 +19,23 @@ export const enum CreateServiceFormMode { BindServiceInstance = 'bind-service-instance', } +/** + * Where should the user be taken on cancel (and success). If not supplied will fall back on previous location and then deduced from + * params + */ +export const CSI_CANCEL_URL = 'cancel' + +/** + * Used when `CSI_CANCEL_URL` is not supplied + */ export const CANCEL_SPACE_ID_PARAM = 'space-guid'; +/** + * Used when `CSI_CANCEL_URL` is not supplied + */ export const CANCEL_ORG_ID_PARAM = 'org-guid'; +/** + * Used when `CSI_CANCEL_URL` is not supplied + */ export const CANCEL_USER_PROVIDED = 'up'; interface ViewDetail { @@ -44,12 +59,16 @@ export class CsiModeService { private mode: string; public viewDetail: ViewDetail; + /** + * Where should the user be taken on cancel (and success). Taken from url param, previous location or deduced + */ public cancelUrl: string; // This property is only used when launching the Create Service Instance Wizard from the Marketplace spaceScopedDetails: SpaceScopedService = { isSpaceScoped: false }; constructor( private activatedRoute: ActivatedRoute, + router: Router ) { const serviceId = getIdFromRoute(activatedRoute, 'serviceId'); const serviceInstanceId = getIdFromRoute(activatedRoute, 'serviceInstanceId'); @@ -118,6 +137,7 @@ export class CsiModeService { `/cloud-foundry/${cfId}/organizations/${orgGuid}/spaces/${spaceGuid}/${isUserProvided ? 'user-service-instances' : 'service-instances'}`; } + this.updateCancelUrl(this.activatedRoute, router); } getViewDetail = () => this.viewDetail; @@ -148,4 +168,33 @@ export class CsiModeService { ); } + private updateCancelUrl( + activatedRoute: ActivatedRoute, + router: Router + ) { + // cancelUrl determines where we go on cancel AND success + const cancelUrl = activatedRoute.snapshot.queryParamMap.get(CSI_CANCEL_URL); + if (cancelUrl) { + // Override cancelUrl with what's been passed in (probably came from the service selection pre-step) + this.cancelUrl = cancelUrl; + } else { + // There's some holes with the way cancelUrl in ctor is calculated + // - marketplace/service/instances list --> cancel goes to space service instance list + // - marketplace/service create instance --> cancel goes to marketplace/service/instance regardless of starting tab + // - .. others?? + // For simplicity always go back to the previous location + // - good catch all + // - doesn't work that well for marketplace/service create instance --> success (should go to marketplace/service/instance) + // - if user has refreshed on stepper (previous url was login) use the old cancelUrl best-guess value + const currentNavigation = router.getCurrentNavigation(); + if (currentNavigation && + currentNavigation.previousNavigation && + currentNavigation.previousNavigation.finalUrl && + currentNavigation.previousNavigation.finalUrl.toString() !== '/login' + ) { + this.cancelUrl = currentNavigation.previousNavigation.finalUrl.toString(); + } + } + } + } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/select-plan-step/select-plan-step.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/select-plan-step/select-plan-step.component.ts index 961ada73a1..e1b2c4d645 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/select-plan-step/select-plan-step.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/select-plan-step/select-plan-step.component.ts @@ -39,8 +39,8 @@ import { } from '../../../../../../cloud-foundry/src/store/selectors/create-service-instance.selectors'; import { safeUnsubscribe } from '../../../../../../core/src/core/utils.service'; import { StepOnNextResult } from '../../../../../../core/src/shared/components/stepper/step/step.component'; -import { StratosStatus } from '../../../../../../core/src/shared/shared.types'; import { APIResource } from '../../../../../../store/src/types/api.types'; +import { StratosStatus } from '../../../../../../store/src/types/shared.types'; import { IServicePlan } from '../../../../cf-api-svc.types'; import { CreateServiceInstanceHelperServiceFactory } from '../create-service-instance-helper-service-factory.service'; import { CreateServiceInstanceHelper } from '../create-service-instance-helper.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/specify-details-step/specify-details-step.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/specify-details-step/specify-details-step.component.ts index 80ed214a56..ae2ed8a868 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/specify-details-step/specify-details-step.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/specify-details-step/specify-details-step.component.ts @@ -31,7 +31,6 @@ import { } from '../../../../../../cloud-foundry/src/actions/create-service-instance.actions'; import { pathGet, safeStringToObj } from '../../../../../../core/src/core/utils.service'; import { StepOnNextResult } from '../../../../../../core/src/shared/components/stepper/step/step.component'; -import { RouterNav } from '../../../../../../store/src/actions/router.actions'; import { getDefaultRequestState, RequestInfoState } from '../../../../../../store/src/reducers/api-request-reducer/types'; import { APIResource, NormalizedResponse } from '../../../../../../store/src/types/api.types'; import { UpdateServiceInstance } from '../../../../actions/service-instances.actions'; @@ -297,7 +296,7 @@ export class SpecifyDetailsStepComponent implements OnDestroy, AfterContentInit this.longRunningOpService.handleLongRunningCreateService(bindApp); // Return to app page instead of falling through to service page if (bindApp) { - return observableOf(this.routeToServices(state.cfGuid, state.bindAppGuid)); + return observableOf(this.routeToServices()); } } else if (request.error) { // The request has errored, report this back @@ -318,7 +317,7 @@ export class SpecifyDetailsStepComponent implements OnDestroy, AfterContentInit } else { // Refetch env vars for app, since they have been changed by CF cfEntityCatalog.appEnvVar.api.getMultiple(state.bindAppGuid, state.cfGuid); - return this.routeToServices(state.cfGuid, state.bindAppGuid); + return this.routeToServices(); } }) ); @@ -356,13 +355,12 @@ export class SpecifyDetailsStepComponent implements OnDestroy, AfterContentInit ); } - routeToServices = (cfGuid: string = null, appGuid: string = null): StepOnNextResult => { - if (this.modeService.isAppServicesMode()) { - this.store.dispatch(new RouterNav({ path: ['/applications', cfGuid, appGuid, 'services'] })); - } else { - this.store.dispatch(new RouterNav({ path: ['/services'] })); - } - return { success: true }; + routeToServices = (): StepOnNextResult => { + return { + success: true, + // We should always go back to where we came from, aka 'cancel' location. + redirect: true, + }; } private setServiceInstanceGuid = (request: { creating: boolean; error: boolean; response: { result: any[]; }; }) => diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/specify-user-provided-details/specify-user-provided-details.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/specify-user-provided-details/specify-user-provided-details.component.ts index a5d2b02b09..258727a9cc 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/specify-user-provided-details/specify-user-provided-details.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/specify-user-provided-details/specify-user-provided-details.component.ts @@ -27,6 +27,7 @@ import { IUserProvidedServiceInstance } from '../../../../cf-api-svc.types'; import { cfEntityCatalog } from '../../../../cf-entity-catalog'; import { AppNameUniqueChecking } from '../../../directives/app-name-unique.directive/app-name-unique.directive'; import { CloudFoundryUserProvidedServicesService } from '../../../services/cloud-foundry-user-provided-services.service'; +import { AppServiceBindingDataSource } from '../../list/list-types/app-sevice-bindings/app-service-binding-data-source'; import { CreateServiceFormMode, CsiModeService } from './../csi-mode.service'; const { proxyAPIVersion, cfAPIVersion } = environment; @@ -38,7 +39,7 @@ const { proxyAPIVersion, cfAPIVersion } = environment; export class SpecifyUserProvidedDetailsComponent implements OnDestroy { constructor( - route: ActivatedRoute, + private route: ActivatedRoute, private upsService: CloudFoundryUserProvidedServicesService, public modeService: CsiModeService, private store: Store, @@ -297,11 +298,24 @@ export class SpecifyUserProvidedDetailsComponent implements OnDestroy { this.serviceInstanceId, updateData ).pipe( - map(er => ({ - success: !er.error, - redirect: !er.error, - message: `Failed to update service instance: ${er.message}` - })) + map(er => { + if (!er.error) { + // Update the application binding list + const appId = this.appId || this.route.snapshot.queryParamMap.get('appId'); + if (appId) { + this.store.dispatch(AppServiceBindingDataSource.createGetAllServiceBindings(appId, this.cfGuid)); + } + return { + success: true, + redirect: true, + }; + } + return { + success: false, + redirect: false, + message: `Failed to update service instance: ${er.message}` + }; + }) ); } diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-instance-chart/application-instance-chart.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/application-instance-chart/application-instance-chart.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/applications/application/application-instance-chart/application-instance-chart.component.html rename to src/frontend/packages/cloud-foundry/src/shared/components/application-instance-chart/application-instance-chart.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-instance-chart/application-instance-chart.component.scss b/src/frontend/packages/cloud-foundry/src/shared/components/application-instance-chart/application-instance-chart.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/applications/application/application-instance-chart/application-instance-chart.component.scss rename to src/frontend/packages/cloud-foundry/src/shared/components/application-instance-chart/application-instance-chart.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-instance-chart/application-instance-chart.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/application-instance-chart/application-instance-chart.component.spec.ts similarity index 74% rename from src/frontend/packages/cloud-foundry/src/features/applications/application/application-instance-chart/application-instance-chart.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/shared/components/application-instance-chart/application-instance-chart.component.spec.ts index 07fb0f11ed..fc47c9f6b3 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-instance-chart/application-instance-chart.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-instance-chart/application-instance-chart.component.spec.ts @@ -2,10 +2,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { CoreModule } from '../../../../../../core/src/core/core.module'; -import { SharedModule } from '../../../../../../core/src/shared/shared.module'; -import { generateCfStoreModules } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { CloudFoundrySharedModule } from './../../../../shared/cf-shared.module'; +import { CoreModule } from '../../../../../core/src/core/core.module'; +import { SharedModule } from '../../../../../core/src/shared/shared.module'; +import { generateCfStoreModules } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; +import { CloudFoundrySharedModule } from '../../cf-shared.module'; import { ApplicationInstanceChartComponent } from './application-instance-chart.component'; describe('ApplicationInstanceChartComponent', () => { diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-instance-chart/application-instance-chart.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/application-instance-chart/application-instance-chart.component.ts similarity index 66% rename from src/frontend/packages/cloud-foundry/src/features/applications/application/application-instance-chart/application-instance-chart.component.ts rename to src/frontend/packages/cloud-foundry/src/shared/components/application-instance-chart/application-instance-chart.component.ts index 5401a177bc..3a54bec82d 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-instance-chart/application-instance-chart.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-instance-chart/application-instance-chart.component.ts @@ -1,13 +1,12 @@ import { Component, Input, OnInit } from '@angular/core'; -import { MetricsConfig } from '../../../../../../core/src/shared/components/metrics-chart/metrics-chart.component'; -import { MetricsLineChartConfig } from '../../../../../../core/src/shared/components/metrics-chart/metrics-chart.types'; -import { MetricsChartHelpers } from '../../../../../../core/src/shared/components/metrics-chart/metrics.component.helpers'; -import { MetricQueryType } from '../../../../../../core/src/shared/services/metrics-range-selector.types'; -import { MetricQueryConfig } from '../../../../../../store/src/actions/metrics.actions'; -import { IMetricMatrixResult } from '../../../../../../store/src/types/base-metric.types'; -import { IMetricApplication } from '../../../../../../store/src/types/metric.types'; -import { FetchApplicationMetricsAction } from '../../../../actions/cf-metrics.actions'; +import { MetricsConfig } from '../../../../../core/src/shared/components/metrics-chart/metrics-chart.component'; +import { MetricsLineChartConfig } from '../../../../../core/src/shared/components/metrics-chart/metrics-chart.types'; +import { MetricsChartHelpers } from '../../../../../core/src/shared/components/metrics-chart/metrics.component.helpers'; +import { MetricQueryConfig } from '../../../../../store/src/actions/metrics.actions'; +import { IMetricMatrixResult } from '../../../../../store/src/types/base-metric.types'; +import { IMetricApplication, MetricQueryType } from '../../../../../store/src/types/metric.types'; +import { FetchApplicationMetricsAction } from '../../../actions/cf-metrics.actions'; @Component({ selector: 'app-application-instance-chart', diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-app-instances/card-app-instances.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-app-instances/card-app-instances.component.html index bf67158f11..4c5cd00b6f 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-app-instances/card-app-instances.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-app-instances/card-app-instances.component.html @@ -5,7 +5,8 @@
- +
@@ -14,23 +15,27 @@
- - - - - - - - - - + + + + + \ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-app-instances/card-app-instances.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-app-instances/card-app-instances.component.ts index c3524019b6..6604494482 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-app-instances/card-app-instances.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-app-instances/card-app-instances.component.ts @@ -1,13 +1,15 @@ -import { Component, ElementRef, Input, OnDestroy, OnInit, ViewChild, Renderer2 } from '@angular/core'; +import { Component, ElementRef, Input, OnDestroy, OnInit, Renderer2, ViewChild } from '@angular/core'; import { MatSnackBar, MatSnackBarRef, SimpleSnackBar } from '@angular/material/snack-bar'; -import { Observable, Subscription } from 'rxjs'; -import { first, map } from 'rxjs/operators'; +import { combineLatest, Observable, Subscription } from 'rxjs'; +import { first, map, switchMap } from 'rxjs/operators'; import { AppMetadataTypes } from '../../../../../../cloud-foundry/src/actions/app-metadata.actions'; import { ApplicationService } from '../../../../../../cloud-foundry/src/features/applications/application.service'; +import { CurrentUserPermissionsService } from '../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ConfirmationDialogConfig } from '../../../../../../core/src/shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from '../../../../../../core/src/shared/components/confirmation-dialog.service'; -import { StratosStatus } from '../../../../../../core/src/shared/shared.types'; +import { StratosStatus } from '../../../../../../store/src/types/shared.types'; +import { CfCurrentUserPermissions } from '../../../../user-permissions/cf-user-permissions-checkers'; const appInstanceScaleToZeroConfirmation = new ConfirmationDialogConfig('Set Instance count to 0', 'Are you sure you want to set the instance count to 0?', 'Confirm', true); @@ -28,14 +30,26 @@ export class CardAppInstancesComponent implements OnInit, OnDestroy { status$: Observable; + public canEditApp$: Observable; + constructor( public appService: ApplicationService, private renderer: Renderer2, private confirmDialog: ConfirmationDialogService, - private snackBar: MatSnackBar) { + private snackBar: MatSnackBar, + cups: CurrentUserPermissionsService + ) { this.status$ = this.appService.applicationState$.pipe( map(state => state.indicator) ); + this.canEditApp$ = combineLatest( + appService.appOrg$, + appService.appSpace$ + ).pipe( + switchMap(([org, space]) => + cups.can(CfCurrentUserPermissions.APPLICATION_EDIT, appService.cfGuid, org.metadata.guid, space.metadata.guid) + )) + } private currentCount = 0; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-app-status/card-app-status.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-app-status/card-app-status.component.ts index def16fda0d..c7e9633951 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-app-status/card-app-status.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-app-status/card-app-status.component.ts @@ -3,7 +3,7 @@ import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ApplicationService } from '../../../../../../cloud-foundry/src/features/applications/application.service'; -import { StratosStatus } from '../../../../../../core/src/shared/shared.types'; +import { StratosStatus } from '../../../../../../store/src/types/shared.types'; @Component({ selector: 'app-card-app-status', diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-app-usage/card-app-usage.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-app-usage/card-app-usage.component.ts index b8dcea269a..8e47a9ab46 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-app-usage/card-app-usage.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-app-usage/card-app-usage.component.ts @@ -7,7 +7,7 @@ import { } from '../../../../../../cloud-foundry/src/features/applications/application-monitor.service'; import { ApplicationService } from '../../../../../../cloud-foundry/src/features/applications/application.service'; import { pathGet } from '../../../../../../core/src/core/utils.service'; -import { StratosStatus } from '../../../../../../core/src/shared/shared.types'; +import { StratosStatus } from '../../../../../../store/src/types/shared.types'; @Component({ selector: 'app-card-app-usage', diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/compact-app-card/compact-app-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/compact-app-card/compact-app-card.component.ts index a6c13b1af5..d982f91991 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/compact-app-card/compact-app-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/compact-app-card/compact-app-card.component.ts @@ -7,7 +7,7 @@ import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state' import { ApplicationService } from '../../../../../../../cloud-foundry/src/features/applications/application.service'; import { ActiveRouteCfOrgSpace } from '../../../../../../../cloud-foundry/src/features/cloud-foundry/cf-page.types'; import { BREADCRUMB_URL_PARAM } from '../../../../../../../core/src/shared/components/breadcrumbs/breadcrumbs.types'; -import { StratosStatus } from '../../../../../../../core/src/shared/shared.types'; +import { StratosStatus } from '../../../../../../../store/src/types/shared.types'; import { ApplicationStateData, ApplicationStateService } from '../../../../services/application-state.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-space-details/card-cf-space-details.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-space-details/card-cf-space-details.component.ts index 1e20e5ab65..555865d96e 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-space-details/card-cf-space-details.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-space-details/card-cf-space-details.component.ts @@ -8,8 +8,8 @@ import { CloudFoundrySpaceService, } from '../../../../../../cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service'; import { safeUnsubscribe } from '../../../../../../core/src/core/utils.service'; +import { SnackBarService } from '../../../../../../core/src/shared/services/snackbar.service'; import { RouterNav } from '../../../../../../store/src/actions/router.actions'; -import { ShowReturnSnackBar } from '../../../../../../store/src/actions/snackBar.actions'; import { AppState } from '../../../../../../store/src/app-state'; @Component({ @@ -24,7 +24,8 @@ export class CardCfSpaceDetailsComponent implements OnDestroy { constructor( public cfSpaceService: CloudFoundrySpaceService, private store: Store, - private router: Router + private router: Router, + private snackBarService: SnackBarService ) { this.allowSshStatus$ = cfSpaceService.allowSsh$.pipe( map(status => status === 'false' ? 'Disabled' : 'Enabled') @@ -34,7 +35,7 @@ export class CardCfSpaceDetailsComponent implements OnDestroy { goToOrgQuota() { this.quotaLinkSub = this.cfSpaceService.quotaLink$.subscribe(quotaLink => { this.store.dispatch(new RouterNav({ path: quotaLink })); - this.store.dispatch(new ShowReturnSnackBar('You were switched to an organization', this.router.url, 'Return to space')); + this.snackBarService.showReturn('You were switched to an organization', this.router.url, 'Return to space'); }); } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cf-role-checkbox/cf-role-checkbox.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cf-role-checkbox/cf-role-checkbox.component.ts index 8472dd6633..f2337914c2 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cf-role-checkbox/cf-role-checkbox.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cf-role-checkbox/cf-role-checkbox.component.ts @@ -46,6 +46,7 @@ export class CfRoleCheckboxComponent implements OnInit, OnDestroy { @Input() cfGuid: string; + @Input() orgGuid: string; @Input() spaceGuid: string; @Input() orgName: string; @Input() spaceName: string; @@ -60,7 +61,6 @@ export class CfRoleCheckboxComponent implements OnInit, OnDestroy { sub: Subscription; isOrgRole = false; disabled = false; - orgGuid: string; private static hasExistingRole(role: string, roles: CfUserRolesSelected, userGuid: string, orgGuid: string, spaceGuid: string): boolean { if (roles && roles[userGuid] && roles[userGuid][orgGuid]) { @@ -266,7 +266,6 @@ export class CfRoleCheckboxComponent implements OnInit, OnDestroy { combineLatestOp(this.cfRolesService.newRoles$, users$, canEditRole$, selectUsersIsSetByUsername$), filter(([existingRoles, newRoles, users, canEditRole, isSetByUsername]) => !!users.length && !!newRoles.orgGuid) ).subscribe(([existingRoles, newRoles, users, canEditRole, isSetByUsername]) => { - this.orgGuid = newRoles.orgGuid; const { checked, tooltip } = CfRoleCheckboxComponent.getCheckedState( this.role, users, existingRoles, newRoles, this.orgGuid, this.spaceGuid); this.checked = checked; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.spec.ts index dd2d9a08a4..3966e4880a 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.spec.ts @@ -6,15 +6,11 @@ import { testSCFEndpointGuid } from '@stratosui/store/testing'; import { CoreModule } from '../../../../../../../core/src/core/core.module'; import { CF_GUID } from '../../../../../../../core/src/shared/entity.tokens'; import { SharedModule } from '../../../../../../../core/src/shared/shared.module'; -import { generateTestEntityServiceProvider } from '../../../../../../../core/test-framework/entity-service.helper'; import { generateTestApplicationServiceProvider } from '../../../../../../test-framework/application-service-helper'; import { generateCfStoreModules, generateTestCfEndpointServiceProvider, } from '../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { GetApplication } from '../../../../../actions/application.actions'; -import { cfEntityFactory } from '../../../../../cf-entity-factory'; -import { applicationEntityType } from '../../../../../cf-entity-types'; import { ApplicationsModule } from '../../../../../features/applications/applications.module'; import { CfAppInstancesConfigService } from './cf-app-instances-config.service'; @@ -27,11 +23,6 @@ describe('CfAppInstancesConfigService', () => { TestBed.configureTestingModule({ providers: [ CfAppInstancesConfigService, - generateTestEntityServiceProvider( - appGuid, - cfEntityFactory(applicationEntityType), - new GetApplication(appGuid, cfGuid) - ), generateTestApplicationServiceProvider(appGuid, cfGuid), generateTestCfEndpointServiceProvider(), { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.ts index 1559dfec06..2753ce8909 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.ts @@ -1,12 +1,15 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { Store } from '@ngrx/store'; -import { Observable } from 'rxjs'; +import { combineLatest as combineLatestObs, Observable } from 'rxjs'; import { combineLatest, map, switchMap } from 'rxjs/operators'; import { DeleteApplicationInstance } from '../../../../../../../cloud-foundry/src/actions/application.actions'; import { FetchApplicationMetricsAction } from '../../../../../../../cloud-foundry/src/actions/cf-metrics.actions'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { UtilsService } from '../../../../../../../core/src/core/utils.service'; import { ConfirmationDialogConfig } from '../../../../../../../core/src/shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from '../../../../../../../core/src/shared/components/confirmation-dialog.service'; @@ -19,14 +22,14 @@ import { IListConfig, ListViewTypes, } from '../../../../../../../core/src/shared/components/list/list.component.types'; -import { MetricQueryType } from '../../../../../../../core/src/shared/services/metrics-range-selector.types'; import { MetricQueryConfig } from '../../../../../../../store/src/actions/metrics.actions'; import { EntityServiceFactory } from '../../../../../../../store/src/entity-service-factory.service'; import { PaginationMonitorFactory } from '../../../../../../../store/src/monitors/pagination-monitor.factory'; import { IMetricMatrixResult, IMetrics } from '../../../../../../../store/src/types/base-metric.types'; -import { IMetricApplication } from '../../../../../../../store/src/types/metric.types'; +import { IMetricApplication, MetricQueryType } from '../../../../../../../store/src/types/metric.types'; import { ApplicationService } from '../../../../../features/applications/application.service'; import { CfCellHelper } from '../../../../../features/cloud-foundry/cf-cell.helpers'; +import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { ListAppInstance } from './app-instance-types'; import { CfAppInstancesDataSource } from './cf-app-instances-data-source'; import { TableCellCfCellComponent } from './table-cell-cf-cell/table-cell-cf-cell.component'; @@ -158,7 +161,7 @@ export class CfAppInstancesConfigService implements IListConfig }, label: 'Terminate', description: ``, // Description depends on console user permission - + createVisible: () => this.canEditApp$ }; private listActionSsh: IListAction = { @@ -182,7 +185,8 @@ export class CfAppInstancesConfigService implements IListConfig space.entity.allow_ssh; }) ); - })) + })), + createVisible: () => this.canEditApp$ }; private singleActions = [ @@ -190,6 +194,8 @@ export class CfAppInstancesConfigService implements IListConfig this.listActionSsh, ]; + private canEditApp$: Observable; + constructor( private store: Store, private appService: ApplicationService, @@ -197,7 +203,8 @@ export class CfAppInstancesConfigService implements IListConfig private router: Router, private confirmDialog: ConfirmationDialogService, entityServiceFactory: EntityServiceFactory, - paginationMonitorFactory: PaginationMonitorFactory + paginationMonitorFactory: PaginationMonitorFactory, + cups: CurrentUserPermissionsService ) { const cellHelper = new CfCellHelper(store, paginationMonitorFactory); @@ -220,6 +227,15 @@ export class CfAppInstancesConfigService implements IListConfig this.appService.appGuid, this, ); + + this.canEditApp$ = combineLatestObs( + appService.appOrg$, + appService.appSpace$ + ).pipe( + switchMap(([org, space]) => + cups.can(CfCurrentUserPermissions.APPLICATION_EDIT, appService.cfGuid, org.metadata.guid, space.metadata.guid) + ) + ) } getGlobalActions = () => null; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/table-cell-cf-cell/table-cell-cf-cell.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/table-cell-cf-cell/table-cell-cf-cell.component.ts index 64ccc8b4f4..651c1b90ac 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/table-cell-cf-cell/table-cell-cf-cell.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/table-cell-cf-cell/table-cell-cf-cell.component.ts @@ -2,8 +2,8 @@ import { Component, Input, OnDestroy } from '@angular/core'; import { Observable, Subscription } from 'rxjs'; import { filter, map, tap } from 'rxjs/operators'; -import { EntityService } from '../../../../../../../../store/src/entity-service'; import { TableCellCustom } from '../../../../../../../../core/src/shared/components/list/list.types'; +import { EntityService } from '../../../../../../../../store/src/entity-service'; import { IMetricMatrixResult, IMetrics } from '../../../../../../../../store/src/types/base-metric.types'; import { IMetricCell } from '../../../../../../../../store/src/types/metric.types'; import { ListAppInstance } from '../app-instance-types'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-route/cf-app-routes-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-route/cf-app-routes-list-config.service.ts index a631fac6ae..b69eb4ab15 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-route/cf-app-routes-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-route/cf-app-routes-list-config.service.ts @@ -1,7 +1,8 @@ import { DatePipe } from '@angular/common'; import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import { take } from 'rxjs/operators'; +import { combineLatest } from 'rxjs'; +import { switchMap, take } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { @@ -12,6 +13,7 @@ import { IGlobalListAction, IListConfig } from '../../../../../../../core/src/sh import { RouterNav } from '../../../../../../../store/src/actions/router.actions'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { ApplicationService } from '../../../../../features/applications/application.service'; +import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { CfAppRoutesListConfigServiceBase } from './cf-app-routes-list-config-base'; @@ -23,11 +25,12 @@ export class CfAppRoutesListConfigService extends CfAppRoutesListConfigServiceBa appService: ApplicationService, confirmDialog: ConfirmationDialogService, datePipe: DatePipe, - currentUserPermissionsService: CurrentUserPermissionsService, + private currentUserPermissionsService: CurrentUserPermissionsService, ) { super(store, appService, confirmDialog, datePipe, currentUserPermissionsService, null, true); this.setupList(store, appService); + this.allowSelection = false; // Allow the multi action visibility to determine this } private setupList(store: Store, appService: ApplicationService) { @@ -51,7 +54,18 @@ export class CfAppRoutesListConfigService extends CfAppRoutesListConfigServiceBa }, icon: 'add', label: 'Add', - description: 'Add new route' + description: 'Add new route', + visible$: combineLatest( + appService.appOrg$, + appService.appSpace$ + ).pipe( + switchMap(([org, space]) => this.currentUserPermissionsService.can( + CfCurrentUserPermissions.ROUTE_CREATE, + appService.cfGuid, + org.metadata.guid, + space.metadata.guid + )) + ) }; this.getGlobalActions = () => [listActionAddRoute]; } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.ts index 5f15687925..50fb563f34 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.ts @@ -8,12 +8,10 @@ import { CurrentUserPermissionsService, } from '../../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { AppChip } from '../../../../../../../../core/src/shared/components/chips/chips.component'; -import { - MetaCardMenuItem, -} from '../../../../../../../../core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component'; import { CardCell, IListRowCell } from '../../../../../../../../core/src/shared/components/list/list.types'; -import { ComponentEntityMonitorConfig } from '../../../../../../../../core/src/shared/shared.types'; import { APIResource, EntityInfo } from '../../../../../../../../store/src/types/api.types'; +import { MenuItem } from '../../../../../../../../store/src/types/menu-item.types'; +import { ComponentEntityMonitorConfig } from '../../../../../../../../store/src/types/shared.types'; import { IService, IServiceBinding, @@ -34,6 +32,7 @@ import { import { AppEnvVarsState } from '../../../../../../store/types/app-metadata.types'; import { CfCurrentUserPermissions } from '../../../../../../user-permissions/cf-user-permissions-checkers'; import { ServiceActionHelperService } from '../../../../../data-services/service-action-helper.service'; +import { CSI_CANCEL_URL } from '../../../../add-service-instance/csi-mode.service'; import { EnvVarViewComponent } from '../../../../env-var-view/env-var-view.component'; @@ -54,7 +53,7 @@ export class AppServiceBindingCardComponent extends CardCell; customStyle?: string; }[]; - cardMenu: MetaCardMenuItem[]; + cardMenu: MenuItem[]; service$: Observable> | null>; serviceInstance$: Observable>>; tags$: Observable[]>; @@ -241,7 +240,10 @@ export class AppServiceBindingCardComponent extends CardCell this.serviceActionHelperService.startEditServiceBindingStepper( this.row.entity.service_instance_guid, this.appService.cfGuid, - { appId: this.appService.appGuid }, + { + appId: this.appService.appGuid, + [CSI_CANCEL_URL]: `/applications/${this.appService.cfGuid}/${this.appService.appGuid}/services` + }, this.isUserProvidedServiceInstance ) } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-list-config.service.ts index 68288fa9ec..2d39cd38df 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-list-config.service.ts @@ -19,12 +19,12 @@ import { import { ListView } from '../../../../../../../store/src/actions/list.actions'; import { RouterNav } from '../../../../../../../store/src/actions/router.actions'; import { APIResource } from '../../../../../../../store/src/types/api.types'; -import { GetAppServiceBindings } from '../../../../../actions/application-service-routes.actions'; import { IServiceBinding } from '../../../../../cf-api-svc.types'; import { ApplicationService } from '../../../../../features/applications/application.service'; import { isServiceInstance, isUserProvidedServiceInstance } from '../../../../../features/cloud-foundry/cf.helpers'; import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { ServiceActionHelperService } from '../../../../data-services/service-action-helper.service'; +import { CSI_CANCEL_URL } from '../../../add-service-instance/csi-mode.service'; import { BaseCfListConfig } from '../base-cf/base-cf-list-config'; import { TableCellServiceInstanceTagsComponent, @@ -57,17 +57,15 @@ export class AppServiceBindingListConfigService extends BaseCfListConfig> = { action: (item) => { - // FIXME: If the user cancels stepper this leaks #4295 this.serviceActionHelperService.startEditServiceBindingStepper( item.entity.service_instance_guid, this.appService.cfGuid, - { appId: this.appService.appGuid }, + { + appId: this.appService.appGuid, + [CSI_CANCEL_URL]: `/applications/${this.appService.cfGuid}/${this.appService.appGuid}/services` + }, !!isUserProvidedServiceInstance(item.entity.service_instance.entity) - ).subscribe(res => { - if (!res.error) { - this.store.dispatch(new GetAppServiceBindings(this.appService.appGuid, this.appService.cfGuid)); - } - }); + ); }, label: 'Edit', createVisible: () => this.appService.waitForAppEntity$.pipe( @@ -104,7 +102,7 @@ export class AppServiceBindingListConfigService extends BaseCfListConfig 'Service Instances', + headerCell: () => 'Name', cellDefinition: { getValue: (row) => row.entity.service_instance.entity.name }, diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-variables/cf-app-variables-list-config.service.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-variables/cf-app-variables-list-config.service.spec.ts index 4eea9c86fc..b2a3d64486 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-variables/cf-app-variables-list-config.service.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-variables/cf-app-variables-list-config.service.spec.ts @@ -4,12 +4,8 @@ import { RouterTestingModule } from '@angular/router/testing'; import { CoreModule } from '../../../../../../../core/src/core/core.module'; import { SharedModule } from '../../../../../../../core/src/shared/shared.module'; -import { generateTestEntityServiceProvider } from '../../../../../../../core/test-framework/entity-service.helper'; import { generateTestApplicationServiceProvider } from '../../../../../../test-framework/application-service-helper'; import { generateCfStoreModules } from '../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { GetApplication } from '../../../../../actions/application.actions'; -import { cfEntityFactory } from '../../../../../cf-entity-factory'; -import { applicationEntityType } from '../../../../../cf-entity-types'; import { ApplicationsModule } from '../../../../../features/applications/applications.module'; import { CfAppVariablesListConfigService } from './cf-app-variables-list-config.service'; @@ -22,11 +18,6 @@ describe('CfAppVariablesListConfigService', () => { TestBed.configureTestingModule({ providers: [ CfAppVariablesListConfigService, - generateTestEntityServiceProvider( - appGuid, - cfEntityFactory(applicationEntityType), - new GetApplication(appGuid, cfGuid) - ), generateTestApplicationServiceProvider(appGuid, cfGuid) ], imports: [ diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/card/card-app.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/card/card-app.component.ts index 8236ed5d64..39a18e5336 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/card/card-app.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/card/card-app.component.ts @@ -6,14 +6,12 @@ import { map, startWith } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../../cloud-foundry/src/cf-app-state'; import { applicationEntityType } from '../../../../../../../../cloud-foundry/src/cf-entity-types'; import { IAppFavMetadata } from '../../../../../../../../cloud-foundry/src/cf-metadata-types'; -import { getFavoriteFromEntity } from '../../../../../../../../core/src/core/user-favorite-helpers'; -import { - FavoritesConfigMapper, -} from '../../../../../../../../core/src/shared/components/favorites-meta-card/favorite-config-mapper'; import { CardCell } from '../../../../../../../../core/src/shared/components/list/list.types'; -import { ComponentEntityMonitorConfig, StratosStatus } from '../../../../../../../../core/src/shared/shared.types'; +import { FavoritesConfigMapper } from '../../../../../../../../store/src/favorite-config-mapper'; import { APIResource } from '../../../../../../../../store/src/types/api.types'; +import { ComponentEntityMonitorConfig, StratosStatus } from '../../../../../../../../store/src/types/shared.types'; import { UserFavorite } from '../../../../../../../../store/src/types/user-favorites.types'; +import { getFavoriteFromEntity } from '../../../../../../../../store/src/user-favorite-helpers'; import { IApp, ISpace } from '../../../../../../cf-api.types'; import { cfEntityFactory } from '../../../../../../cf-entity-factory'; import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-buildpacks/cf-buildpacks-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-buildpacks/cf-buildpacks-data-source.ts index ae5065e46e..fbc40398d9 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-buildpacks/cf-buildpacks-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-buildpacks/cf-buildpacks-data-source.ts @@ -9,7 +9,7 @@ import { ListDataSource, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; import { IListConfig } from '../../../../../../../core/src/shared/components/list/list.component.types'; -import { endpointSchemaKey } from '../../../../../../../store/src/helpers/entity-factory'; +import { endpointEntityType } from '../../../../../../../store/src/helpers/stratos-entity-factory'; import { getRowMetadata } from '../../../../../../../store/src/public-api'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { cfEntityCatalog } from '../../../../../cf-entity-catalog'; @@ -17,7 +17,7 @@ import { cfEntityFactory } from '../../../../../cf-entity-factory'; export class CfBuildpacksDataSource extends ListDataSource { constructor(store: Store, cfGuid: string, listConfig?: IListConfig) { - const paginationKey = createEntityRelationPaginationKey(endpointSchemaKey, cfGuid); + const paginationKey = createEntityRelationPaginationKey(endpointEntityType, cfGuid); const action = cfEntityCatalog.buildPack.actions.getMultiple(cfGuid, paginationKey) super({ store, diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-apps/cf-cell-apps-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-apps/cf-cell-apps-list-config.service.ts index 6bc5cdfb94..6d4c6d7b02 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-apps/cf-cell-apps-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-apps/cf-cell-apps-list-config.service.ts @@ -5,7 +5,6 @@ import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state' import { ITableColumn } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; import { ListViewTypes } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { ListView } from '../../../../../../../store/src/actions/list.actions'; -import { EntityServiceFactory } from '../../../../../../../store/src/entity-service-factory.service'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { IApp, ISpace } from '../../../../../cf-api.types'; import { ActiveRouteCfCell } from '../../../../../features/cloud-foundry/cf-page.types'; @@ -24,9 +23,9 @@ export class CfCellAppsListConfigService extends BaseCfListConfig { noEntries: 'There are no applications' }; - constructor(store: Store, private activeRouteCfCell: ActiveRouteCfCell, entityServiceFactory: EntityServiceFactory) { + constructor(store: Store, private activeRouteCfCell: ActiveRouteCfCell) { super(); - this.dataSource = new CfCellAppsDataSource(store, activeRouteCfCell.cfGuid, activeRouteCfCell.cellId, this, entityServiceFactory); + this.dataSource = new CfCellAppsDataSource(store, activeRouteCfCell.cfGuid, activeRouteCfCell.cellId, this); } getColumns = (): ITableColumn[] => [ diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-apps/cf-cell-apps-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-apps/cf-cell-apps-source.ts index 6e834f3121..ba9148a902 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-apps/cf-cell-apps-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-apps/cf-cell-apps-source.ts @@ -2,7 +2,6 @@ import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { GetApplication } from '../../../../../../../cloud-foundry/src/actions/application.actions'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { applicationEntityType, @@ -13,14 +12,13 @@ import { ListDataSource, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; import { IListConfig } from '../../../../../../../core/src/shared/components/list/list.component.types'; -import { MetricQueryType } from '../../../../../../../core/src/shared/services/metrics-range-selector.types'; import { MetricQueryConfig } from '../../../../../../../store/src/actions/metrics.actions'; -import { EntityServiceFactory } from '../../../../../../../store/src/entity-service-factory.service'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { IMetrics, IMetricVectorResult } from '../../../../../../../store/src/types/base-metric.types'; -import { IMetricApplication } from '../../../../../../../store/src/types/metric.types'; +import { IMetricApplication, MetricQueryType } from '../../../../../../../store/src/types/metric.types'; import { FetchCFMetricsPaginatedAction } from '../../../../../actions/cf-metrics.actions'; import { IApp } from '../../../../../cf-api.types'; +import { cfEntityCatalog } from '../../../../../cf-entity-catalog'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; import { createEntityRelationKey } from '../../../../../entity-relations/entity-relations.types'; @@ -41,7 +39,6 @@ export class CfCellAppsDataSource cfGuid: string, cellId: string, listConfig: IListConfig, - entityServiceFactory: EntityServiceFactory ) { const action = new FetchCFMetricsPaginatedAction( cellId, @@ -64,7 +61,7 @@ export class CfCellAppsDataSource return response[0].data.result.map(res => ({ metric: res.metric, appGuid: res.metric.application_id, - appEntityService: this.createAppEntityService(res.metric.application_id, cfGuid, entityServiceFactory) + appEntityService: this.createAppEntityService(res.metric.application_id, cfGuid) })); }), listConfig @@ -74,15 +71,18 @@ export class CfCellAppsDataSource private createAppEntityService( appGuid: string, - cfGuid: string, - entityServiceFactory: EntityServiceFactory): Observable> { + cfGuid: string + ): Observable> { if (!this.appEntityServices[appGuid]) { - this.appEntityServices[appGuid] = entityServiceFactory.create>( + this.appEntityServices[appGuid] = cfEntityCatalog.application.store.getEntityService( appGuid, - new GetApplication(appGuid, cfGuid, [ + cfGuid, { + includeRelations: [ createEntityRelationKey(applicationEntityType, spaceEntityType), createEntityRelationKey(spaceEntityType, organizationEntityType) - ]) + ], + populateMissing: true + } ).waitForEntity$.pipe( map(entityInfo => entityInfo.entity) ); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-endpoints/cf-endpoints-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-endpoints/cf-endpoints-data-source.ts index a9e6b2856e..591c35bf58 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-endpoints/cf-endpoints-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-endpoints/cf-endpoints-data-source.ts @@ -9,7 +9,7 @@ import { IListConfig } from '../../../../../../../core/src/shared/components/lis import { EntityMonitorFactory } from '../../../../../../../store/src/monitors/entity-monitor.factory.service'; import { InternalEventMonitorFactory } from '../../../../../../../store/src/monitors/internal-event-monitor.factory'; import { PaginationMonitorFactory } from '../../../../../../../store/src/monitors/pagination-monitor.factory'; -import { GetAllEndpoints } from '../../../../../../../store/src/actions/endpoint.actions'; +import { stratosEntityCatalog } from '../../../../../../../store/src/stratos-entity-catalog'; import { EndpointModel } from '../../../../../../../store/src/types/endpoint.types'; export class CFEndpointsDataSource extends BaseEndpointsDataSource { @@ -22,7 +22,7 @@ export class CFEndpointsDataSource extends BaseEndpointsDataSource { entityMonitorFactory: EntityMonitorFactory, internalEventMonitorFactory: InternalEventMonitorFactory ) { - const action = new GetAllEndpoints(); + const action = stratosEntityCatalog.endpoint.actions.getAll(); const paginationKey = 'cf-endpoints'; // We do this here to ensure we sync up with main endpoint table data. syncPaginationSection(store, action, paginationKey); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component.ts index c8275787bf..f7e4ff459b 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component.ts @@ -8,24 +8,20 @@ import { organizationEntityType } from '../../../../../../../../cloud-foundry/sr import { CurrentUserPermissionsService, } from '../../../../../../../../core/src/core/permissions/current-user-permissions.service'; -import { getFavoriteFromEntity } from '../../../../../../../../core/src/core/user-favorite-helpers'; import { truthyIncludingZeroString } from '../../../../../../../../core/src/core/utils.service'; import { ConfirmationDialogConfig } from '../../../../../../../../core/src/shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from '../../../../../../../../core/src/shared/components/confirmation-dialog.service'; -import { - FavoritesConfigMapper, -} from '../../../../../../../../core/src/shared/components/favorites-meta-card/favorite-config-mapper'; -import { - MetaCardMenuItem, -} from '../../../../../../../../core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component'; import { CardCell } from '../../../../../../../../core/src/shared/components/list/list.types'; -import { ComponentEntityMonitorConfig, StratosStatus } from '../../../../../../../../core/src/shared/shared.types'; import { RouterNav } from '../../../../../../../../store/src/actions/router.actions'; +import { FavoritesConfigMapper } from '../../../../../../../../store/src/favorite-config-mapper'; import { EntityMonitorFactory } from '../../../../../../../../store/src/monitors/entity-monitor.factory.service'; import { PaginationMonitorFactory } from '../../../../../../../../store/src/monitors/pagination-monitor.factory'; import { APIResource } from '../../../../../../../../store/src/types/api.types'; import { EndpointUser } from '../../../../../../../../store/src/types/endpoint.types'; +import { MenuItem } from '../../../../../../../../store/src/types/menu-item.types'; +import { ComponentEntityMonitorConfig, StratosStatus } from '../../../../../../../../store/src/types/shared.types'; import { IFavoriteMetadata, UserFavorite } from '../../../../../../../../store/src/types/user-favorites.types'; +import { getFavoriteFromEntity } from '../../../../../../../../store/src/user-favorite-helpers'; import { IApp, IOrganization } from '../../../../../../cf-api.types'; import { cfEntityFactory } from '../../../../../../cf-entity-factory'; import { getStartedAppInstanceCount } from '../../../../../../cf.helpers'; @@ -49,7 +45,7 @@ import { CF_ENDPOINT_TYPE } from './../../../../../../cf-types'; styleUrls: ['./cf-org-card.component.scss'] }) export class CfOrgCardComponent extends CardCell> implements OnInit, OnDestroy { - cardMenu: MetaCardMenuItem[]; + cardMenu: MenuItem[]; orgGuid: string; normalisedMemoryUsage: number; memoryLimit: string; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-quotas/cf-quotas-data-source.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-quotas/cf-quotas-data-source.service.ts index d5018ccb88..ef96d027fc 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-quotas/cf-quotas-data-source.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-quotas/cf-quotas-data-source.service.ts @@ -11,10 +11,10 @@ import { getDefaultRowState, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; import { IListConfig } from '../../../../../../../core/src/shared/components/list/list.component.types'; -import { endpointSchemaKey } from '../../../../../../../store/src/helpers/entity-factory'; -import { EntityMonitor } from '../../../../../../../store/src/monitors/entity-monitor'; +import { endpointEntityType } from '../../../../../../../store/src/helpers/stratos-entity-factory'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { CFAppState } from '../../../../../cf-app-state'; +import { cfEntityCatalog } from '../../../../../cf-entity-catalog'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; import { quotaDefinitionEntityType } from '../../../../../cf-entity-types'; import { createEntityRelationPaginationKey } from '../../../../../entity-relations/entity-relations.types'; @@ -22,7 +22,7 @@ import { createEntityRelationPaginationKey } from '../../../../../entity-relatio export class CfQuotasDataSourceService extends ListDataSource { constructor(store: Store, cfGuid: string, listConfig?: IListConfig) { - const quotaPaginationKey = createEntityRelationPaginationKey(endpointSchemaKey, cfGuid); + const quotaPaginationKey = createEntityRelationPaginationKey(endpointEntityType, cfGuid); const action = new GetQuotaDefinitions(quotaPaginationKey, cfGuid); super({ @@ -44,8 +44,7 @@ export class CfQuotasDataSourceService extends ListDataSource { if (!this.sourceScheme || !row) { return of(getDefaultRowState()); } - const entityMonitor = new EntityMonitor(this.store, this.getRowUniqueId(row), this.entityKey, this.sourceScheme); - return entityMonitor.entityRequest$.pipe( + return cfEntityCatalog.quotaDefinition.store.getEntityMonitor(this.getRowUniqueId(row)).entityRequest$.pipe( distinctUntilChanged(), map(requestInfo => ({ deleting: requestInfo.deleting.busy, diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-data-source.ts index 42b046e9dd..2f0b04583f 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-data-source.ts @@ -10,14 +10,14 @@ import { ListDataSource, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; import { IListConfig } from '../../../../../../../core/src/shared/components/list/list.component.types'; -import { endpointSchemaKey } from '../../../../../../../store/src/helpers/entity-factory'; +import { endpointEntityType } from '../../../../../../../store/src/helpers/stratos-entity-factory'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { cfEntityCatalog } from '../../../../../cf-entity-catalog'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; export class CfSecurityGroupsDataSource extends ListDataSource { constructor(store: Store, cfGuid: string, listConfig?: IListConfig) { - const paginationKey = createEntityRelationPaginationKey(endpointSchemaKey, cfGuid); + const paginationKey = createEntityRelationPaginationKey(endpointEntityType, cfGuid); const action = cfEntityCatalog.securityGroup.actions.getMultiple(cfGuid, paginationKey, {}); super({ store, diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts index 52b60a12ee..b1ecf9ef80 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts @@ -21,9 +21,10 @@ import { import { ListView } from '../../../../../../../store/src/actions/list.actions'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { IServiceInstance } from '../../../../../cf-api-svc.types'; +import { isUserProvidedServiceInstance } from '../../../../../features/cloud-foundry/cf.helpers'; import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { ServiceActionHelperService } from '../../../../data-services/service-action-helper.service'; -import { CANCEL_ORG_ID_PARAM, CANCEL_SPACE_ID_PARAM } from '../../../add-service-instance/csi-mode.service'; +import { CANCEL_ORG_ID_PARAM, CANCEL_SPACE_ID_PARAM, CSI_CANCEL_URL } from '../../../add-service-instance/csi-mode.service'; import { TableCellAppCfOrgSpaceHeaderComponent, } from '../app/table-cell-app-cforgspace-header/table-cell-app-cforgspace-header.component'; @@ -61,7 +62,7 @@ export class CfServiceInstancesListConfigBase implements IListConfig>[] = [ { columnId: 'name', - headerCell: () => 'Service Instance', + headerCell: () => 'Name', cellDefinition: { getValue: (row) => `${row.entity.name}` }, @@ -149,10 +150,15 @@ export class CfServiceInstancesListConfigBase implements IListConfig = { action: (item: APIResource) => - this.serviceActionHelperService.startEditServiceBindingStepper(item.metadata.guid, item.entity.cfGuid, { - [CANCEL_SPACE_ID_PARAM]: item.entity.space_guid, - [CANCEL_ORG_ID_PARAM]: item.entity.space.entity.organization_guid - }), + this.serviceActionHelperService.startEditServiceBindingStepper( + item.metadata.guid, + item.entity.cfGuid, + { + [CANCEL_SPACE_ID_PARAM]: item.entity.space_guid, + [CANCEL_ORG_ID_PARAM]: item.entity.space.entity.organization_guid, + [CSI_CANCEL_URL]: this.rootLocation + }, + !!isUserProvidedServiceInstance(item.entity)), label: 'Edit', description: 'Edit Service Instance', createVisible: (row$: Observable>) => @@ -176,7 +182,8 @@ export class CfServiceInstancesListConfigBase implements IListConfig, protected datePipe: DatePipe, protected currentUserPermissionsService: CurrentUserPermissionsService, - private serviceActionHelperService: ServiceActionHelperService + private serviceActionHelperService: ServiceActionHelperService, + private rootLocation: string ) { } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-data-source.ts index 334d992dd6..58e54ea397 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-data-source.ts @@ -11,7 +11,7 @@ import { } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; import { IListConfig } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { entityCatalog } from '../../../../../../../store/src/entity-catalog/entity-catalog'; -import { endpointSchemaKey } from '../../../../../../../store/src/helpers/entity-factory'; +import { endpointEntityType } from '../../../../../../../store/src/helpers/stratos-entity-factory'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { PaginationEntityState } from '../../../../../../../store/src/types/pagination.types'; import { cfEntityCatalog } from '../../../../../cf-entity-catalog'; @@ -19,7 +19,7 @@ import { CF_ENDPOINT_TYPE } from '../../../../../cf-types'; export class CfServicesDataSource extends ListDataSource { constructor(store: Store, endpointGuid: string, listConfig?: IListConfig) { - const paginationKey = createEntityRelationPaginationKey(endpointSchemaKey); + const paginationKey = createEntityRelationPaginationKey(endpointEntityType); const getServicesAction = cfEntityCatalog.service.actions.getMultiple(endpointGuid, paginationKey, {}); super({ store, diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-user-service-instances-list-config.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-user-service-instances-list-config.ts index 9f653f851f..4a9ee537ec 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-user-service-instances-list-config.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-user-service-instances-list-config.ts @@ -28,6 +28,7 @@ import { CANCEL_ORG_ID_PARAM, CANCEL_SPACE_ID_PARAM, CANCEL_USER_PROVIDED, + CSI_CANCEL_URL, } from '../../../add-service-instance/csi-mode.service'; import { CfSpacesUserServiceInstancesDataSource, @@ -64,7 +65,7 @@ export class CfUserServiceInstancesListConfigBase implements IListConfig>[] = [ { columnId: 'name', - headerCell: () => 'Service Instance', + headerCell: () => 'Name', cellDefinition: { getValue: (row) => `${row.entity.name}` }, @@ -152,7 +153,8 @@ export class CfUserServiceInstancesListConfigBase implements IListConfig, - cfSpaceService: CloudFoundrySpaceService, + private cfSpaceService: CloudFoundrySpaceService, protected datePipe: DatePipe, protected currentUserPermissionsService: CurrentUserPermissionsService, private serviceActionHelperService: ServiceActionHelperService diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-quotas/cf-space-quotas-data-source.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-quotas/cf-space-quotas-data-source.service.ts index ccfb93879a..fa308e0b71 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-quotas/cf-space-quotas-data-source.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-quotas/cf-space-quotas-data-source.service.ts @@ -10,11 +10,11 @@ import { getDefaultRowState, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; import { IListConfig } from '../../../../../../../core/src/shared/components/list/list.component.types'; -import { endpointSchemaKey } from '../../../../../../../store/src/helpers/entity-factory'; -import { EntityMonitor } from '../../../../../../../store/src/monitors/entity-monitor'; +import { endpointEntityType } from '../../../../../../../store/src/helpers/stratos-entity-factory'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { GetOrganizationSpaceQuotaDefinitions } from '../../../../../actions/quota-definitions.actions'; import { CFAppState } from '../../../../../cf-app-state'; +import { cfEntityCatalog } from '../../../../../cf-entity-catalog'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; import { spaceQuotaEntityType } from '../../../../../cf-entity-types'; import { createEntityRelationPaginationKey } from '../../../../../entity-relations/entity-relations.types'; @@ -22,7 +22,7 @@ import { createEntityRelationPaginationKey } from '../../../../../entity-relatio export class CfOrgSpaceQuotasDataSourceService extends ListDataSource { constructor(store: Store, orgGuid: string, cfGuid: string, listConfig?: IListConfig) { - const quotaPaginationKey = createEntityRelationPaginationKey(endpointSchemaKey, cfGuid); + const quotaPaginationKey = createEntityRelationPaginationKey(endpointEntityType, cfGuid); const action = new GetOrganizationSpaceQuotaDefinitions(quotaPaginationKey, orgGuid, cfGuid); super({ @@ -44,8 +44,8 @@ export class CfOrgSpaceQuotasDataSourceService extends ListDataSource ({ deleting: requestInfo.deleting.busy, diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-service-instances-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-service-instances-list-config.service.ts index e87ed733c5..f747341abe 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-service-instances-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-service-instances-list-config.service.ts @@ -30,7 +30,13 @@ export class CfSpacesServiceInstancesListConfigService extends CfServiceInstance datePipe: DatePipe, currentUserPermissionsService: CurrentUserPermissionsService, serviceActionHelperService: ServiceActionHelperService) { - super(store, datePipe, currentUserPermissionsService, serviceActionHelperService); + super( + store, + datePipe, + currentUserPermissionsService, + serviceActionHelperService, + `/cloud-foundry/${cfSpaceService.cfGuid}/organizations/${cfSpaceService.orgGuid}/spaces/${cfSpaceService.spaceGuid}/service-instances` + ); this.dataSource = new CfSpacesServiceInstancesDataSource(cfSpaceService.cfGuid, cfSpaceService.spaceGuid, this.store, this); this.serviceInstanceColumns.find(column => column.columnId === 'attachedApps').cellConfig = { breadcrumbs: 'space-services' diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces/cf-space-card/cf-space-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces/cf-space-card/cf-space-card.component.ts index d124f0d47e..322d4bc454 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces/cf-space-card/cf-space-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces/cf-space-card/cf-space-card.component.ts @@ -9,24 +9,20 @@ import { ISpaceFavMetadata } from '../../../../../../../../cloud-foundry/src/cf- import { CurrentUserPermissionsService, } from '../../../../../../../../core/src/core/permissions/current-user-permissions.service'; -import { getFavoriteFromEntity } from '../../../../../../../../core/src/core/user-favorite-helpers'; import { truthyIncludingZeroString } from '../../../../../../../../core/src/core/utils.service'; import { ConfirmationDialogConfig } from '../../../../../../../../core/src/shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from '../../../../../../../../core/src/shared/components/confirmation-dialog.service'; -import { - FavoritesConfigMapper, -} from '../../../../../../../../core/src/shared/components/favorites-meta-card/favorite-config-mapper'; -import { - MetaCardMenuItem, -} from '../../../../../../../../core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component'; import { CardCell } from '../../../../../../../../core/src/shared/components/list/list.types'; -import { ComponentEntityMonitorConfig, StratosStatus } from '../../../../../../../../core/src/shared/shared.types'; import { RouterNav } from '../../../../../../../../store/src/actions/router.actions'; +import { FavoritesConfigMapper } from '../../../../../../../../store/src/favorite-config-mapper'; import { EntityMonitorFactory } from '../../../../../../../../store/src/monitors/entity-monitor.factory.service'; import { PaginationMonitorFactory } from '../../../../../../../../store/src/monitors/pagination-monitor.factory'; import { APIResource } from '../../../../../../../../store/src/types/api.types'; import { EndpointUser } from '../../../../../../../../store/src/types/endpoint.types'; +import { MenuItem } from '../../../../../../../../store/src/types/menu-item.types'; +import { ComponentEntityMonitorConfig, StratosStatus } from '../../../../../../../../store/src/types/shared.types'; import { UserFavorite } from '../../../../../../../../store/src/types/user-favorites.types'; +import { getFavoriteFromEntity } from '../../../../../../../../store/src/user-favorite-helpers'; import { IApp, ISpace } from '../../../../../../cf-api.types'; import { cfEntityFactory } from '../../../../../../cf-entity-factory'; import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; @@ -49,7 +45,7 @@ import { CfUserService } from '../../../../../data-services/cf-user.service'; styleUrls: ['./cf-space-card.component.scss'] }) export class CfSpaceCardComponent extends CardCell> implements OnInit, OnDestroy { - cardMenu: MetaCardMenuItem[]; + cardMenu: MenuItem[]; spaceGuid: string; appInstancesCount: number; appInstancesLimit: string; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/table-cell-org-space-role/table-cell-org-space-role.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/table-cell-org-space-role/table-cell-org-space-role.component.html index 76bda159ca..427d921780 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/table-cell-org-space-role/table-cell-org-space-role.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/table-cell-org-space-role/table-cell-org-space-role.component.html @@ -1,4 +1,5 @@ \ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/service-instances/service-instances-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/service-instances/service-instances-data-source.ts index b99fead8d1..7c3cc59981 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/service-instances/service-instances-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/service-instances/service-instances-data-source.ts @@ -31,6 +31,7 @@ export class ServiceInstancesDataSource extends ListDataSource { paginationKey, isLocal: true, transformEntities: [ + { type: 'filter', field: 'entity.name' }, (entities: APIResource[], paginationState: PaginationEntityState) => { return entities.filter(e => e.entity.service_guid === serviceGuid); } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/service-instances/service-instances-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/service-instances/service-instances-list-config.service.ts index 20878f7924..89c39ee4fa 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/service-instances/service-instances-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/service-instances/service-instances-list-config.service.ts @@ -6,6 +6,7 @@ import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state' import { CurrentUserPermissionsService, } from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; +import { ITableText } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; import { ServicesService } from '../../../../../features/service-catalog/services.service'; import { ServiceActionHelperService } from '../../../../data-services/service-action-helper.service'; import { CfServiceInstancesListConfigBase } from '../cf-services/cf-service-instances-list-config.base'; @@ -20,13 +21,26 @@ import { ServiceInstancesDataSource } from './service-instances-data-source'; @Injectable() export class ServiceInstancesListConfigService extends CfServiceInstancesListConfigBase { + enableTextFilter = true; + text: ITableText = { + title: null, + filter: 'Search by name', + noEntries: 'There are no service instances', + }; + constructor( store: Store, servicesService: ServicesService, datePipe: DatePipe, currentUserPermissionsService: CurrentUserPermissionsService, serviceActionHelperService: ServiceActionHelperService) { - super(store, datePipe, currentUserPermissionsService, serviceActionHelperService); + super( + store, + datePipe, + currentUserPermissionsService, + serviceActionHelperService, + `/marketplace/${servicesService.cfGuid}/${servicesService.serviceGuid}/instances` + ); // Remove 'Service' column this.serviceInstanceColumns.splice(1, 1); this.dataSource = new ServiceInstancesDataSource(servicesService.cfGuid, servicesService.serviceGuid, store, this); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.ts index 649162074c..640c95bcf2 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.ts @@ -8,12 +8,10 @@ import { CurrentUserPermissionsService, } from '../../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { AppChip } from '../../../../../../../../core/src/shared/components/chips/chips.component'; -import { - MetaCardMenuItem, -} from '../../../../../../../../core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component'; import { CardCell } from '../../../../../../../../core/src/shared/components/list/list.types'; -import { ComponentEntityMonitorConfig } from '../../../../../../../../core/src/shared/shared.types'; import { APIResource } from '../../../../../../../../store/src/types/api.types'; +import { MenuItem } from '../../../../../../../../store/src/types/menu-item.types'; +import { ComponentEntityMonitorConfig } from '../../../../../../../../store/src/types/shared.types'; import { IServiceInstance } from '../../../../../../cf-api-svc.types'; import { cfEntityFactory } from '../../../../../../cf-entity-factory'; import { @@ -25,6 +23,7 @@ import { import { CfCurrentUserPermissions } from '../../../../../../user-permissions/cf-user-permissions-checkers'; import { ServiceActionHelperService } from '../../../../../data-services/service-action-helper.service'; import { CfOrgSpaceLabelService } from '../../../../../services/cf-org-space-label.service'; +import { CSI_CANCEL_URL } from '../../../../add-service-instance/csi-mode.service'; @Component({ selector: 'app-service-instance-card', @@ -103,7 +102,7 @@ export class ServiceInstanceCardComponent extends CardCell; cfGuid: string; - cardMenu: MetaCardMenuItem[]; + cardMenu: MenuItem[]; serviceInstanceTags: AppChip[]; hasMultipleBindings = new BehaviorSubject(true); @@ -130,7 +129,9 @@ export class ServiceInstanceCardComponent extends CardCell this.serviceActionHelperService.startEditServiceBindingStepper( this.serviceInstanceEntity.metadata.guid, this.serviceInstanceEntity.entity.cfGuid, - null + { + [CSI_CANCEL_URL]: '/services' + } ) getServiceName = () => { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instances-wall-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instances-wall-list-config.service.ts index f3a4c57132..0f597ef543 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instances-wall-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instances-wall-list-config.service.ts @@ -70,7 +70,13 @@ export class ServiceInstancesWallListConfigService extends CfServiceInstancesLis currentUserPermissionsService: CurrentUserPermissionsService, serviceActionHelperService: ServiceActionHelperService ) { - super(store, datePipe, currentUserPermissionsService, serviceActionHelperService); + super( + store, + datePipe, + currentUserPermissionsService, + serviceActionHelperService, + `/services` + ); const multiFilterConfigs = [ createCfOrgSpaceFilterConfig('cf', 'Cloud Foundry', this.cfOrgSpaceService.cf), createCfOrgSpaceFilterConfig('org', 'Organization', this.cfOrgSpaceService.org), diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/user-provided-service-instance-card/user-provided-service-instance-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/user-provided-service-instance-card/user-provided-service-instance-card.component.ts index 0b2fdcf274..0103bc36be 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/user-provided-service-instance-card/user-provided-service-instance-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/user-provided-service-instance-card/user-provided-service-instance-card.component.ts @@ -8,17 +8,16 @@ import { CurrentUserPermissionsService, } from '../../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { AppChip } from '../../../../../../../../core/src/shared/components/chips/chips.component'; -import { - MetaCardMenuItem, -} from '../../../../../../../../core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component'; import { CardCell } from '../../../../../../../../core/src/shared/components/list/list.types'; -import { ComponentEntityMonitorConfig } from '../../../../../../../../core/src/shared/shared.types'; import { APIResource } from '../../../../../../../../store/src/types/api.types'; +import { MenuItem } from '../../../../../../../../store/src/types/menu-item.types'; +import { ComponentEntityMonitorConfig } from '../../../../../../../../store/src/types/shared.types'; import { IUserProvidedServiceInstance } from '../../../../../../cf-api-svc.types'; import { cfEntityFactory } from '../../../../../../cf-entity-factory'; import { CfCurrentUserPermissions } from '../../../../../../user-permissions/cf-user-permissions-checkers'; import { ServiceActionHelperService } from '../../../../../data-services/service-action-helper.service'; import { CfOrgSpaceLabelService } from '../../../../../services/cf-org-space-label.service'; +import { CSI_CANCEL_URL } from '../../../../add-service-instance/csi-mode.service'; @Component({ @@ -29,7 +28,7 @@ import { CfOrgSpaceLabelService } from '../../../../../services/cf-org-space-lab export class UserProvidedServiceInstanceCardComponent extends CardCell> { serviceInstanceEntity: APIResource; cfGuid: string; - cardMenu: MetaCardMenuItem[]; + cardMenu: MenuItem[]; serviceInstanceTags: AppChip[]; hasMultipleBindings = new BehaviorSubject(true); @@ -120,7 +119,9 @@ export class UserProvidedServiceInstanceCardComponent extends CardCell this.serviceActionHelperService.startEditServiceBindingStepper( this.serviceInstanceEntity.metadata.guid, this.serviceInstanceEntity.entity.cfGuid, - null, + { + [CSI_CANCEL_URL]: '/services' + }, true ) diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/service-plan-public/service-plan-public.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/service-plan-public/service-plan-public.component.spec.ts index 0ecf090c2a..d7788eb41e 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/service-plan-public/service-plan-public.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/service-plan-public/service-plan-public.component.spec.ts @@ -1,9 +1,9 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { of } from 'rxjs'; -import { StratosStatus } from '../../../../../core/src/shared/shared.types'; import { EntityService } from '../../../../../store/src/entity-service'; import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; +import { StratosStatus } from '../../../../../store/src/types/shared.types'; import { generateCfBaseTestModulesNoShared } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; import * as servicesHelpers from '../../../features/service-catalog/services-helper'; import { ServicesService } from '../../../features/service-catalog/services.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/service-plan-public/service-plan-public.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/service-plan-public/service-plan-public.component.ts index e4e4ba6ef9..8d47ae46c6 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/service-plan-public/service-plan-public.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/service-plan-public/service-plan-public.component.ts @@ -6,8 +6,8 @@ import { getServicePlanAccessibilityCardStatus, } from '../../../../../cloud-foundry/src/features/service-catalog/services-helper'; import { ServicesService } from '../../../../../cloud-foundry/src/features/service-catalog/services.service'; -import { StratosStatus } from '../../../../../core/src/shared/shared.types'; import { APIResource } from '../../../../../store/src/types/api.types'; +import { StratosStatus } from '../../../../../store/src/types/shared.types'; import { IServiceBroker, IServicePlan } from '../../../cf-api-svc.types'; import { cfEntityCatalog } from '../../../cf-entity-catalog'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-user.service.ts b/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-user.service.ts index 30a0022d4b..e797eab3c8 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-user.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-user.service.ts @@ -7,10 +7,8 @@ import { CFAppState } from '../../../../cloud-foundry/src/cf-app-state'; import { cfUserEntityType, organizationEntityType, spaceEntityType } from '../../../../cloud-foundry/src/cf-entity-types'; import { createEntityRelationPaginationKey } from '../../../../cloud-foundry/src/entity-relations/entity-relations.types'; import { getCurrentUserCFGlobalStates } from '../../../../cloud-foundry/src/store/selectors/cf-current-user-role.selectors'; -import { - LocalPaginationHelpers, -} from '../../../../core/src/shared/components/list/data-sources-controllers/local-list.helpers'; import { entityCatalog } from '../../../../store/src/entity-catalog/entity-catalog'; +import { LocalPaginationHelpers } from '../../../../store/src/helpers/local-list.helpers'; import { PaginationMonitorFactory } from '../../../../store/src/monitors/pagination-monitor.factory'; import { getDefaultPaginationEntityState, diff --git a/src/frontend/packages/cloud-foundry/src/shared/data-services/cloud-foundry.service.ts b/src/frontend/packages/cloud-foundry/src/shared/data-services/cloud-foundry.service.ts index fe0acd6e1b..2482601268 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/data-services/cloud-foundry.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/data-services/cloud-foundry.service.ts @@ -1,13 +1,11 @@ import { Injectable } from '@angular/core'; -import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { CFAppState } from '../../../../cloud-foundry/src/cf-app-state'; -import { endpointEntitySchema } from '../../../../core/src/base-entity-schemas'; import { PaginationMonitor } from '../../../../store/src/monitors/pagination-monitor'; +import { stratosEntityCatalog } from '../../../../store/src/stratos-entity-catalog'; import { APIResource, EntityInfo } from '../../../../store/src/types/api.types'; -import { endpointListKey, EndpointModel } from '../../../../store/src/types/endpoint.types'; +import { EndpointModel } from '../../../../store/src/types/endpoint.types'; @Injectable() export class CloudFoundryService { @@ -18,11 +16,9 @@ export class CloudFoundryService { cfEndpointsMonitor: PaginationMonitor; waitForAppEntity$: Observable>; - constructor( - store: Store - ) { + constructor() { - this.cfEndpointsMonitor = new PaginationMonitor(store, endpointListKey, endpointEntitySchema, true); + this.cfEndpointsMonitor = stratosEntityCatalog.endpoint.store.getPaginationMonitor(); this.cFEndpoints$ = this.cfEndpointsMonitor.currentPage$.pipe( map(endpoints => endpoints.filter(e => e.cnsi_type === 'cf')) diff --git a/src/frontend/packages/cloud-foundry/src/shared/data-services/long-running-cf-op.service.ts b/src/frontend/packages/cloud-foundry/src/shared/data-services/long-running-cf-op.service.ts index 443247c3a3..acec511ca3 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/data-services/long-running-cf-op.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/data-services/long-running-cf-op.service.ts @@ -2,14 +2,17 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { LongRunningOperationsService } from '../../../../core/src/shared/services/long-running-op.service'; -import { ShowSnackBar } from '../../../../store/src/actions/snackBar.actions'; +import { SnackBarService } from '../../../../core/src/shared/services/snackbar.service'; import { AppState } from '../../../../store/src/app-state'; -import { GetServiceInstance } from '../../actions/service-instances.actions'; +import { cfEntityCatalog } from '../../cf-entity-catalog'; @Injectable() export class LongRunningCfOperationsService extends LongRunningOperationsService { - constructor(store: Store) { + constructor( + store: Store, + private snackBarService: SnackBarService + ) { super(store); } @@ -17,23 +20,23 @@ export class LongRunningCfOperationsService extends LongRunningOperationsService const message = `The operation to create the service instance is taking a long time and will continue in the background. Please refresh the service instance list to check it's status ${bindApp ? ` and then bind the application via the Application page.` : '.'}`; - this.store.dispatch(new ShowSnackBar(message, 'Dismiss')); + this.snackBarService.show(message, 'Dismiss'); } handleLongRunningUpdateService(serviceInstanceGuid: string, cfGuid: string) { const message = `The operation to update the service instance is taking a long time and will continue in the background. Please refresh the service instance list to check it's status`; - this.store.dispatch(new ShowSnackBar(message, 'Dismiss')); // Also attempt to fetch the service instance, this will update the `last operation` value to `update` and `in progress` - this.store.dispatch(new GetServiceInstance(serviceInstanceGuid, cfGuid)); + this.snackBarService.show(message, 'Dismiss'); + cfEntityCatalog.serviceInstance.api.get(serviceInstanceGuid, cfGuid); } handleLongRunningDeleteService(serviceInstanceGuid: string, cfGuid: string) { const message = `The operation to delete the service instance is taking a long time and will continue in the background. Please refresh the service instance list to check it's status`; - this.store.dispatch(new ShowSnackBar(message, 'Dismiss')); + this.snackBarService.show(message, 'Dismiss'); // Also attempt to fetch the service instance, this will update the `last operation` value to `delete` and `in progress` - this.store.dispatch(new GetServiceInstance(serviceInstanceGuid, cfGuid)); + cfEntityCatalog.serviceInstance.api.get(serviceInstanceGuid, cfGuid); } } diff --git a/src/frontend/packages/cloud-foundry/src/shared/services/application-state.service.ts b/src/frontend/packages/cloud-foundry/src/shared/services/application-state.service.ts index 751994c0f0..af5283b27f 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/services/application-state.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/services/application-state.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; -import { StratosStatus, StratosStatusMetadata } from '../../../../core/src/shared/shared.types'; +import { StratosStatus, StratosStatusMetadata } from '../../../../store/src/types/shared.types'; import { AppStat } from '../../store/types/app-metadata.types'; export interface ApplicationStateData extends StratosStatusMetadata { diff --git a/src/frontend/packages/cloud-foundry/src/shared/services/cf-org-space-label.service.ts b/src/frontend/packages/cloud-foundry/src/shared/services/cf-org-space-label.service.ts index 585f41ed42..36c9cb2e38 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/services/cf-org-space-label.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/services/cf-org-space-label.service.ts @@ -2,9 +2,8 @@ import { Store } from '@ngrx/store'; import { combineLatest, Observable } from 'rxjs'; import { filter, first, map } from 'rxjs/operators'; -import { STRATOS_ENDPOINT_TYPE } from '../../../../core/src/base-entity-schemas'; import { entityCatalog } from '../../../../store/src/entity-catalog/entity-catalog'; -import { endpointSchemaKey } from '../../../../store/src/helpers/entity-factory'; +import { endpointEntityType, STRATOS_ENDPOINT_TYPE } from '../../../../store/src/helpers/stratos-entity-factory'; import { selectEntity } from '../../../../store/src/selectors/api.selectors'; import { APIResource } from '../../../../store/src/types/api.types'; import { EndpointModel } from '../../../../store/src/types/endpoint.types'; @@ -33,7 +32,7 @@ export class CfOrgSpaceLabelService { private spaceGuid?: string) { this.multipleConnectedEndpoints$ = haveMultiConnectedCfs(this.store); // FIXME: hide STRATOS_ENDPOINT_TYPE from extensions - STRAT-154 - const endpointEntityKey = entityCatalog.getEntityKey(STRATOS_ENDPOINT_TYPE, endpointSchemaKey); + const endpointEntityKey = entityCatalog.getEntityKey(STRATOS_ENDPOINT_TYPE, endpointEntityType); this.cf$ = this.store.select(selectEntity(endpointEntityKey, this.cfGuid)); diff --git a/src/frontend/packages/cloud-foundry/src/shared/services/current-user-permissions.service.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/services/current-user-permissions.service.spec.ts index 99dcb96deb..ddf7d54350 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/services/current-user-permissions.service.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/services/current-user-permissions.service.spec.ts @@ -12,8 +12,6 @@ import { CfPermissionTypes, CfScopeStrings, } from '../../../../cloud-foundry/src/user-permissions/cf-user-permissions-checkers'; -import { endpointEntitySchema } from '../../../../core/src/base-entity-schemas'; -import { generateStratosEntities } from '../../../../core/src/base-entity-types'; import { PermissionConfig } from '../../../../core/src/core/permissions/current-user-permissions.config'; import { CurrentUserPermissionsService } from '../../../../core/src/core/permissions/current-user-permissions.service'; import { StratosScopeStrings } from '../../../../core/src/core/permissions/stratos-user-permissions.checker'; @@ -21,6 +19,8 @@ import { AppTestModule } from '../../../../core/test-framework/core-test.helper' import { AppState } from '../../../../store/src/app-state'; import { EntityCatalogTestModule, TEST_CATALOGUE_ENTITIES } from '../../../../store/src/entity-catalog-test.module'; import { EntityCatalogEntityConfig } from '../../../../store/src/entity-catalog/entity-catalog.types'; +import { endpointEntityType, stratosEntityFactory } from '../../../../store/src/helpers/stratos-entity-factory'; +import { generateStratosEntities } from '../../../../store/src/stratos-entity-generator'; import { APIResource } from '../../../../store/src/types/api.types'; import { EndpointModel } from '../../../../store/src/types/endpoint.types'; import { BaseEntityValues } from '../../../../store/src/types/entity.types'; @@ -588,7 +588,7 @@ describe('CurrentUserPermissionsService with CF checker', () => { // Create request and requestData sections const entityMap = new Map>([ [ - endpointEntitySchema, + stratosEntityFactory(endpointEntityType), endpoints.map(endpoint => ({ guid: endpoint.guid, data: endpoint diff --git a/src/frontend/packages/cloud-foundry/src/store/reducers/cf-users.reducer.ts b/src/frontend/packages/cloud-foundry/src/store/reducers/cf-users.reducer.ts index 71079d216e..2395df2ff6 100644 --- a/src/frontend/packages/cloud-foundry/src/store/reducers/cf-users.reducer.ts +++ b/src/frontend/packages/cloud-foundry/src/store/reducers/cf-users.reducer.ts @@ -12,6 +12,7 @@ import { } from '../../actions/users.actions'; import { IOrganization, ISpace } from '../../cf-api.types'; import { cfUserEntityType } from '../../cf-entity-types'; +import { CF_ENDPOINT_TYPE } from '../../cf-types'; import { CfUser, CfUserMissingOrgRoles, @@ -61,7 +62,7 @@ export function cfUserReducer(state: IRequestEntityTypeState } export function endpointDisconnectUserReducer(state: IRequestEntityTypeState>, action: DisconnectEndpoint) { - if (action.endpointType === 'cf') { + if (action.endpointType === CF_ENDPOINT_TYPE) { switch (action.type) { case DISCONNECT_ENDPOINTS_SUCCESS: const cfGuid = action.guid; diff --git a/src/frontend/packages/cloud-foundry/src/store/selectors/cf-current-user-role.selectors.ts b/src/frontend/packages/cloud-foundry/src/store/selectors/cf-current-user-role.selectors.ts index c3df05ca37..ee2fd00471 100644 --- a/src/frontend/packages/cloud-foundry/src/store/selectors/cf-current-user-role.selectors.ts +++ b/src/frontend/packages/cloud-foundry/src/store/selectors/cf-current-user-role.selectors.ts @@ -1,7 +1,7 @@ import { compose } from '@ngrx/store'; -import { PermissionValues } from '../../../../core/src/core/permissions/current-user-permissions.config'; import { + PermissionValues, selectCurrentUserGlobalHasScopes, selectCurrentUserRolesState, } from '../../../../store/src/selectors/current-user-role.selectors'; diff --git a/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-permissions-checkers.ts b/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-permissions-checkers.ts index b38816caf9..1aead1cd0f 100644 --- a/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-permissions-checkers.ts +++ b/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-permissions-checkers.ts @@ -7,7 +7,6 @@ import { PermissionConfig, PermissionConfigLink, PermissionTypes, - PermissionValues, } from '../../../core/src/core/permissions/current-user-permissions.config'; import { CurrentUserPermissionsService, @@ -21,6 +20,7 @@ import { IPermissionCheckCombiner, } from '../../../core/src/core/permissions/current-user-permissions.types'; import { GeneralEntityAppState } from '../../../store/src/app-state'; +import { PermissionValues } from '../../../store/src/selectors/current-user-role.selectors'; import { connectedEndpointsSelector } from '../../../store/src/selectors/endpoint.selectors'; import { CFFeatureFlagTypes, IFeatureFlag } from '../cf-api.types'; import { cfEntityCatalog } from '../cf-entity-catalog'; @@ -396,7 +396,7 @@ export class CfUserPermissionsChecker extends BaseCurrentUserPermissionsChecker } private getAllEndpointGuids() { - return this.store.select(connectedEndpointsSelector).pipe( + return this.store.select(connectedEndpointsSelector()).pipe( map(endpoints => Object.values(endpoints).filter(e => e.cnsi_type === CF_ENDPOINT_TYPE).map(endpoint => endpoint.guid)) ); } diff --git a/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-roles-fetch.ts b/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-roles-fetch.ts index ec6687ced1..06340d5496 100644 --- a/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-roles-fetch.ts +++ b/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-roles-fetch.ts @@ -3,7 +3,6 @@ import { Action, Store } from '@ngrx/store'; import { combineLatest, Observable, of } from 'rxjs'; import { catchError, first, map, pairwise, share, skipWhile, switchMap, tap } from 'rxjs/operators'; -import { LoggerService } from '../../../core/src/core/logger.service'; import { AppState } from '../../../store/src/app-state'; import { entityCatalog } from '../../../store/src/entity-catalog/entity-catalog'; import { @@ -50,7 +49,6 @@ const createEndpointArray = (store: Store, endpoints: string[] | Entit export const cfUserRolesFetch: EntityUserRolesFetch = ( endpoints: string[] | EntityUserRolesEndpoint[], store: Store, - logService: LoggerService, httpClient: HttpClient ) => { return createEndpointArray(store, endpoints).pipe( @@ -61,7 +59,7 @@ export const cfUserRolesFetch: EntityUserRolesFetch = ( cfEndpoints.forEach(endpoint => store.dispatch(new GetCfUserRelations(endpoint.guid, GET_CURRENT_CF_USER_RELATIONS_SUCCESS))) } else { // If some endpoints are not connected as admin, go out and fetch the current user's specific roles - const flagsAndRoleRequests = dispatchRoleRequests(cfEndpoints, store, logService, httpClient); + const flagsAndRoleRequests = dispatchRoleRequests(cfEndpoints, store, httpClient); const allRequestsCompleted = handleCfRequests(flagsAndRoleRequests); return combineLatest(allRequestsCompleted).pipe( map(succeeds => succeeds.every(succeeded => !!succeeded)), @@ -84,7 +82,6 @@ interface IEndpointConnectionInfo { function dispatchRoleRequests( endpoints: EntityUserRolesEndpoint[], store: Store, - logService: LoggerService, httpClient: HttpClient ): CfsRequestState { const requests: CfsRequestState = {}; @@ -117,7 +114,7 @@ function dispatchRoleRequests( ); }), catchError(err => { - logService.warn('Failed to fetch current user permissions for a cf: ', err); + console.warn('Failed to fetch current user permissions for a cf: ', err); store.dispatch(new GetCfUserRelations(endpoint.guid, GET_CURRENT_CF_USER_RELATIONS_FAILED)); return of(err); }) diff --git a/src/frontend/packages/cloud-foundry/test-framework/application-service-helper.ts b/src/frontend/packages/cloud-foundry/test-framework/application-service-helper.ts index 6c07e9ba8f..18ed991825 100644 --- a/src/frontend/packages/cloud-foundry/test-framework/application-service-helper.ts +++ b/src/frontend/packages/cloud-foundry/test-framework/application-service-helper.ts @@ -1,7 +1,8 @@ import { Store } from '@ngrx/store'; -import { Observable, of as observableOf } from 'rxjs'; +import { Observable, of as observableOf, of } from 'rxjs'; import { map } from 'rxjs/internal/operators/map'; +import { EntityService } from '../../store/src/entity-service'; import { RequestInfoState } from '../../store/src/reducers/api-request-reducer/types'; import { APIResource, EntityInfo } from '../../store/src/types/api.types'; import { IApp, IAppSummary, IDomain, ISpace } from '../src/cf-api.types'; @@ -81,6 +82,10 @@ export class ApplicationServiceMock { appSpace$: Observable> = observableOf(createEntity({} as ISpace)); applicationRunning$: Observable = observableOf(false); orgDomains$: Observable[]> = observableOf([]); + entityService: EntityService>> = { + waitForEntity$: of({}), + updatingSection$: of({}) + } as EntityService>> } export function generateTestApplicationServiceProvider(appGuid: string, cfGuid: string) { diff --git a/src/frontend/packages/cloud-foundry/test-framework/cf-test-helper.ts b/src/frontend/packages/cloud-foundry/test-framework/cf-test-helper.ts index 0ef667db7e..c22514e227 100644 --- a/src/frontend/packages/cloud-foundry/test-framework/cf-test-helper.ts +++ b/src/frontend/packages/cloud-foundry/test-framework/cf-test-helper.ts @@ -1,7 +1,7 @@ import { BaseTestModules } from '../../core/test-framework/core-test.helper'; import { EntityCatalogTestModule, TEST_CATALOGUE_ENTITIES } from '../../store/src/entity-catalog-test.module'; +import { generateStratosEntities } from '../../store/src/stratos-entity-generator'; import { generateCFEntities } from '../src/cf-entity-generator'; -import { generateStratosEntities } from '../../core/src/base-entity-types'; export const CFBaseTestModules = [ ...BaseTestModules, diff --git a/src/frontend/packages/cloud-foundry/test-framework/cloud-foundry-endpoint-service.helper.ts b/src/frontend/packages/cloud-foundry/test-framework/cloud-foundry-endpoint-service.helper.ts index 2174658903..3fc7ed40e9 100644 --- a/src/frontend/packages/cloud-foundry/test-framework/cloud-foundry-endpoint-service.helper.ts +++ b/src/frontend/packages/cloud-foundry/test-framework/cloud-foundry-endpoint-service.helper.ts @@ -114,7 +114,7 @@ export function generateTestCfServiceProvider() { useFactory: ( store: Store, ) => { - const appService = new CloudFoundryService(store); + const appService = new CloudFoundryService(); return appService; }, deps: [Store] diff --git a/src/frontend/packages/core/endpoints-health-checks.ts b/src/frontend/packages/core/endpoints-health-checks.ts index 2c7624fd76..11e46ae3fc 100644 --- a/src/frontend/packages/core/endpoints-health-checks.ts +++ b/src/frontend/packages/core/endpoints-health-checks.ts @@ -1,20 +1,9 @@ import { Injectable } from '@angular/core'; import { entityCatalog } from '../store/src/entity-catalog/entity-catalog'; +import { EndpointHealthCheck } from '../store/src/entity-catalog/entity-catalog.types'; import { EndpointModel } from '../store/src/types/endpoint.types'; - -export class EndpointHealthCheck { - /** - * @param check To show an error, the check should either call a WrapperRequestActionFailed - * or kick off a chain that eventually calls a WrapperRequestActionFailed - */ - constructor( - public endpointType: string, - public check: (endpoint: EndpointModel) => void - ) { } -} - @Injectable({ providedIn: 'root' }) diff --git a/src/frontend/packages/core/misc/custom/custom-src-routing.module.ts_ b/src/frontend/packages/core/misc/custom/custom-src-routing.module.ts_ deleted file mode 100644 index a1f9fa4457..0000000000 --- a/src/frontend/packages/core/misc/custom/custom-src-routing.module.ts_ +++ /dev/null @@ -1,21 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CustomModule } from './custom/custom.module'; -import { CustomRoutingModule } from './custom/custom-routing.module'; - -// Default import customization module - DO NOT EDIT - -// This file is in the .gitignore - changes will not be flagged - -@NgModule({ - imports: [ - CustomModule, - ] -}) -export class CustomImportModule { } - -@NgModule({ - imports: [ - CustomRoutingModule, - ] -}) -export class CustomRoutingImportModule { } diff --git a/src/frontend/packages/core/misc/custom/custom-src.module.ts_ b/src/frontend/packages/core/misc/custom/custom-src.module.ts_ deleted file mode 100644 index 3e536cc582..0000000000 --- a/src/frontend/packages/core/misc/custom/custom-src.module.ts_ +++ /dev/null @@ -1,16 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CustomModule } from './custom/custom.module'; - -// Default import customization module - DO NOT EDIT - -// This file is in the .gitignore - changes will not be flagged - -@NgModule({ - imports: [ - CustomModule, - ] -}) -export class CustomImportModule { } - -@NgModule() -export class CustomRoutingImportModule { } diff --git a/src/frontend/packages/core/misc/custom/custom.scss b/src/frontend/packages/core/misc/custom/custom.scss deleted file mode 100644 index f6d83b743c..0000000000 --- a/src/frontend/packages/core/misc/custom/custom.scss +++ /dev/null @@ -1,5 +0,0 @@ -// Empty customization file - DO NOT EDIT - -// This file is in the .gitignore - changes will not be flagged - -// The customization build step will replace this file with the custom one if provided diff --git a/src/frontend/packages/core/misc/custom/custom.yaml b/src/frontend/packages/core/misc/custom/custom.yaml deleted file mode 100644 index db58c36ad7..0000000000 --- a/src/frontend/packages/core/misc/custom/custom.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Customization manifest - -files: - custom.scss: "sass/custom.scss" - favicon.ico: "favicon.ico" - login-bg.jpg: "assets/login-bg.jpg" - logo.png: "assets/logo.png" - nav-logo.png: "assets/nav-logo.png" - eula.html: "assets/eula.html" - -folders: - - "app/custom:src/custom" - - "assets/custom" - - "sass/custom" diff --git a/src/frontend/packages/core/misc/custom/eula.html b/src/frontend/packages/core/misc/custom/eula.html deleted file mode 100644 index dfd92e3d98..0000000000 --- a/src/frontend/packages/core/misc/custom/eula.html +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/src/frontend/packages/core/ng-package.json b/src/frontend/packages/core/ng-package.json new file mode 100644 index 0000000000..9a348247b8 --- /dev/null +++ b/src/frontend/packages/core/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../../../dist/core", + "lib": { + "entryFile": "src/public-api.ts" + } +} \ No newline at end of file diff --git a/src/frontend/packages/core/package.json b/src/frontend/packages/core/package.json new file mode 100644 index 0000000000..63f4668511 --- /dev/null +++ b/src/frontend/packages/core/package.json @@ -0,0 +1,9 @@ +{ + "name": "@stratosui/core", + "version": "0.0.1", + "stratos": { + "assets": { + "assets": "core/assets" + } + } +} diff --git a/src/frontend/packages/core/sass/_all-theme.scss b/src/frontend/packages/core/sass/_all-theme.scss index 9fc21b86f9..67ae92ae8b 100644 --- a/src/frontend/packages/core/sass/_all-theme.scss +++ b/src/frontend/packages/core/sass/_all-theme.scss @@ -48,7 +48,6 @@ @import '../src/shared/components/user-avatar/user-avatar.component.theme'; @import './components/text-status.theme'; @import './components/hyperlinks.theme'; -@import './mat-themes'; @import './mat-desktop'; @import './fonts'; @import './ansi-colors'; @@ -57,53 +56,28 @@ @import '../../cloud-foundry/src/shared/components/schema-form/schema-form.component.theme'; @import '../../cloud-foundry/src/features/services/services-wall/services-wall.component.theme'; -@import '../../cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-card/cf-security-groups-card.component.theme'; @import '../../cloud-foundry/src/features/cloud-foundry/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.theme'; @import '../../cloud-foundry/src/features/applications/application/application-base.component.theme'; @import '../../cloud-foundry/src/features/applications/deploy-application/deploy-application.component.theme'; @import '../../cloud-foundry/src/features/applications/deploy-application/deploy-application-step2/deploy-application-fs/deploy-application-fs.component.theme'; @import '../../cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-firehose/cloud-foundry-firehose.component.theme'; @import '../../cloud-foundry/src/features/service-catalog/service-catalog-page/service-catalog-page.component.theme'; -@import '../../cloud-foundry/src/features/applications/application-wall/application-wall.component.theme'; @import '../../core/src/features/error-page/error-page/error-page.component.theme'; @import '../../core/src/features/endpoints/backup-restore/restore-endpoints/restore-endpoints.component.theme'; @import '../../core/src/features/metrics/metrics/metrics.component.theme'; - -// Defaults -$side-nav-light-text: #fff; -$side-nav-light-bg: #333; -$side-nav-light-hover: #555; -$side-nav-light-active: #484848; - // Creates the app theme and applies it to the application // $theme = Angular Material Theme // $nav-theme - Colors for the Side Nav (optional) // $status-theme - Colors for status (optional) -@mixin app-theme($theme, $nav-theme: null, $status-theme: null) { - $background-colors: map-get($theme, background); - $foreground-colors: map-get($theme, foreground); - $is-dark: map-get($theme, is-dark); - $app-background-color: white; - $app-background-text-color: rgba(mat-color($foreground-colors, base), .65); - $primary: map-get($theme, primary); - $accent: map-get($theme, accent); - $warn: map-get($theme, warn); - $subdued: mat-contrast($primary, 50); - - @if $is-dark == true { - $app-background-color: lighten(mat-color($background-colors, background), 10%); - $subdued: lighten($subdued, 90); - } @else { - $app-background-color: darken(mat-color($background-colors, background), 2%); - $subdued: lighten($subdued, 50); - } +@mixin app-theme($stratos-theme, $nav-theme: null, $status-theme: null) { + $theme: map-get($stratos-theme, theme); + $app-theme: map-get($stratos-theme, app-theme); + $app-background-color: map-get($app-theme, app-background-color); html { background-color: $app-background-color; } - // App Theme defines a set of colors used by stratos components - $app-theme: (app-background-color: $app-background-color, app-background-text-color: rgba(mat-color($foreground-colors, base), .65), side-nav: app-generate-nav-theme($theme, $nav-theme), status: app-generate-status-theme($theme, $status-theme), subdued-color: $subdued, ansi-colors: $ansi-color-palette); // Pass the Material theme and the App Theme to components that need to be themed @include dialog-error-theme($theme, $app-theme); @include login-page-theme($theme, $app-theme); @include side-nav-theme($theme, $app-theme); @@ -122,7 +96,6 @@ $side-nav-light-active: #484848; @include app-hyperlinks($theme, $app-theme); @include app-no-content-message-theme($theme, $app-theme); @include app-boolean-indicator-theme($theme, $app-theme); - @include cf-security-group-theme($theme); @include loading-page-theme($theme, $app-theme); @include app-log-viewer-theme($theme, $app-theme); @include app-deploy-app-theme($theme, $app-theme); @@ -164,23 +137,3 @@ $side-nav-light-active: #484848; @include restore-endpoints-theme($theme, $app-theme); @include metrics-component-theme($theme, $app-theme); } - -@function app-generate-nav-theme($theme, $nav-theme: null) { - @if ($nav-theme) { - @return $nav-theme; - } @else { - // Use default palette for side navigation - @return (background: $side-nav-light-bg, background-top: $side-nav-light-bg, text: darken($side-nav-light-text, 10%), active: $side-nav-light-active, active-text: $side-nav-light-text, hover: $side-nav-light-hover, hover-text: $side-nav-light-text); - } -} - -@function app-generate-status-theme($theme, $status-theme: null) { - @if ($status-theme) { - @return $status-theme; - } @else { - $warn: map-get($theme, warn); - $primary: map-get($theme, primary); - $white: #fff; // Use default palette for status - @return (success: map-get($mat-green, 500), warning: map-get($mat-orange, 500), danger: mat-color($warn), tentative: map-get($mat-grey, 500), busy: mat-color($primary), text: $white, info: map-get($mat-blue, 500)); - } -} diff --git a/src/frontend/packages/core/sass/colors.scss b/src/frontend/packages/core/sass/colors.scss deleted file mode 100644 index 75071fe6ba..0000000000 --- a/src/frontend/packages/core/sass/colors.scss +++ /dev/null @@ -1,7 +0,0 @@ -// @import './suse-theme'; -$dark-grey: rgb(95, 95, 95); -$middle-grey: rgb(167, 169, 172); -$light-grey: rgb(220, 221, 222); -// $blue: map-get($suse-blue, 500); - -// See here for palette info: https://github.com/angular/material2/blob/master/src/lib/core/theming/_palette.scss diff --git a/src/frontend/packages/core/sass/theme.scss b/src/frontend/packages/core/sass/theme.scss index 365cd4750e..11ba592a9f 100644 --- a/src/frontend/packages/core/sass/theme.scss +++ b/src/frontend/packages/core/sass/theme.scss @@ -1,45 +1,46 @@ @import '~@angular/material/theming'; -@import './mat-themes'; @import './all-theme'; -@import './mat-colors'; -// Custom theme support -@import './custom'; +// Import the theme +@import '~@stratosui/theme'; -body.stratos { - .dark-theme { - // Dark Theme defaults - $dark-primary: mat-palette($mat-blue); - $dark-accent: mat-palette($mat-amber, A400, A100, A700); - $dark-warn: mat-palette($mat-red); - $dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn); +$stratos-dark-theme-supported: false !default; - $stratos-dark-theme: $dark-theme !default; - $stratos-dark-nav-theme: null !default; - $stratos-dark-status-theme: null !default; +// Themes - stratos-theme() function must be supplied by the theme +$stratos-themes: stratos-theme(); - @include angular-material-theme($stratos-dark-theme); - @include app-theme($stratos-dark-theme, $stratos-dark-nav-theme, $stratos-dark-status-theme); +// stratos-theme() can return a single theme rather than a map of default and dark themes +@if not map-has-key($stratos-themes, 'default') { + $tmp: $stratos-themes; + $stratos-themes: ( + default: $tmp + ) +} + +// Import any custom scss that the theme defines +@import '~@stratosui/theme/extensions'; + +// Default theme ( = light theme) +$stratos-theme: map-get($stratos-themes, default); +$theme: map-get($stratos-theme, theme); + +@if map-has-key($stratos-themes, 'dark') { + $stratos-dark-theme-supported: true; + $dark-stratos-theme: map-get($stratos-themes, dark); + $dark-theme: map-get($dark-stratos-theme, theme); + + body.stratos { + .dark-theme { + @include angular-material-theme($dark-theme); + @include app-theme($dark-stratos-theme); + } } } .default { - // Themes palettes and colors - $oss-theme-primary: mat-palette($mat-blue); - $oss-theme-accent: mat-palette($mat-blue); - $oss-theme-warn: mat-palette($mat-red); - $oss-theme: mat-light-theme($oss-theme-primary, $oss-theme-accent, $oss-theme-warn); - - // Default to using the open source theme - $stratos-theme: $oss-theme !default; - $stratos-nav-theme: null !default; - $stratos-status-theme: null !default; - - @include angular-material-theme($stratos-theme); - @include app-theme($stratos-theme, $stratos-nav-theme, $stratos-status-theme); + @include angular-material-theme($theme); + @include app-theme($stratos-theme); } -$stratos-dark-theme-supported: true !default; - -// Create the theme +// Import mat-core @include mat-core; diff --git a/src/frontend/packages/core/src/app.component.ts b/src/frontend/packages/core/src/app.component.ts index 542077cb5e..7e7be0556e 100644 --- a/src/frontend/packages/core/src/app.component.ts +++ b/src/frontend/packages/core/src/app.component.ts @@ -1,13 +1,13 @@ -import { AfterContentInit, Component, HostBinding, OnDestroy, OnInit, Inject } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { AfterContentInit, Component, HostBinding, Inject, OnDestroy, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { create } from 'rxjs-spy'; import { AuthOnlyAppState } from '../../store/src/app-state'; -import { ThemeService } from './core/theme.service'; +import { ThemeService } from '../../store/src/theme.service'; import { environment } from './environments/environment'; import { LoggedInService } from './logged-in.service'; -import { DOCUMENT } from '@angular/common'; @Component({ selector: 'app-root', diff --git a/src/frontend/packages/core/src/app.module.ts b/src/frontend/packages/core/src/app.module.ts index 7ba9fb46a1..24bfcbbe56 100644 --- a/src/frontend/packages/core/src/app.module.ts +++ b/src/frontend/packages/core/src/app.module.ts @@ -4,40 +4,40 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { Params, RouteReuseStrategy, RouterStateSnapshot } from '@angular/router'; import { DefaultRouterStateSerializer, RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store'; import { Store } from '@ngrx/store'; +import { StoreDevtoolsModule } from '@ngrx/store-devtools'; import { debounceTime, filter, withLatestFrom } from 'rxjs/operators'; import { CfAutoscalerModule } from '../../cf-autoscaler/src/cf-autoscaler.module'; import { CloudFoundryPackageModule } from '../../cloud-foundry/src/cloud-foundry-package.module'; import { SetRecentlyVisitedEntityAction } from '../../store/src/actions/recently-visited.actions'; -import { - UpdateUserFavoriteMetadataAction, -} from '../../store/src/actions/user-favourites-actions/update-user-favorite-metadata-action'; import { GeneralEntityAppState, GeneralRequestDataState } from '../../store/src/app-state'; import { EntityCatalogModule } from '../../store/src/entity-catalog.module'; import { entityCatalog } from '../../store/src/entity-catalog/entity-catalog'; import { EntityCatalogHelper } from '../../store/src/entity-catalog/entity-catalog-entity/entity-catalog.service'; import { EntityCatalogHelpers } from '../../store/src/entity-catalog/entity-catalog.helper'; -import { endpointSchemaKey } from '../../store/src/helpers/entity-factory'; +import { FavoritesConfigMapper } from '../../store/src/favorite-config-mapper'; +import { endpointEntityType, STRATOS_ENDPOINT_TYPE } from '../../store/src/helpers/stratos-entity-factory'; import { getAPIRequestDataState, selectEntity } from '../../store/src/selectors/api.selectors'; import { internalEventStateSelector } from '../../store/src/selectors/internal-events.selectors'; import { recentlyVisitedSelector } from '../../store/src/selectors/recently-visitied.selectors'; import { AppStoreModule } from '../../store/src/store.module'; +import { stratosEntityCatalog } from '../../store/src/stratos-entity-catalog'; +import { generateStratosEntities } from '../../store/src/stratos-entity-generator'; import { EndpointModel } from '../../store/src/types/endpoint.types'; import { IFavoriteMetadata, UserFavorite } from '../../store/src/types/user-favorites.types'; +import { UserFavoriteManager } from '../../store/src/user-favorite-manager'; import { TabNavService } from '../tab-nav.service'; import { XSRFModule } from '../xsrf.module'; import { AppComponent } from './app.component'; import { RouteModule } from './app.routing'; -import { STRATOS_ENDPOINT_TYPE } from './base-entity-schemas'; -import { generateStratosEntities } from './base-entity-types'; import { CoreModule } from './core/core.module'; import { CustomizationService } from './core/customizations.types'; import { DynamicExtensionRoutes } from './core/extension/dynamic-extension-routes'; import { ExtensionService } from './core/extension/extension-service'; import { getGitHubAPIURL, GITHUB_API_URL } from './core/github.helpers'; import { CurrentUserPermissionsService } from './core/permissions/current-user-permissions.service'; -import { UserFavoriteManager } from './core/user-favorite-manager'; import { CustomImportModule } from './custom-import.module'; +import { environment } from './environments/environment'; import { AboutModule } from './features/about/about.module'; import { DashboardModule } from './features/dashboard/dashboard.module'; import { HomeModule } from './features/home/home.module'; @@ -46,7 +46,6 @@ import { NoEndpointsNonAdminComponent } from './features/no-endpoints-non-admin/ import { SetupModule } from './features/setup/setup.module'; import { LoggedInService } from './logged-in.service'; import { CustomReuseStrategy } from './route-reuse-stragegy'; -import { FavoritesConfigMapper } from './shared/components/favorites-meta-card/favorite-config-mapper'; import { endpointEventKey, GlobalEventData, GlobalEventService } from './shared/global-events.service'; import { SidePanelService } from './shared/services/side-panel.service'; import { SharedModule } from './shared/shared.module'; @@ -80,6 +79,18 @@ export class CustomRouterStateSerializer } } +const storeDebugImports = environment.production ? [] : [ + StoreDevtoolsModule.instrument({ + maxAge: 100, + logOnly: !environment.production + }) +]; + +@NgModule({ + imports: storeDebugImports +}) +class AppStoreDebugModule { } + /** * `HttpXsrfTokenExtractor` which retrieves the token from a cookie. */ @@ -94,6 +105,7 @@ export class CustomRouterStateSerializer RouteModule, CloudFoundryPackageModule, AppStoreModule, + AppStoreDebugModule, BrowserModule, SharedModule, BrowserAnimationsModule, @@ -159,7 +171,7 @@ export class AppModule { if (!backendErrors.length) { return res; } - const entityConfig = entityCatalog.getEntity(STRATOS_ENDPOINT_TYPE, endpointSchemaKey); + const entityConfig = entityCatalog.getEntity(STRATOS_ENDPOINT_TYPE, endpointEntityType); res.push(new GlobalEventData(true, { endpoint: selectEntity(entityConfig.entityKey, eventId)(state), count: backendErrors.length @@ -248,7 +260,7 @@ export class AppModule { private syncFavorite(favorite: UserFavorite, entities: GeneralRequestDataState) { if (favorite) { - const isEndpoint = (favorite.entityType === endpointSchemaKey); + const isEndpoint = (favorite.entityType === endpointEntityType); // If the favorite is an endpoint ensure we look in the stratosEndpoint part of the store instead of, for example, cfEndpoint const entityKey = isEndpoint ? entityCatalog.getEntityKey({ ...favorite, @@ -258,10 +270,10 @@ export class AppModule { if (entity) { const newMetadata = this.favoritesConfigMapper.getEntityMetadata(favorite, entity); if (this.metadataHasChanged(favorite.metadata, newMetadata)) { - this.store.dispatch(new UpdateUserFavoriteMetadataAction({ + stratosEntityCatalog.userFavorite.api.updateFavorite({ ...favorite, metadata: newMetadata - })); + }); } } } diff --git a/src/frontend/packages/core/src/base-entity-schemas.ts b/src/frontend/packages/core/src/base-entity-schemas.ts deleted file mode 100644 index 19da225c8b..0000000000 --- a/src/frontend/packages/core/src/base-entity-schemas.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - endpointSchemaKey, - entityFactory, - systemInfoSchemaKey, - userFavouritesSchemaKey, - userProfileSchemaKey, -} from '../../store/src/helpers/entity-factory'; -import { EntitySchema } from '../../store/src/helpers/entity-schema'; - -export const metricEntityType = 'metrics'; - -export const STRATOS_ENDPOINT_TYPE = 'stratos'; -export const ENDPOINT_TYPE = 'endpoint'; - -class StratosEntitySchema extends EntitySchema { - constructor(entityType: string) { - super(entityType, STRATOS_ENDPOINT_TYPE); - } -} - -export const userFavoritesEntitySchema = new StratosEntitySchema(entityFactory(userFavouritesSchemaKey).entityType); -export const endpointEntitySchema = new StratosEntitySchema(entityFactory(endpointSchemaKey).entityType); -export const userProfileEntitySchema = new StratosEntitySchema(entityFactory(userProfileSchemaKey).entityType); -export const systemInfoEntitySchema = new StratosEntitySchema(entityFactory(systemInfoSchemaKey).entityType); diff --git a/src/frontend/packages/core/src/base-entity-types.ts b/src/frontend/packages/core/src/base-entity-types.ts deleted file mode 100644 index 15cee657b0..0000000000 --- a/src/frontend/packages/core/src/base-entity-types.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { - StratosCatalogEndpointEntity, - StratosCatalogEntity, -} from '../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; -import { - addOrUpdateUserFavoriteMetadataReducer, - deleteUserFavoriteMetadataReducer, -} from '../../store/src/reducers/favorite.reducer'; -import { systemEndpointsReducer } from '../../store/src/reducers/system-endpoints.reducer'; -import { - endpointEntitySchema, - STRATOS_ENDPOINT_TYPE, - systemInfoEntitySchema, - userFavoritesEntitySchema, - userProfileEntitySchema, -} from './base-entity-schemas'; -import { BaseEndpointAuth } from './features/endpoints/endpoint-auth'; -import { - MetricsEndpointDetailsComponent, -} from './features/metrics/metrics-endpoint-details/metrics-endpoint-details.component'; - -// -// These types are used to represent the base stratos types. -// - -/** - * This is used as a fake endpoint type to allow the store to be initiated correctly - */ -const stratosType = { - logoUrl: '', - authTypes: [], - type: STRATOS_ENDPOINT_TYPE, - schema: null -}; - -/** - * DefaultEndpointEntityType is used to represent a general endpoint - * This should not be used to actually attempt to render an endpoint and is instead used as a way to fill the - */ -class DefaultEndpointCatalogEntity extends StratosCatalogEntity { - constructor() { - super({ - schema: endpointEntitySchema, - type: endpointEntitySchema.entityType, - endpoint: stratosType, - }, { - dataReducers: [ - systemEndpointsReducer - ] - }); - } -} - -class UserFavoriteCatalogEntity extends StratosCatalogEntity { - constructor() { - super({ - schema: userFavoritesEntitySchema, - type: userFavoritesEntitySchema.entityType, - endpoint: stratosType, - }, { - dataReducers: [ - addOrUpdateUserFavoriteMetadataReducer, - deleteUserFavoriteMetadataReducer, - ] - }); - } -} - -class UserProfileCatalogEntity extends StratosCatalogEntity { - constructor() { - super({ - schema: userProfileEntitySchema, - type: userProfileEntitySchema.entityType, - endpoint: stratosType, - }); - } -} - -class SystemInfoCatalogEntity extends StratosCatalogEntity { - constructor() { - super({ - schema: systemInfoEntitySchema, - type: systemInfoEntitySchema.entityType, - endpoint: stratosType, - }); - } -} - -export function generateStratosEntities() { - return [ - new DefaultEndpointCatalogEntity(), - new SystemInfoCatalogEntity(), - new UserFavoriteCatalogEntity(), - new UserProfileCatalogEntity(), - // TODO: metrics location to be sorted - STRAT-152 - new StratosCatalogEndpointEntity({ - type: 'metrics', - label: 'Metrics', - labelPlural: 'Metrics', - tokenSharing: true, - logoUrl: '/core/assets/endpoint-icons/metrics.svg', - authTypes: [BaseEndpointAuth.UsernamePassword, BaseEndpointAuth.None], - renderPriority: 1, - listDetailsComponent: MetricsEndpointDetailsComponent, - }, - metadata => `/endpoints/metrics/${metadata.guid}` - ) - ]; -} - diff --git a/src/frontend/packages/core/src/features/endpoints/endpoint-auth.ts b/src/frontend/packages/core/src/core/endpoint-auth.ts similarity index 60% rename from src/frontend/packages/core/src/features/endpoints/endpoint-auth.ts rename to src/frontend/packages/core/src/core/endpoint-auth.ts index 757b0677e5..0fa06e90d1 100644 --- a/src/frontend/packages/core/src/features/endpoints/endpoint-auth.ts +++ b/src/frontend/packages/core/src/core/endpoint-auth.ts @@ -1,9 +1,17 @@ -import { EndpointAuthTypeNames } from './endpoint-helpers'; import { Validators } from '@angular/forms'; -import { EndpointType, EndpointAuthTypeConfig } from '../../core/extension/extension-types'; -import { CredentialsAuthFormComponent } from './connect-endpoint-dialog/auth-forms/credentials-auth-form.component'; -import { SSOAuthFormComponent } from './connect-endpoint-dialog/auth-forms/sso-auth-form.component'; -import { NoneAuthFormComponent } from './connect-endpoint-dialog/auth-forms/none-auth-form.component'; + +import { EndpointAuthTypeConfig, EndpointType } from '../../../store/src/extension-types'; +import { + CredentialsAuthFormComponent, +} from '../features/endpoints/connect-endpoint-dialog/auth-forms/credentials-auth-form.component'; +import { NoneAuthFormComponent } from '../features/endpoints/connect-endpoint-dialog/auth-forms/none-auth-form.component'; +import { SSOAuthFormComponent } from '../features/endpoints/connect-endpoint-dialog/auth-forms/sso-auth-form.component'; + +export enum EndpointAuthTypeNames { + CREDS = 'creds', + SSO = 'sso', + NONE = 'none' +} export abstract class BaseEndpointAuth { static readonly UsernamePassword = { diff --git a/src/frontend/packages/core/src/core/endpoints.service.spec.ts b/src/frontend/packages/core/src/core/endpoints.service.spec.ts index 006d10ccb7..3ac576abcd 100644 --- a/src/frontend/packages/core/src/core/endpoints.service.spec.ts +++ b/src/frontend/packages/core/src/core/endpoints.service.spec.ts @@ -1,8 +1,8 @@ import { inject, TestBed } from '@angular/core/testing'; - -import { CoreTestingModule } from '../../test-framework/core-test.modules'; import { createBasicStoreModule } from '@stratosui/store/testing'; + import { PaginationMonitorFactory } from '../../../store/src/monitors/pagination-monitor.factory'; +import { CoreTestingModule } from '../../test-framework/core-test.modules'; import { CoreModule } from './core.module'; import { EndpointsService } from './endpoints.service'; import { UtilsService } from './utils.service'; diff --git a/src/frontend/packages/core/src/core/endpoints.service.ts b/src/frontend/packages/core/src/core/endpoints.service.ts index 7160d50652..6354590f8a 100644 --- a/src/frontend/packages/core/src/core/endpoints.service.ts +++ b/src/frontend/packages/core/src/core/endpoints.service.ts @@ -7,10 +7,11 @@ import { first, map, skipWhile, withLatestFrom } from 'rxjs/operators'; import { RouterNav } from '../../../store/src/actions/router.actions'; import { EndpointOnlyAppState, IRequestEntityTypeState } from '../../../store/src/app-state'; import { entityCatalog } from '../../../store/src/entity-catalog/entity-catalog'; +import { EndpointHealthCheck } from '../../../store/src/entity-catalog/entity-catalog.types'; import { AuthState } from '../../../store/src/reducers/auth.reducer'; import { endpointEntitiesSelector, endpointStatusSelector } from '../../../store/src/selectors/endpoint.selectors'; import { EndpointModel, EndpointState } from '../../../store/src/types/endpoint.types'; -import { EndpointHealthCheck, EndpointHealthChecks } from '../../endpoints-health-checks'; +import { EndpointHealthChecks } from '../../endpoints-health-checks'; import { endpointHasMetricsByAvailable } from '../features/endpoints/endpoint-helpers'; import { UserService } from './user.service'; diff --git a/src/frontend/packages/core/src/core/entity-favorite-star/entity-favorite-star.component.spec.ts b/src/frontend/packages/core/src/core/entity-favorite-star/entity-favorite-star.component.spec.ts index 5aee8a1959..067a4e1f69 100644 --- a/src/frontend/packages/core/src/core/entity-favorite-star/entity-favorite-star.component.spec.ts +++ b/src/frontend/packages/core/src/core/entity-favorite-star/entity-favorite-star.component.spec.ts @@ -2,13 +2,13 @@ import { OverlayContainer } from '@angular/cdk/overlay'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BehaviorSubject, of } from 'rxjs'; +import { FavoritesConfigMapper } from '../../../../store/src/favorite-config-mapper'; import { PaginationMonitorFactory } from '../../../../store/src/monitors/pagination-monitor.factory'; import { IFavoriteMetadata, UserFavorite } from '../../../../store/src/types/user-favorites.types'; +import { UserFavoriteManager } from '../../../../store/src/user-favorite-manager'; import { BaseTestModulesNoShared } from '../../../test-framework/core-test.helper'; import { ConfirmationDialogService } from '../../shared/components/confirmation-dialog.service'; import { DialogConfirmComponent } from '../../shared/components/dialog-confirm/dialog-confirm.component'; -import { FavoritesConfigMapper } from '../../shared/components/favorites-meta-card/favorite-config-mapper'; -import { UserFavoriteManager } from '../user-favorite-manager'; import { EntityFavoriteStarComponent } from './entity-favorite-star.component'; describe('EntityFavoriteStarComponent', () => { diff --git a/src/frontend/packages/core/src/core/entity-favorite-star/entity-favorite-star.component.ts b/src/frontend/packages/core/src/core/entity-favorite-star/entity-favorite-star.component.ts index 6487ce3708..c345e7d523 100644 --- a/src/frontend/packages/core/src/core/entity-favorite-star/entity-favorite-star.component.ts +++ b/src/frontend/packages/core/src/core/entity-favorite-star/entity-favorite-star.component.ts @@ -2,12 +2,12 @@ import { Component, Input } from '@angular/core'; import { Observable } from 'rxjs'; import { first, tap } from 'rxjs/operators'; +import { FavoritesConfigMapper } from '../../../../store/src/favorite-config-mapper'; import { IFavoriteMetadata, UserFavorite } from '../../../../store/src/types/user-favorites.types'; +import { UserFavoriteManager } from '../../../../store/src/user-favorite-manager'; import { ConfirmationDialogConfig } from '../../shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from '../../shared/components/confirmation-dialog.service'; -import { FavoritesConfigMapper } from '../../shared/components/favorites-meta-card/favorite-config-mapper'; import { EndpointsService } from '../endpoints.service'; -import { UserFavoriteManager } from '../user-favorite-manager'; @Component({ selector: 'app-entity-favorite-star', diff --git a/src/frontend/packages/core/src/core/extension/extension-service.ts b/src/frontend/packages/core/src/core/extension/extension-service.ts index 82b7a305ff..c71151f5b7 100644 --- a/src/frontend/packages/core/src/core/extension/extension-service.ts +++ b/src/frontend/packages/core/src/core/extension/extension-service.ts @@ -1,4 +1,4 @@ -import { Injectable, NgModule, ModuleWithProviders } from '@angular/core'; +import { Injectable, ModuleWithProviders, NgModule } from '@angular/core'; import { ActivatedRoute, Route, Router } from '@angular/router'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; @@ -6,6 +6,7 @@ import { Observable } from 'rxjs'; import { AppState, GeneralEntityAppState } from '../../../../store/src/app-state'; import { EntityServiceFactory } from '../../../../store/src/entity-service-factory.service'; import { IPageSideNavTab } from '../../features/dashboard/page-side-nav/page-side-nav.component'; +import { CurrentUserPermissionsService } from '../permissions/current-user-permissions.service'; export const extensionsActionRouteKey = 'extensionsActionsKey'; @@ -23,7 +24,12 @@ export interface StratosTabMetadata { link: string; icon?: string; iconFont?: string; - hidden?: (store: Store, esf: EntityServiceFactory, activatedRoute: ActivatedRoute) => Observable; + hidden?: ( + store: Store, + esf: EntityServiceFactory, + activatedRoute: ActivatedRoute, + cups: CurrentUserPermissionsService + ) => Observable; } export interface StratosTabMetadataConfig extends StratosTabMetadata { @@ -97,7 +103,7 @@ function addExtensionTab(tab: StratosTabType, target: any, props: StratosTabMeta }); extensionMetadata.tabs[tab].push({ ...props - }); + }); } function addExtensionAction(action: StratosActionType, target: any, props: StratosActionMetadata) { diff --git a/src/frontend/packages/core/src/core/logger.service.spec.ts b/src/frontend/packages/core/src/core/logger.service.spec.ts index e2f904e060..1720fe0b84 100644 --- a/src/frontend/packages/core/src/core/logger.service.spec.ts +++ b/src/frontend/packages/core/src/core/logger.service.spec.ts @@ -1,7 +1,7 @@ import { inject, TestBed } from '@angular/core/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratosui/store/testing'; import { LoggerService } from './logger.service'; describe('LoggerService', () => { diff --git a/src/frontend/packages/core/src/core/permissions/current-user-permissions.config.ts b/src/frontend/packages/core/src/core/permissions/current-user-permissions.config.ts index a91481dd62..9e08107255 100644 --- a/src/frontend/packages/core/src/core/permissions/current-user-permissions.config.ts +++ b/src/frontend/packages/core/src/core/permissions/current-user-permissions.config.ts @@ -1,3 +1,5 @@ +import { PermissionValues } from '../../../../store/src/selectors/current-user-role.selectors'; + export type PermissionConfigType = PermissionConfig[] | PermissionConfig | PermissionConfigLink; export interface IPermissionConfigs { [permissionString: string]: PermissionConfigType; @@ -6,7 +8,6 @@ export interface IPermissionConfigs { export type PermissionTypes = string; export type CurrentUserPermissions = string; export type ScopeStrings = string; -export type PermissionValues = string; export class PermissionConfig { constructor( public type: PermissionTypes, diff --git a/src/frontend/packages/core/src/core/permissions/current-user-permissions.service.spec.ts b/src/frontend/packages/core/src/core/permissions/current-user-permissions.service.spec.ts index e8e69c7782..90d5401db5 100644 --- a/src/frontend/packages/core/src/core/permissions/current-user-permissions.service.spec.ts +++ b/src/frontend/packages/core/src/core/permissions/current-user-permissions.service.spec.ts @@ -5,12 +5,12 @@ import { first, tap } from 'rxjs/operators'; import { AppState } from '../../../../store/src/app-state'; import { EntityCatalogTestModule, TEST_CATALOGUE_ENTITIES } from '../../../../store/src/entity-catalog-test.module'; import { EntityCatalogEntityConfig } from '../../../../store/src/entity-catalog/entity-catalog.types'; +import { endpointEntityType, stratosEntityFactory } from '../../../../store/src/helpers/stratos-entity-factory'; +import { generateStratosEntities } from '../../../../store/src/stratos-entity-generator'; import { EndpointModel } from '../../../../store/src/types/endpoint.types'; import { BaseEntityValues } from '../../../../store/src/types/entity.types'; import { PaginationState } from '../../../../store/src/types/pagination.types'; import { AppTestModule } from '../../../test-framework/core-test.helper'; -import { endpointEntitySchema } from '../../base-entity-schemas'; -import { generateStratosEntities } from '../../base-entity-types'; import { PermissionConfig } from './current-user-permissions.config'; import { CurrentUserPermissionsService } from './current-user-permissions.service'; import { StratosPermissionStrings, StratosPermissionTypes, StratosScopeStrings } from './stratos-user-permissions.checker'; @@ -138,7 +138,7 @@ describe('CurrentUserPermissionsService', () => { // Create request and requestData sections const entityMap = new Map>([ [ - endpointEntitySchema, + stratosEntityFactory(endpointEntityType), endpoints.map(endpoint => ({ guid: endpoint.guid, data: endpoint diff --git a/src/frontend/packages/core/src/core/permissions/current-user-permissions.service.ts b/src/frontend/packages/core/src/core/permissions/current-user-permissions.service.ts index 0a7490fb0c..4ad096b7c5 100644 --- a/src/frontend/packages/core/src/core/permissions/current-user-permissions.service.ts +++ b/src/frontend/packages/core/src/core/permissions/current-user-permissions.service.ts @@ -4,10 +4,7 @@ import { combineLatest, Observable, of } from 'rxjs'; import { distinctUntilChanged, map, switchMap } from 'rxjs/operators'; import { InternalAppState } from '../../../../store/src/app-state'; -import { entityCatalog } from '../../../../store/src/entity-catalog/entity-catalog'; -import { selectEntity } from '../../../../store/src/selectors/api.selectors'; -import { EndpointModel } from '../../../../store/src/types/endpoint.types'; -import { ENDPOINT_TYPE, STRATOS_ENDPOINT_TYPE } from '../../base-entity-schemas'; +import { stratosEntityCatalog } from '../../../../store/src/stratos-entity-catalog'; import { LoggerService } from '../logger.service'; import { CurrentUserPermissions, @@ -23,7 +20,7 @@ import { import { StratosUserPermissionsChecker } from './stratos-user-permissions.checker'; -export const CUSTOM_USER_PERMISSION_CHECKERS = 'custom_user_perm_checkers' +export const CUSTOM_USER_PERMISSION_CHECKERS = 'custom_user_perm_checkers'; @Injectable() export class CurrentUserPermissionsService { @@ -38,7 +35,7 @@ export class CurrentUserPermissionsService { this.allCheckers = [ new StratosUserPermissionsChecker(store), ...nullSafeCustomCheckers - ] + ]; } /** * @param action The action we're going to check the user's access to. @@ -56,13 +53,13 @@ export class CurrentUserPermissionsService { ): Observable { let actionConfig; if (typeof action === 'string') { - let permConfigType = this.getPermissionConfig(action); + const permConfigType = this.getPermissionConfig(action); if (!permConfigType) { return of(false); // Logging handled in getPermissionConfig } actionConfig = this.getConfig(permConfigType); } else { - actionConfig = this.getConfig(action) + actionConfig = this.getConfig(action); } const obs$ = this.getCanObservable(actionConfig, endpointGuid, ...args); return obs$ ? @@ -79,8 +76,7 @@ export class CurrentUserPermissionsService { } else if (actionConfig) { return this.getSimplePermission(actionConfig, endpointGuid, ...args); } else if (endpointGuid) { - const key = entityCatalog.getEntityKey(STRATOS_ENDPOINT_TYPE, ENDPOINT_TYPE); - return this.store.select(selectEntity(key, endpointGuid)).pipe( + return stratosEntityCatalog.endpoint.store.getEntityMonitor(endpointGuid).entity$.pipe( switchMap(endpoint => endpoint ? this.getFallbackPermission(endpointGuid, endpoint.cnsi_type) : of(false) @@ -96,7 +92,7 @@ export class CurrentUserPermissionsService { 'permissions check', actionConfig.type, of(false) - ) + ); } private getComplexPermission(permissionConfig: PermissionConfig[], endpointGuid?: string, ...args: any[]) { @@ -116,7 +112,7 @@ export class CurrentUserPermissionsService { [{ checks: [of(false)] }] - ) + ); } private getConfig(config: PermissionConfigType, tries = 0): PermissionConfig[] | PermissionConfig { @@ -152,7 +148,7 @@ export class CurrentUserPermissionsService { 'fallback permission', 'N/A', of(null) - ) + ); } private getPermissionConfig(key: CurrentUserPermissions): PermissionConfigType { @@ -161,7 +157,7 @@ export class CurrentUserPermissionsService { 'permissions checker', key, null - ) + ); } /** diff --git a/src/frontend/packages/core/src/core/permissions/stratos-user-permissions.checker.ts b/src/frontend/packages/core/src/core/permissions/stratos-user-permissions.checker.ts index a0bcb58695..db4c382b26 100644 --- a/src/frontend/packages/core/src/core/permissions/stratos-user-permissions.checker.ts +++ b/src/frontend/packages/core/src/core/permissions/stratos-user-permissions.checker.ts @@ -5,8 +5,9 @@ import { GeneralEntityAppState } from '../../../../store/src/app-state'; import { getCurrentUserStratosHasScope, getCurrentUserStratosRole, + PermissionValues, } from '../../../../store/src/selectors/current-user-role.selectors'; -import { IPermissionConfigs, PermissionConfig, PermissionTypes, PermissionValues } from './current-user-permissions.config'; +import { IPermissionConfigs, PermissionConfig, PermissionTypes } from './current-user-permissions.config'; import { BaseCurrentUserPermissionsChecker, IConfigGroups, diff --git a/src/frontend/packages/core/src/core/stateful-icon/stateful-icon.component.spec.ts b/src/frontend/packages/core/src/core/stateful-icon/stateful-icon.component.spec.ts index f258f527ae..1a8cd63bc7 100644 --- a/src/frontend/packages/core/src/core/stateful-icon/stateful-icon.component.spec.ts +++ b/src/frontend/packages/core/src/core/stateful-icon/stateful-icon.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { StratosStatus } from '../../shared/shared.types'; +import { StratosStatus } from '../../../../store/src/types/shared.types'; import { MDAppModule } from '../md.module'; import { StatefulIconComponent } from './stateful-icon.component'; diff --git a/src/frontend/packages/core/src/core/stateful-icon/stateful-icon.component.ts b/src/frontend/packages/core/src/core/stateful-icon/stateful-icon.component.ts index 5ad05aa35d..fdcd63847a 100644 --- a/src/frontend/packages/core/src/core/stateful-icon/stateful-icon.component.ts +++ b/src/frontend/packages/core/src/core/stateful-icon/stateful-icon.component.ts @@ -1,5 +1,6 @@ -import { Component, Input, OnInit, TemplateRef, ViewChild } from '@angular/core'; -import { StratosStatus } from '../../shared/shared.types'; +import { Component, Input, TemplateRef } from '@angular/core'; + +import { StratosStatus } from '../../../../store/src/types/shared.types'; interface IconDefinition { icon: string; diff --git a/src/frontend/packages/core/src/core/user-profile.service.ts b/src/frontend/packages/core/src/core/user-profile.service.ts index f83b16a256..0a496d3adc 100644 --- a/src/frontend/packages/core/src/core/user-profile.service.ts +++ b/src/frontend/packages/core/src/core/user-profile.service.ts @@ -1,24 +1,15 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import { combineLatest, Observable, of as observableOf, of } from 'rxjs'; +import { combineLatest, Observable, of as observableOf } from 'rxjs'; import { filter, first, map, publishReplay, refCount, switchMap } from 'rxjs/operators'; -import { - FetchUserProfileAction, - UpdateUserPasswordAction, - UpdateUserProfileAction, -} from '../../../store/src/actions/user-profile.actions'; import { AppState } from '../../../store/src/app-state'; -import { userProfilePasswordUpdatingKey } from '../../../store/src/effects/user-profile.effects'; -import { entityCatalog } from '../../../store/src/entity-catalog/entity-catalog'; import { EntityService } from '../../../store/src/entity-service'; -import { EntityServiceFactory } from '../../../store/src/entity-service-factory.service'; -import { ActionState, getDefaultActionState, rootUpdatingKey } from '../../../store/src/reducers/api-request-reducer/types'; +import { ActionState, getDefaultActionState } from '../../../store/src/reducers/api-request-reducer/types'; import { AuthState } from '../../../store/src/reducers/auth.reducer'; -import { selectRequestInfo, selectUpdateInfo } from '../../../store/src/selectors/api.selectors'; +import { stratosEntityCatalog } from '../../../store/src/stratos-entity-catalog'; import { SessionData } from '../../../store/src/types/auth.types'; import { UserProfileInfo, UserProfileInfoEmail, UserProfileInfoUpdates } from '../../../store/src/types/user-profile.types'; -import { userProfileEntitySchema } from '../base-entity-schemas'; @Injectable() @@ -32,21 +23,22 @@ export class UserProfileService { userProfile$: Observable; - private stratosUserConfig = entityCatalog.getEntity(userProfileEntitySchema.endpointType, userProfileEntitySchema.entityType); + private userGuid$: Observable; constructor( private store: Store, - esf: EntityServiceFactory ) { - if (!this.stratosUserConfig) { - console.error('Can not get user profile entity'); - this.userProfile$ = of({} as UserProfileInfo); - return; - } + this.userGuid$ = this.store.select(s => s.auth).pipe( + filter((auth: AuthState) => !!(auth && auth.sessionData)), + map((auth: AuthState) => auth.sessionData), + filter((sessionData: SessionData) => !!sessionData.user), + first(), + map(data => data.user.guid) + ); - this.entityService = this.createFetchUserAction().pipe( + this.entityService = this.userGuid$.pipe( first(), - map(action => esf.create(action.guid, action)), + map(userGuid => stratosEntityCatalog.userProfile.store.getEntityService(userGuid)), publishReplay(1), refCount() ); @@ -60,27 +52,16 @@ export class UserProfileService { switchMap(service => service.isFetchingEntity$) ); - this.isError$ = this.store.select(selectRequestInfo(this.stratosUserConfig.entityKey, FetchUserProfileAction.guid)).pipe( + this.isError$ = this.entityService.pipe( + switchMap(es => es.entityMonitor.entityRequest$), filter(requestInfo => !!requestInfo && !requestInfo.fetching), map(requestInfo => requestInfo.error) - ); - } - - private createFetchUserAction(): Observable { - return this.store.select(s => s.auth).pipe( - filter((auth: AuthState) => !!(auth && auth.sessionData)), - map((auth: AuthState) => auth.sessionData), - filter((sessionData: SessionData) => !!sessionData.user), - first(), - map(data => new FetchUserProfileAction(data.user.guid)) - ); + ) } fetchUserProfile() { // Once we have the user's guid, fetch their profile - this.createFetchUserAction().pipe(first()).subscribe(action => { - this.store.dispatch(action); - }); + this.userGuid$.pipe(first()).subscribe(userGuid => stratosEntityCatalog.userProfile.api.get(userGuid)); } getPrimaryEmailAddress(profile: UserProfileInfo): string { @@ -135,11 +116,8 @@ export class UserProfileService { if (profileChanges.emailAddress) { this.setPrimaryEmailAddress(updatedProfile, profileChanges.emailAddress); } - this.store.dispatch(new UpdateUserProfileAction(updatedProfile, profileChanges.currentPassword)); - const actionState = selectUpdateInfo(this.stratosUserConfig.entityKey, - FetchUserProfileAction.guid, - rootUpdatingKey); - return this.store.select(actionState).pipe( + + return stratosEntityCatalog.userProfile.api.updateProfile(updatedProfile, profileChanges.currentPassword).pipe( filter(item => item && !item.busy) ); } @@ -149,11 +127,7 @@ export class UserProfileService { oldPassword: profileChanges.currentPassword, password: profileChanges.newPassword }; - this.store.dispatch(new UpdateUserPasswordAction(profile.id, passwordUpdates)); - const actionState = selectUpdateInfo(this.stratosUserConfig.entityKey, - FetchUserProfileAction.guid, - userProfilePasswordUpdatingKey); - return this.store.select(actionState).pipe( + return stratosEntityCatalog.userProfile.api.updatePassword(profile.id, passwordUpdates).pipe( filter(item => item && !item.busy) ); } diff --git a/src/frontend/packages/core/src/core/user.service.spec.ts b/src/frontend/packages/core/src/core/user.service.spec.ts index 6fdaab8b3a..f831c61b18 100644 --- a/src/frontend/packages/core/src/core/user.service.spec.ts +++ b/src/frontend/packages/core/src/core/user.service.spec.ts @@ -1,7 +1,7 @@ import { inject, TestBed } from '@angular/core/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../test-framework/core-test.modules'; -import { createBasicStoreModule } from '@stratosui/store/testing'; import { SharedModule } from '../shared/shared.module'; import { CoreModule } from './core.module'; import { UserService } from './user.service'; diff --git a/src/frontend/packages/core/src/core/utils.service.ts b/src/frontend/packages/core/src/core/utils.service.ts index 83de981544..d3d1c3f837 100644 --- a/src/frontend/packages/core/src/core/utils.service.ts +++ b/src/frontend/packages/core/src/core/utils.service.ts @@ -11,56 +11,6 @@ export function getIdFromRoute(activatedRoute: ActivatedRoute, id: string) { return null; } -export type OptionalKeys = Exclude<{ - [K in keyof T]: T extends Record - ? K - : never -}[keyof T], undefined> - - -export type NonOptionalKeys = Exclude<{ - [K in keyof T]: T extends Record - ? K - : never -}[keyof T], undefined> - -export type NeverKeys = Exclude<{ - [K in keyof T]: T[K] extends never - ? K - : never -}[keyof T], undefined> - - -/** - * Remove keys such as typed indexes (i.e. [key: string]) - * For magic see - * - https://github.com/Microsoft/TypeScript/issues/25987#issuecomment-441224690 - * - https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-414808995 - */ -export type KnownKeys = { - [K in keyof T]: string extends K ? never : number extends K ? never : K -} extends { [_ in keyof T]: infer U } ? ({} extends U ? never : U) : never; - -/** - * Pick all properties who's function has the specified return type U - */ -export type FilteredByReturnType any }, U> = { - [P in keyof T]: ReturnType extends U ? T[P] : never -}; - -/** - * Pick all properties who's function do not have the specified return type U - */ -export type FilteredByNotReturnType any }, U> = { - [P in keyof T]: ReturnType extends U ? never : T[P] -}; - -// Note - Adding }[keyof T] to [P in keyof T] types should filter out properties of type `never`, however this fails with generics! -export type FilteredByValueType any }, U> = { - [P in keyof T]: T[P] extends U ? never : T[P] -}; - - export const urlValidationExpression = '^' + // protocol identifier @@ -319,13 +269,6 @@ export const safeUnsubscribe = (...subs: Subscription[]) => { export const truthyIncludingZero = (obj: any): boolean => !!obj || obj === 0; export const truthyIncludingZeroString = (obj: any): string => truthyIncludingZero(obj) ? obj.toString() : null; -export const sortStringify = (obj: { [key: string]: string | string[] | number }): string => { - const keys = Object.keys(obj).sort(); - return keys.reduce((res, key) => { - return res += `${key}-${obj[key]},`; - }, ''); -}; - /** * Real basic, shallow check */ diff --git a/src/frontend/packages/core/misc/custom/custom.module.ts_ b/src/frontend/packages/core/src/custom-import.module.ts similarity index 57% rename from src/frontend/packages/core/misc/custom/custom.module.ts_ rename to src/frontend/packages/core/src/custom-import.module.ts index b4118c293d..3d84d8fdec 100644 --- a/src/frontend/packages/core/misc/custom/custom.module.ts_ +++ b/src/frontend/packages/core/src/custom-import.module.ts @@ -2,7 +2,8 @@ import { NgModule } from '@angular/core'; // Default empty customization module - DO NOT EDIT -// This file is in the .gitignore - changes will not be flagged +// These modules will be intercepted by the custom webpack configuration +// They are only here so that on a fresh checkout, both modules are defined @NgModule() export class CustomImportModule { } diff --git a/src/frontend/packages/core/src/environments/environment.prod.ts b/src/frontend/packages/core/src/environments/environment.prod.ts index 651a68d984..da8e7d8dbd 100644 --- a/src/frontend/packages/core/src/environments/environment.prod.ts +++ b/src/frontend/packages/core/src/environments/environment.prod.ts @@ -1,10 +1,11 @@ +import { cfAPIVersion, proxyAPIVersion } from '../../../store/src/jetstream'; import { LogLevel } from './../../../store/src/actions/log.actions'; export const environment = { production: true, logLevel: LogLevel.WARN, - proxyAPIVersion: 'v1', - cfAPIVersion: 'v2', + proxyAPIVersion, + cfAPIVersion, logToConsole: true, logEnableConsoleActions: false, showObsDebug: false, diff --git a/src/frontend/packages/core/src/environments/environment.ts b/src/frontend/packages/core/src/environments/environment.ts index bb1db6f839..e10f7527a5 100644 --- a/src/frontend/packages/core/src/environments/environment.ts +++ b/src/frontend/packages/core/src/environments/environment.ts @@ -1,4 +1,5 @@ import { LogLevel } from '../../../store/src/actions/log.actions'; +import { cfAPIVersion, proxyAPIVersion } from '../../../store/src/jetstream'; // The file contents for the current environment will overwrite these during build. // The build system defaults to the dev environment which uses `environment.ts`, but if you do @@ -7,8 +8,8 @@ import { LogLevel } from '../../../store/src/actions/log.actions'; export const environment = { production: false, - proxyAPIVersion: 'v1', - cfAPIVersion: 'v2', + proxyAPIVersion, + cfAPIVersion, logLevel: LogLevel.DEBUG, logToConsole: true, logEnableConsoleActions: false, diff --git a/src/frontend/packages/core/src/features/about/about.module.ts b/src/frontend/packages/core/src/features/about/about.module.ts index c71cd11f55..9fe18f06af 100644 --- a/src/frontend/packages/core/src/features/about/about.module.ts +++ b/src/frontend/packages/core/src/features/about/about.module.ts @@ -5,8 +5,7 @@ import { SharedModule } from '../../shared/shared.module'; import { AboutPageComponent } from './about-page/about-page.component'; import { AboutRoutingModule } from './about.routing'; import { DiagnosticsPageComponent } from './diagnostics-page/diagnostics-page.component'; -import { EulaPageComponent, EulaPageContentComponent } from './eula-page/eula-page.component'; - +import { EulaPageComponent } from './eula-page/eula-page.component'; @NgModule({ @@ -17,7 +16,6 @@ import { EulaPageComponent, EulaPageContentComponent } from './eula-page/eula-pa ], declarations: [ AboutPageComponent, - EulaPageContentComponent, EulaPageComponent, DiagnosticsPageComponent ] diff --git a/src/frontend/packages/core/src/features/about/eula-page/eula-page.component.html b/src/frontend/packages/core/src/features/about/eula-page/eula-page.component.html index 27791db6f3..3a40946d0b 100644 --- a/src/frontend/packages/core/src/features/about/eula-page/eula-page.component.html +++ b/src/frontend/packages/core/src/features/about/eula-page/eula-page.component.html @@ -8,4 +8,4 @@

EULA

- \ No newline at end of file +
diff --git a/src/frontend/packages/core/src/features/about/eula-page/eula-page.component.spec.ts b/src/frontend/packages/core/src/features/about/eula-page/eula-page.component.spec.ts index 20023d114b..113aac37d3 100644 --- a/src/frontend/packages/core/src/features/about/eula-page/eula-page.component.spec.ts +++ b/src/frontend/packages/core/src/features/about/eula-page/eula-page.component.spec.ts @@ -1,3 +1,5 @@ +import { HttpClientModule } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { createBasicStoreModule } from '@stratosui/store/testing'; @@ -5,7 +7,7 @@ import { createBasicStoreModule } from '@stratosui/store/testing'; import { TabNavService } from '../../../../tab-nav.service'; import { CoreModule } from '../../../core/core.module'; import { SharedModule } from '../../../shared/shared.module'; -import { EulaPageComponent, EulaPageContentComponent } from './eula-page.component'; +import { EulaPageComponent } from './eula-page.component'; describe('EulaPageComponent', () => { let component: EulaPageComponent; @@ -13,11 +15,13 @@ describe('EulaPageComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [EulaPageComponent, EulaPageContentComponent], + declarations: [EulaPageComponent], imports: [ CoreModule, RouterTestingModule, SharedModule, + HttpClientModule, + HttpClientTestingModule, createBasicStoreModule(), ], providers: [TabNavService] diff --git a/src/frontend/packages/core/src/features/about/eula-page/eula-page.component.ts b/src/frontend/packages/core/src/features/about/eula-page/eula-page.component.ts index 8271e24c66..a93e345bf3 100644 --- a/src/frontend/packages/core/src/features/about/eula-page/eula-page.component.ts +++ b/src/frontend/packages/core/src/features/about/eula-page/eula-page.component.ts @@ -1,17 +1,12 @@ -import { Component, OnInit } from '@angular/core'; - -@Component({ - selector: 'app-eula-content', - templateUrl: '../../../../assets/eula.html' -}) -export class EulaPageContentComponent { } +import { Component } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; @Component({ selector: 'app-eula-page', templateUrl: './eula-page.component.html', styleUrls: ['./eula-page.component.scss'] }) -export class EulaPageComponent implements OnInit { +export class EulaPageComponent { public breadcrumbs = [ { @@ -19,10 +14,14 @@ export class EulaPageComponent implements OnInit { } ]; - constructor() { } - - ngOnInit() { + public eulaHtml = ''; + // Load the EULA + constructor(http: HttpClient) { + http.get('/core/assets/eula.html', {responseType: 'text'}).subscribe( + html => this.eulaHtml = html, + () => this.eulaHtml = 'An error occurred retrieving the EULA' + ); } } diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss index d212cac3c2..26a2d129ce 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss @@ -1,4 +1,3 @@ -@import '../../../../sass/colors'; @import '../../../../sass/mixins'; $app-sub-header-height: 48px; diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts index c409c37976..8310e6723e 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts @@ -9,11 +9,11 @@ import { distinctUntilChanged, filter, map, startWith, withLatestFrom } from 'rx import { CloseSideNav, DisableMobileNav, EnableMobileNav } from '../../../../../store/src/actions/dashboard-actions'; import { GetCurrentUsersRelations } from '../../../../../store/src/actions/permissions.actions'; -import { GetUserFavoritesAction } from '../../../../../store/src/actions/user-favourites-actions/get-user-favorites-action'; import { DashboardOnlyAppState } from '../../../../../store/src/app-state'; import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; import { DashboardState } from '../../../../../store/src/reducers/dashboard-reducer'; import { selectDashboardState } from '../../../../../store/src/selectors/dashboard.selectors'; +import { stratosEntityCatalog } from '../../../../../store/src/stratos-entity-catalog'; import { TabNavService } from '../../../../tab-nav.service'; import { CustomizationService } from '../../../core/customizations.types'; import { EndpointsService } from '../../../core/endpoints.service'; @@ -144,7 +144,7 @@ export class DashboardBaseComponent implements OnInit, OnDestroy, AfterViewInit }); this.dispatchRelations(); - this.store.dispatch(new GetUserFavoritesAction()); + stratosEntityCatalog.userFavorite.api.getAll(); } ngOnDestroy() { @@ -190,7 +190,7 @@ export class DashboardBaseComponent implements OnInit, OnDestroy, AfterViewInit link: path + '/' + route.path }; if (item.requiresEndpointType) { - // Upstream always likes to show Cloud Foundry related endpoints - other distributions can chane this behaviour + // Upstream always likes to show Cloud Foundry related endpoints - other distributions can change this behaviour const alwaysShow = this.cs.get().alwaysShowNavForEndpointTypes ? this.cs.get().alwaysShowNavForEndpointTypes(item.requiresEndpointType) : (item.requiresEndpointType === 'cf'); item.hidden = alwaysShow ? of(false) : this.endpointsService.doesNotHaveConnectedEndpointType(item.requiresEndpointType); diff --git a/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.ts b/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.ts index a91f4bd200..b74dbed831 100644 --- a/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.ts +++ b/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.ts @@ -4,12 +4,15 @@ import { Store } from '@ngrx/store'; import { Observable, of } from 'rxjs'; import { AppState } from '../../../../../store/src/app-state'; +import { EntityServiceFactory } from '../../../../../store/src/entity-service-factory.service'; import { selectIsMobile } from '../../../../../store/src/selectors/dashboard.selectors'; import { TabNavService } from '../../../../tab-nav.service'; -import { EntityServiceFactory } from '../../../../../store/src/entity-service-factory.service'; import { StratosTabMetadata } from '../../../core/extension/extension-service'; +import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; import { IBreadcrumb } from '../../../shared/components/breadcrumbs/breadcrumbs.types'; + + export interface IPageSideNavTab extends StratosTabMetadata { hidden$?: Observable; } @@ -28,7 +31,7 @@ export class PageSideNavComponent implements OnInit { } this.pTabs = tabs.map(tab => ({ ...tab, - hidden$: tab.hidden$ || (tab.hidden ? tab.hidden(this.store, this.esf, this.activatedRoute) : of(false)) + hidden$: tab.hidden$ || (tab.hidden ? tab.hidden(this.store, this.esf, this.activatedRoute, this.cups) : of(false)) })); } get tabs(): IPageSideNavTab[] { @@ -45,6 +48,7 @@ export class PageSideNavComponent implements OnInit { private store: Store, private esf: EntityServiceFactory, private activatedRoute: ActivatedRoute, + private cups: CurrentUserPermissionsService ) { this.isMobile$ = this.store.select(selectIsMobile); } diff --git a/src/frontend/packages/core/src/features/dashboard/side-nav/side-nav.component.theme.scss b/src/frontend/packages/core/src/features/dashboard/side-nav/side-nav.component.theme.scss index 7c2b8a671e..e4023c6b4c 100644 --- a/src/frontend/packages/core/src/features/dashboard/side-nav/side-nav.component.theme.scss +++ b/src/frontend/packages/core/src/features/dashboard/side-nav/side-nav.component.theme.scss @@ -4,6 +4,17 @@ $side-nav-colors: map-get($app-theme, side-nav); $side-nav-bottom-color: map-get($app-theme, subdued-color); $side-nav-hover-color: mat-color($side-nav-colors, hover); + $header-span: map-get($app-theme, header-background-span); + + $side-nav-top-background: map-get($side-nav-colors, background); + $side-nav-top-foreground: map-get($side-nav-colors, text); + + // Does the header background color span across the top, or is the sidenav background color used for the top-left portion + @if $header-span == true { + $side-nav-top-background: map-get($app-theme, header-background-color); + $side-nav-top-foreground: map-get($app-theme, header-foreground-color); + } + .side-nav { &__nav-toggle { color: mat-color($side-nav-colors, text); @@ -12,7 +23,8 @@ background-color: mat-color($side-nav-colors, background); } &__top { - background-color: mat-color($side-nav-colors, background-top); + background-color: $side-nav-top-background; + color: $side-nav-top-foreground; } &__item { color: mat-color($side-nav-colors, text); diff --git a/src/frontend/packages/core/src/features/endpoints/backup-restore/backup-endpoints.service.ts b/src/frontend/packages/core/src/features/endpoints/backup-restore/backup-endpoints.service.ts index 5251a66479..2aee2629c1 100644 --- a/src/frontend/packages/core/src/features/endpoints/backup-restore/backup-endpoints.service.ts +++ b/src/frontend/packages/core/src/features/endpoints/backup-restore/backup-endpoints.service.ts @@ -5,9 +5,9 @@ import { BehaviorSubject, Observable } from 'rxjs'; import { first, map } from 'rxjs/operators'; import { GeneralEntityAppState } from '../../../../../store/src/app-state'; +import { BrowserStandardEncoder } from '../../../../../store/src/browser-encoder'; import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; -import { BrowserStandardEncoder } from '../../../helper'; import { BackupEndpointConfigUI, BackupEndpointConnectionTypes, diff --git a/src/frontend/packages/core/src/features/endpoints/backup-restore/backup-endpoints/backup-endpoints.component.ts b/src/frontend/packages/core/src/features/endpoints/backup-restore/backup-endpoints/backup-endpoints.component.ts index 3ad6856b48..9c41ea8a3d 100644 --- a/src/frontend/packages/core/src/features/endpoints/backup-restore/backup-endpoints/backup-endpoints.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/backup-restore/backup-endpoints/backup-endpoints.component.ts @@ -1,17 +1,13 @@ import { Component } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { Store } from '@ngrx/store'; import * as moment from 'moment'; import { Observable, of, Subject } from 'rxjs'; import { filter, first, map } from 'rxjs/operators'; -import { GetAllEndpoints } from '../../../../../../store/src/actions/endpoint.actions'; -import { AppState } from '../../../../../../store/src/app-state'; import { entityCatalog } from '../../../../../../store/src/entity-catalog/entity-catalog'; -import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; -import { getPaginationObservables } from '../../../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper'; +import { httpErrorResponseToSafeString } from '../../../../../../store/src/jetstream'; +import { stratosEntityCatalog } from '../../../../../../store/src/stratos-entity-catalog'; import { EndpointModel } from '../../../../../../store/src/types/endpoint.types'; -import { httpErrorResponseToSafeString } from '../../../../jetstream.helpers'; import { ConfirmationDialogConfig } from '../../../../shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from '../../../../shared/components/confirmation-dialog.service'; import { ITableListDataSource } from '../../../../shared/components/list/data-sources-controllers/list-data-source-types'; @@ -74,8 +70,6 @@ export class BackupEndpointsComponent { constructor( public service: BackupEndpointsService, - private store: Store, - private paginationMonitorFactory: PaginationMonitorFactory, private confirmDialog: ConfirmationDialogService, ) { this.setupSelectStep(); @@ -84,17 +78,7 @@ export class BackupEndpointsComponent { setupSelectStep() { - const action = new GetAllEndpoints(); - const endpointObs = getPaginationObservables({ - store: this.store, - action, - paginationMonitor: this.paginationMonitorFactory.create( - action.paginationKey, - action, - true - ) - }, true); - + const endpointObs = stratosEntityCatalog.endpoint.store.getAll.getPaginationService(); const endpoints$ = endpointObs.entities$.pipe( filter(entities => !!entities), diff --git a/src/frontend/packages/core/src/features/endpoints/backup-restore/restore-endpoints.service.ts b/src/frontend/packages/core/src/features/endpoints/backup-restore/restore-endpoints.service.ts index 8fa35048c1..38dee43257 100644 --- a/src/frontend/packages/core/src/features/endpoints/backup-restore/restore-endpoints.service.ts +++ b/src/frontend/packages/core/src/features/endpoints/backup-restore/restore-endpoints.service.ts @@ -5,10 +5,10 @@ import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { filter, map, switchMap } from 'rxjs/operators'; import { GeneralEntityAppState } from '../../../../../store/src/app-state'; +import { BrowserStandardEncoder } from '../../../../../store/src/browser-encoder'; import { selectSessionData } from '../../../../../store/src/reducers/auth.reducer'; import { SessionData } from '../../../../../store/src/types/auth.types'; import { LoggerService } from '../../../core/logger.service'; -import { BrowserStandardEncoder } from '../../../helper'; interface BackupContent { payload: string; diff --git a/src/frontend/packages/core/src/features/endpoints/backup-restore/restore-endpoints/restore-endpoints.component.ts b/src/frontend/packages/core/src/features/endpoints/backup-restore/restore-endpoints/restore-endpoints.component.ts index c3483dca50..3ddca26bd4 100644 --- a/src/frontend/packages/core/src/features/endpoints/backup-restore/restore-endpoints/restore-endpoints.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/backup-restore/restore-endpoints/restore-endpoints.component.ts @@ -5,10 +5,10 @@ import { Store } from '@ngrx/store'; import { Observable, of, Subject } from 'rxjs'; import { first, map } from 'rxjs/operators'; -import { GetAllEndpoints } from '../../../../../../store/src/actions/endpoint.actions'; import { GeneralEntityAppState } from '../../../../../../store/src/app-state'; +import { httpErrorResponseToSafeString } from '../../../../../../store/src/jetstream'; +import { stratosEntityCatalog } from '../../../../../../store/src/stratos-entity-catalog'; import { getEventFiles } from '../../../../core/browser-helper'; -import { httpErrorResponseToSafeString } from '../../../../jetstream.helpers'; import { ConfirmationDialogConfig } from '../../../../shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from '../../../../shared/components/confirmation-dialog.service'; import { StepOnNextFunction, StepOnNextResult } from '../../../../shared/components/stepper/step/step.component'; @@ -79,7 +79,7 @@ export class RestoreEndpointsComponent { }; const restoreSuccess = () => { - this.store.dispatch(new GetAllEndpoints()); + stratosEntityCatalog.endpoint.api.getAll() result.next({ success: true, redirect: true, diff --git a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/auth-forms/credentials-auth-form.component.ts b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/auth-forms/credentials-auth-form.component.ts index cdf3c405d0..b585c0ab96 100644 --- a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/auth-forms/credentials-auth-form.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/auth-forms/credentials-auth-form.component.ts @@ -1,6 +1,7 @@ -import { FormGroup } from '@angular/forms'; import { Component, Input } from '@angular/core'; -import { IAuthForm } from '../../../../core/extension/extension-types'; +import { FormGroup } from '@angular/forms'; + +import { IAuthForm } from '../../../../../../store/src/extension-types'; @Component({ selector: 'app-credentials-auth-form', diff --git a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/auth-forms/none-auth-form.component.ts b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/auth-forms/none-auth-form.component.ts index c76f5a954c..3b6bb325ee 100644 --- a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/auth-forms/none-auth-form.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/auth-forms/none-auth-form.component.ts @@ -1,6 +1,7 @@ import { Component, Input } from '@angular/core'; import { FormGroup } from '@angular/forms'; -import { IAuthForm } from '../../../../core/extension/extension-types'; + +import { IAuthForm } from '../../../../../../store/src/extension-types'; @Component({ selector: 'app-none-auth-form', diff --git a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/auth-forms/sso-auth-form.component.ts b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/auth-forms/sso-auth-form.component.ts index bdac43f89e..83243a24ec 100644 --- a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/auth-forms/sso-auth-form.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/auth-forms/sso-auth-form.component.ts @@ -1,6 +1,7 @@ import { Component, Input } from '@angular/core'; import { FormGroup } from '@angular/forms'; -import { IAuthForm } from '../../../../core/extension/extension-types'; + +import { IAuthForm } from '../../../../../../store/src/extension-types'; @Component({ selector: 'app-sso-auth-form', diff --git a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts index 3d0f72fa4c..3a56bdbc23 100644 --- a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts @@ -1,13 +1,11 @@ import { Component, Inject, OnDestroy } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { Store } from '@ngrx/store'; import { Subscription } from 'rxjs'; -import { ShowSnackBar } from '../../../../../store/src/actions/snackBar.actions'; -import { EndpointOnlyAppState } from '../../../../../store/src/app-state'; import { EndpointsService } from '../../../core/endpoints.service'; import { MarkdownPreviewComponent } from '../../../shared/components/markdown-preview/markdown-preview.component'; import { SidePanelService } from '../../../shared/services/side-panel.service'; +import { SnackBarService } from '../../../shared/services/snackbar.service'; import { ConnectEndpointConfig, ConnectEndpointService } from '../connect.service'; @@ -27,14 +25,14 @@ export class ConnectEndpointDialogComponent implements OnDestroy { constructor( public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: ConnectEndpointConfig, - private store: Store, endpointsService: EndpointsService, private sidePanelService: SidePanelService, + private snackBarService: SnackBarService, ) { - this.connectService = new ConnectEndpointService(store, endpointsService, data); + this.connectService = new ConnectEndpointService(endpointsService, data); this.hasConnected = this.connectService.hasConnected$.subscribe(() => { - this.store.dispatch(new ShowSnackBar(`Connected endpoint '${this.data.name}'`)); + this.snackBarService.show(`Connected endpoint '${this.data.name}'`); this.dialogRef.close(); }); } diff --git a/src/frontend/packages/core/src/features/endpoints/connect-endpoint/connect-endpoint.component.ts b/src/frontend/packages/core/src/features/endpoints/connect-endpoint/connect-endpoint.component.ts index fc355c4928..0f6bc94595 100644 --- a/src/frontend/packages/core/src/features/endpoints/connect-endpoint/connect-endpoint.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/connect-endpoint/connect-endpoint.component.ts @@ -13,13 +13,12 @@ import { } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Subscription } from 'rxjs'; -import { map } from 'rxjs/operators'; import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; -import { EndpointAuthTypeConfig, IAuthForm, IEndpointAuthComponent } from '../../../core/extension/extension-types'; +import { EndpointAuthTypeConfig, IAuthForm, IEndpointAuthComponent } from '../../../../../store/src/extension-types'; +import { BaseEndpointAuth } from '../../../core/endpoint-auth'; import { safeUnsubscribe } from '../../../core/utils.service'; import { ConnectEndpointConfig, ConnectEndpointData, ConnectEndpointService } from '../connect.service'; -import { BaseEndpointAuth } from '../endpoint-auth'; @Component({ selector: 'app-connect-endpoint', diff --git a/src/frontend/packages/core/src/features/endpoints/connect.service.ts b/src/frontend/packages/core/src/features/endpoints/connect.service.ts index d4e9dac73d..2ad24a7196 100644 --- a/src/frontend/packages/core/src/features/endpoints/connect.service.ts +++ b/src/frontend/packages/core/src/features/endpoints/connect.service.ts @@ -1,4 +1,3 @@ -import { Store } from '@ngrx/store'; import { combineLatest, Observable, of, Subject, Subscription } from 'rxjs'; import { delay, @@ -13,18 +12,12 @@ import { } from 'rxjs/operators'; import { AuthParams, ConnectEndpoint } from '../../../../store/src/actions/endpoint.actions'; -import { GetSystemInfo } from '../../../../store/src/actions/system.actions'; -import { EndpointOnlyAppState } from '../../../../store/src/app-state'; -import { EndpointsEffect } from '../../../../store/src/effects/endpoint.effects'; -import { SystemEffects } from '../../../../store/src/effects/system.effects'; import { entityCatalog } from '../../../../store/src/entity-catalog/entity-catalog'; -import { endpointSchemaKey } from '../../../../store/src/helpers/entity-factory'; +import { EndpointType } from '../../../../store/src/extension-types'; import { ActionState } from '../../../../store/src/reducers/api-request-reducer/types'; -import { selectEntity, selectRequestInfo, selectUpdateInfo } from '../../../../store/src/selectors/api.selectors'; +import { stratosEntityCatalog } from '../../../../store/src/stratos-entity-catalog'; import { EndpointModel } from '../../../../store/src/types/endpoint.types'; -import { STRATOS_ENDPOINT_TYPE } from '../../base-entity-schemas'; import { EndpointsService } from '../../core/endpoints.service'; -import { EndpointType } from '../../core/extension/extension-types'; import { safeUnsubscribe } from '../../core/utils.service'; export interface ConnectEndpointConfig { @@ -65,15 +58,12 @@ export class ConnectEndpointService { private hasAttemptedConnect: boolean; private pData: ConnectEndpointData; - private endpointEntityKey = entityCatalog.getEntityKey(STRATOS_ENDPOINT_TYPE, endpointSchemaKey); - // We need a delay to ensure the BE has finished registering the endpoint. // If we don't do this and if we're quick enough, we can navigate to the application page // and end up with an empty list where we should have results. private connectDelay = 1000; constructor( - private store: Store, private endpointsService: EndpointsService, public config: ConnectEndpointConfig, ) { @@ -87,33 +77,29 @@ export class ConnectEndpointService { ).subscribe(([oldVal, newVal]) => { if (!newVal.error && (oldVal.busy && !newVal.busy)) { // Has finished fetching - this.store.dispatch(new GetSystemInfo()); + stratosEntityCatalog.endpoint.api.get(this.config.guid); } })); this.subs.push(this.connected$.pipe( - filter(([connected]) => connected), + filter(([isConnected]) => isConnected), delay(this.connectDelay), tap(() => this.hasConnected.next(true)), - distinctUntilChanged(([connected], [oldConnected]) => connected && oldConnected), - ).subscribe(([connected, endpoint]) => this.endpointsService.checkEndpoint(endpoint)) + distinctUntilChanged(([isConnected], [oldIsConnected]) => isConnected && oldIsConnected), + ).subscribe(([, endpoint]) => this.endpointsService.checkEndpoint(endpoint)) ); } private setupObservables() { - this.update$ = this.store.select( - this.getUpdateSelector() - ).pipe(filter(update => !!update)); + this.update$ = stratosEntityCatalog.endpoint.store.getEntityMonitor(this.config.guid).getUpdatingSection(ConnectEndpoint.UpdatingKey) + .pipe(filter(update => !!update)); - this.fetchingInfo$ = this.store.select( - this.getRequestSelector() - ).pipe( + this.fetchingInfo$ = stratosEntityCatalog.endpoint.store.getEntityMonitor(this.config.guid).entityRequest$.pipe( filter(request => !!request), - map(request => request.fetching)); + map(request => request.fetching) + ); - this.connected$ = this.store.select( - this.getEntitySelector() - ).pipe( + this.connected$ = stratosEntityCatalog.endpoint.store.getEntityMonitor(this.config.guid).entity$.pipe( map(endpoint => { const isConnected = !!(endpoint && endpoint.api_endpoint && endpoint.user); return [isConnected, endpoint] as [boolean, EndpointModel]; @@ -149,28 +135,6 @@ export class ConnectEndpointService { ); } - private getUpdateSelector() { - return selectUpdateInfo( - this.endpointEntityKey, - this.config.guid, - EndpointsEffect.connectingKey - ); - } - - private getRequestSelector() { - return selectRequestInfo( - this.endpointEntityKey, - SystemEffects.guid - ); - } - - private getEntitySelector() { - return selectEntity( - this.endpointEntityKey, - this.config.guid, - ); - } - public setData(data: ConnectEndpointData) { this.pData = data; } @@ -179,25 +143,21 @@ export class ConnectEndpointService { this.hasAttemptedConnect = true; const { authType, authVal, systemShared, bodyContent } = this.pData; - this.store.dispatch(new ConnectEndpoint( + return stratosEntityCatalog.endpoint.api.connect( this.config.guid, this.config.type, authType, authVal, systemShared, bodyContent, - )); - - return this.isBusy$.pipe( + ).pipe( pairwise(), - filter(([oldBusy, newBusy]) => { - return !(oldBusy === true && newBusy === false); - }), - withLatestFrom(this.update$), - map(([, updateSection]) => ({ + filter(([oldV, newV]) => oldV.busy && !newV.busy), + map(([, newV]) => newV), + map(updateSection => ({ success: !updateSection.error, errorMessage: updateSection.message - })) + })), ); } diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-cf-step-1/create-endpoint-cf-step-1.component.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-cf-step-1/create-endpoint-cf-step-1.component.ts index a046dc6b62..96bca56f21 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-cf-step-1/create-endpoint-cf-step-1.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-cf-step-1/create-endpoint-cf-step-1.component.ts @@ -1,27 +1,21 @@ import { AfterContentInit, Component, Input, ViewChild } from '@angular/core'; import { NgForm, NgModel } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; -import { Store } from '@ngrx/store'; -import { denormalize } from 'normalizr'; import { Observable } from 'rxjs'; -import { filter, map, pairwise, withLatestFrom } from 'rxjs/operators'; +import { filter, map, pairwise } from 'rxjs/operators'; -import { GetAllEndpoints, RegisterEndpoint } from '../../../../../../store/src/actions/endpoint.actions'; -import { ShowSnackBar } from '../../../../../../store/src/actions/snackBar.actions'; -import { GeneralEntityAppState } from '../../../../../../store/src/app-state'; -import { EndpointsEffect } from '../../../../../../store/src/effects/endpoint.effects'; +import { getFullEndpointApiUrl } from '../../../../../../store/src/endpoint-utils'; import { entityCatalog } from '../../../../../../store/src/entity-catalog/entity-catalog'; import { StratosCatalogEndpointEntity, } from '../../../../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; -import { endpointSchemaKey } from '../../../../../../store/src/helpers/entity-factory'; -import { getAPIRequestDataState, selectUpdateInfo } from '../../../../../../store/src/selectors/api.selectors'; -import { selectPaginationState } from '../../../../../../store/src/selectors/pagination.selectors'; -import { endpointEntitySchema, STRATOS_ENDPOINT_TYPE } from '../../../../base-entity-schemas'; +import { ActionState } from '../../../../../../store/src/reducers/api-request-reducer/types'; +import { stratosEntityCatalog } from '../../../../../../store/src/stratos-entity-catalog'; import { getIdFromRoute } from '../../../../core/utils.service'; import { IStepperStep, StepOnNextFunction } from '../../../../shared/components/stepper/step/step.component'; +import { SnackBarService } from '../../../../shared/services/snackbar.service'; import { ConnectEndpointConfig } from '../../connect.service'; -import { getFullEndpointApiUrl, getSSOClientRedirectURI } from '../../endpoint-helpers'; +import { getSSOClientRedirectURI } from '../../endpoint-helpers'; /* tslint:disable:no-access-missing-member https://github.com/mgechev/codelyzer/issues/191*/ @Component({ @@ -58,23 +52,16 @@ export class CreateEndpointCfStep1Component implements IStepperStep, AfterConten endpointTypeSupportsSSO = false; endpoint: StratosCatalogEndpointEntity; - private endpointEntityKey = entityCatalog.getEntityKey(STRATOS_ENDPOINT_TYPE, endpointSchemaKey); - - constructor(private store: Store, activatedRoute: ActivatedRoute, ) { - - this.existingEndpoints = store.select(selectPaginationState(this.endpointEntityKey, GetAllEndpoints.storeKey)) - .pipe( - withLatestFrom(store.select(getAPIRequestDataState)), - map(([pagination, entities]) => { - const pages = Object.values(pagination.ids); - const page = [].concat.apply([], pages); - const endpoints = page.length ? denormalize(page, [endpointEntitySchema], entities) : []; - return { - names: endpoints.map(ep => ep.name), - urls: endpoints.map(ep => getFullEndpointApiUrl(ep)), - }; - }) - ); + constructor( + activatedRoute: ActivatedRoute, + private snackBarService: SnackBarService + ) { + this.existingEndpoints = stratosEntityCatalog.endpoint.store.getAll.getPaginationMonitor().currentPage$.pipe( + map(endpoints => ({ + names: endpoints.map(ep => ep.name), + urls: endpoints.map(ep => getFullEndpointApiUrl(ep)), + })) + ); const epType = getIdFromRoute(activatedRoute, 'type'); const epSubType = getIdFromRoute(activatedRoute, 'subtype'); @@ -87,7 +74,7 @@ export class CreateEndpointCfStep1Component implements IStepperStep, AfterConten onNext: StepOnNextFunction = () => { const { subType, type } = this.endpoint.getTypeAndSubtype(); - const action = new RegisterEndpoint( + return stratosEntityCatalog.endpoint.api.register( type, subType, this.nameField.value, @@ -96,15 +83,8 @@ export class CreateEndpointCfStep1Component implements IStepperStep, AfterConten this.clientIDField ? this.clientIDField.value : '', this.clientSecretField ? this.clientSecretField.value : '', this.ssoAllowedField ? !!this.ssoAllowedField.value : false, - ); - - this.store.dispatch(action); - - const update$ = this.store.select( - this.getUpdateSelector(action.guid()) - ).pipe(filter(update => !!update)); - - return update$.pipe(pairwise(), + ).pipe( + pairwise(), filter(([oldVal, newVal]) => (oldVal.busy && !newVal.busy)), map(([oldVal, newVal]) => newVal), map(result => { @@ -116,7 +96,7 @@ export class CreateEndpointCfStep1Component implements IStepperStep, AfterConten ssoAllowed: this.ssoAllowedField ? !!this.ssoAllowedField.value : false }; if (!result.error) { - this.store.dispatch(new ShowSnackBar(`Successfully registered '${this.nameField.value}'`)); + this.snackBarService.show(`Successfully registered '${this.nameField.value}'`); } const success = !result.error; return { @@ -129,13 +109,6 @@ export class CreateEndpointCfStep1Component implements IStepperStep, AfterConten ); } - private getUpdateSelector(guid) { - return selectUpdateInfo( - this.endpointEntityKey, - guid, - EndpointsEffect.registeringKey, - ); - } ngAfterContentInit() { this.validate = this.form.statusChanges.pipe( diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts index 9fc5b74dee..7be71e52aa 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts @@ -1,9 +1,7 @@ import { Component, OnDestroy } from '@angular/core'; -import { Store } from '@ngrx/store'; import { Observable, of } from 'rxjs'; import { map } from 'rxjs/operators'; -import { EndpointOnlyAppState } from '../../../../../../store/src/app-state'; import { EndpointsService } from '../../../../core/endpoints.service'; import { MarkdownPreviewComponent } from '../../../../shared/components/markdown-preview/markdown-preview.component'; import { IStepperStep, StepOnNextResult } from '../../../../shared/components/stepper/step/step.component'; @@ -26,7 +24,6 @@ export class CreateEndpointConnectComponent implements OnDestroy, IStepperStep { public doConnect = false; constructor( - private store: Store, private endpointsService: EndpointsService, private sidePanelService: SidePanelService, ) { @@ -37,7 +34,7 @@ export class CreateEndpointConnectComponent implements OnDestroy, IStepperStep { } onEnter = (data: ConnectEndpointConfig) => { - this.connectService = new ConnectEndpointService(this.store, this.endpointsService, data); + this.connectService = new ConnectEndpointService(this.endpointsService, data); } onNext = (): Observable => this.doConnect ? this.connectService.submit().pipe( diff --git a/src/frontend/packages/core/src/features/endpoints/edit-endpoint/edit-endpoint-step/edit-endpoint-step.component.ts b/src/frontend/packages/core/src/features/endpoints/edit-endpoint/edit-endpoint-step/edit-endpoint-step.component.ts index 1376e3e6b7..cb6120b12e 100644 --- a/src/frontend/packages/core/src/features/endpoints/edit-endpoint/edit-endpoint-step/edit-endpoint-step.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/edit-endpoint/edit-endpoint-step/edit-endpoint-step.component.ts @@ -1,21 +1,19 @@ import { Component, OnDestroy } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; -import { Store } from '@ngrx/store'; import { Observable, Subscription } from 'rxjs'; import { filter, first, map, pairwise, switchMap } from 'rxjs/operators'; -import { AppState } from '../../../../../../store/src/app-state'; +import { getFullEndpointApiUrl } from '../../../../../../store/src/endpoint-utils'; import { entityCatalog } from '../../../../../../store/src/entity-catalog/entity-catalog'; -import { selectUpdateInfo } from '../../../../../../store/src/selectors/api.selectors'; +import { ActionState } from '../../../../../../store/src/reducers/api-request-reducer/types'; +import { stratosEntityCatalog } from '../../../../../../store/src/stratos-entity-catalog'; import { StepOnNextFunction } from '../../../../shared/components/stepper/step/step.component'; -import { getFullEndpointApiUrl, getSSOClientRedirectURI } from '../../endpoint-helpers'; -import { UpdateEndpoint } from './../../../../../../store/src/actions/endpoint.actions'; +import { getSSOClientRedirectURI } from '../../endpoint-helpers'; import { EntityCatalogSchemas, IStratosEndpointDefinition, } from './../../../../../../store/src/entity-catalog/entity-catalog.types'; -import { endpointEntitiesSelector } from './../../../../../../store/src/selectors/endpoint.selectors'; import { EndpointModel } from './../../../../../../store/src/types/endpoint.types'; import { getIdFromRoute, safeUnsubscribe } from './../../../../core/utils.service'; import { IStepperStep } from './../../../../shared/components/stepper/step/step.component'; @@ -41,12 +39,11 @@ export class EditEndpointStepComponent implements OnDestroy, IStepperStep { existingEndpoints: Observable; endpoint$: Observable; definition$: Observable>; - existingEndpoinNames$: Observable; + existingEndpointNames$: Observable; formChangeSub: Subscription; setClientInfo = false; constructor( - private store: Store, activatedRoute: ActivatedRoute, ) { this.editEndpoint = new FormGroup({ @@ -65,9 +62,14 @@ export class EditEndpointStepComponent implements OnDestroy, IStepperStep { this.endpointID = getIdFromRoute(activatedRoute, 'id'); - this.existingEndpoints = this.store.select(endpointEntitiesSelector); + this.existingEndpoints = stratosEntityCatalog.endpoint.store.getAll.getPaginationMonitor().currentPage$.pipe( + map(endpoints => endpoints.reduce((res, endpoint) => { + res[endpoint.guid] = endpoint; + return res; + }, {})) + ); - this.existingEndpoinNames$ = this.existingEndpoints.pipe( + this.existingEndpointNames$ = this.existingEndpoints.pipe( map(endpoints => Object.values(endpoints).filter((ep: EndpointModel) => ep.guid !== this.endpointID)), map((endpoints: EndpointModel[]) => endpoints.map(ep => ep.name)) ); @@ -127,20 +129,19 @@ export class EditEndpointStepComponent implements OnDestroy, IStepperStep { return this.endpoint$.pipe( first(), switchMap(endpoint => { - const action = new UpdateEndpoint( - endpoint.cnsi_type, + return stratosEntityCatalog.endpoint.api.update( this.endpointID, - this.editEndpoint.value.name, - this.editEndpoint.value.skipSSL, - this.editEndpoint.value.setClientInfo, - this.editEndpoint.value.clientID, - this.editEndpoint.value.clientSecret, - this.editEndpoint.value.allowSSO, - ); - - this.store.dispatch(action); - - return this.store.select(selectUpdateInfo('stratosEndpoint', this.endpointID, 'updating')).pipe( + this.endpointID, { + endpointType: endpoint.cnsi_type, + id: this.endpointID, + name: this.editEndpoint.value.name, + skipSSL: this.editEndpoint.value.skipSSL, + setClientInfo: this.editEndpoint.value.setClientInfo, + clientID: this.editEndpoint.value.clientID, + clientSecret: this.editEndpoint.value.clientSecret, + allowSSO: this.editEndpoint.value.allowSSO, + } + ).pipe( pairwise(), filter(([oldV, newV]) => oldV.busy && !newV.busy), map(([, newV]) => newV), diff --git a/src/frontend/packages/core/src/features/endpoints/endpoint-helpers.ts b/src/frontend/packages/core/src/features/endpoints/endpoint-helpers.ts index 39d567fe8b..10b9a13fff 100644 --- a/src/frontend/packages/core/src/features/endpoints/endpoint-helpers.ts +++ b/src/frontend/packages/core/src/features/endpoints/endpoint-helpers.ts @@ -11,10 +11,6 @@ import { import { EndpointModel } from '../../../../store/src/types/endpoint.types'; import { EndpointListDetailsComponent } from '../../shared/components/list/list-types/endpoint/endpoint-list.helpers'; -export function getFullEndpointApiUrl(endpoint: EndpointModel) { - return endpoint && endpoint.api_endpoint ? - `${endpoint.api_endpoint.Scheme}://${endpoint.api_endpoint.Host}${endpoint.api_endpoint.Path}` : 'Unknown'; -} export function getEndpointUsername(endpoint: EndpointModel) { return endpoint && endpoint.user ? endpoint.user.name : '-'; @@ -27,12 +23,6 @@ export interface EndpointIcon { font: string; } -export enum EndpointAuthTypeNames { - CREDS = 'creds', - SSO = 'sso', - NONE = 'none' -} - // Any initial endpointTypes listDetailsComponent should be added here export const coreEndpointListDetailsComponents: Type[] = []; diff --git a/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.ts b/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.ts index 7992db8c39..9ecf211ded 100644 --- a/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.ts @@ -10,7 +10,6 @@ import { ViewChild, ViewContainerRef, } from '@angular/core'; -import { MatSnackBar, MatSnackBarRef, SimpleSnackBar } from '@angular/material/snack-bar'; import { Store } from '@ngrx/store'; import { combineLatest, Subscription } from 'rxjs'; import { delay, first, map, tap } from 'rxjs/operators'; @@ -32,6 +31,7 @@ import { EndpointsListConfigService, } from '../../../shared/components/list/list-types/endpoint/endpoints-list-config.service'; import { ListConfig } from '../../../shared/components/list/list.component.types'; +import { SnackBarService } from '../../../shared/services/snackbar.service'; @Component({ selector: 'app-endpoints-page', @@ -49,7 +49,6 @@ export class EndpointsPageComponent implements AfterViewInit, OnDestroy, OnInit @ViewChild('customNoEndpoints', { read: ViewContainerRef, static: true }) customNoEndpointsContainer; customContentComponentRef: ComponentRef; - private snackBarRef: MatSnackBarRef; private snackBarText = { message: `There are no connected endpoints, connect with your personal credentials to get started.`, action: 'Got it' @@ -62,7 +61,7 @@ export class EndpointsPageComponent implements AfterViewInit, OnDestroy, OnInit public store: Store, private ngZone: NgZone, private resolver: ComponentFactoryResolver, - private snackBar: MatSnackBar, + private snackBarService: SnackBarService, cs: CustomizationService ) { this.customizations = cs.get(); @@ -103,10 +102,10 @@ export class EndpointsPageComponent implements AfterViewInit, OnDestroy, OnInit } private showSnackBar(show: boolean) { - if (!this.snackBarRef && show) { - this.snackBarRef = this.snackBar.open(this.snackBarText.message, this.snackBarText.action, { duration: 20000 }); - } else if (this.snackBarRef && !show) { - this.snackBarRef.dismiss(); + if (show) { + this.snackBarService.show(this.snackBarText.message, this.snackBarText.action, 20000); + } else { + this.snackBarService.hide(); } } diff --git a/src/frontend/packages/core/src/features/error-page/error-page/error-page.component.ts b/src/frontend/packages/core/src/features/error-page/error-page/error-page.component.ts index 9b6ab439b4..a404433a5e 100644 --- a/src/frontend/packages/core/src/features/error-page/error-page/error-page.component.ts +++ b/src/frontend/packages/core/src/features/error-page/error-page/error-page.component.ts @@ -7,14 +7,13 @@ import { first, map, withLatestFrom } from 'rxjs/operators'; import { SendClearEndpointEventsAction } from '../../../../../store/src/actions/internal-events.actions'; import { AppState } from '../../../../../store/src/app-state'; -import { endpointSchemaKey } from '../../../../../store/src/helpers/entity-factory'; -import { EntityMonitor } from '../../../../../store/src/monitors/entity-monitor'; +import { endpointEntityType } from '../../../../../store/src/helpers/stratos-entity-factory'; import { InternalEventMonitorFactory } from '../../../../../store/src/monitors/internal-event-monitor.factory'; +import { stratosEntityCatalog } from '../../../../../store/src/stratos-entity-catalog'; import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; import { InternalEventState } from '../../../../../store/src/types/internal-events.types'; import { getPreviousRoutingState } from '../../../../../store/src/types/routing.type'; -import { endpointEntitySchema } from '../../../base-entity-schemas'; -import { StratosStatus } from '../../../shared/shared.types'; +import { StratosStatus } from '../../../../../store/src/types/shared.types'; import { eventReturnUrlParam } from '../../event-page/events-page/events-page.component'; @Component({ @@ -36,13 +35,8 @@ export class ErrorPageComponent implements OnInit { ngOnInit() { const endpointId = this.activatedRoute.snapshot.params.endpointId; if (endpointId) { - const endpointMonitor = new EntityMonitor( - this.store, - endpointId, - endpointEntitySchema.key, - endpointEntitySchema - ); - const cfEndpointEventMonitor = this.internalEventMonitorFactory.getMonitor(endpointSchemaKey, of([endpointId])); + const endpointMonitor = stratosEntityCatalog.endpoint.store.getEntityMonitor(endpointId); + const cfEndpointEventMonitor = this.internalEventMonitorFactory.getMonitor(endpointEntityType, of([endpointId])); this.errorDetails$ = cfEndpointEventMonitor.hasErroredOverTimeNoPoll(30).pipe( withLatestFrom(endpointMonitor.entity$), map(([errors, endpoint]) => { diff --git a/src/frontend/packages/core/src/features/home/home/home-page.component.ts b/src/frontend/packages/core/src/features/home/home/home-page.component.ts index 1c3a977d31..6700b83fa3 100644 --- a/src/frontend/packages/core/src/features/home/home/home-page.component.ts +++ b/src/frontend/packages/core/src/features/home/home/home-page.component.ts @@ -5,12 +5,12 @@ import { first, map } from 'rxjs/operators'; import { RouterNav } from '../../../../../store/src/actions/router.actions'; import { AppState, IRequestEntityTypeState } from '../../../../../store/src/app-state'; +import { EntityCatalogHelpers } from '../../../../../store/src/entity-catalog/entity-catalog.helper'; import { IUserFavoritesGroups } from '../../../../../store/src/types/favorite-groups.types'; import { UserFavorite } from '../../../../../store/src/types/user-favorites.types'; +import { UserFavoriteManager } from '../../../../../store/src/user-favorite-manager'; import { EndpointsService } from '../../../core/endpoints.service'; import { LoggerService } from '../../../core/logger.service'; -import { UserFavoriteManager } from '../../../core/user-favorite-manager'; -import { EntityCatalogHelpers } from '../../../../../store/src/entity-catalog/entity-catalog.helper'; @Component({ selector: 'app-home-page', diff --git a/src/frontend/packages/core/src/features/metrics/metrics.helpers.ts b/src/frontend/packages/core/src/features/metrics/metrics.helpers.ts index fc31d4b577..72dfa2d0c1 100644 --- a/src/frontend/packages/core/src/features/metrics/metrics.helpers.ts +++ b/src/frontend/packages/core/src/features/metrics/metrics.helpers.ts @@ -1,7 +1,8 @@ import { Observable, of as observableOf } from 'rxjs'; -import { StratosStatus } from '../../shared/shared.types'; -import { EndpointIcon, getFullEndpointApiUrl } from '../endpoints/endpoint-helpers'; +import { getFullEndpointApiUrl } from '../../../../store/src/endpoint-utils'; +import { StratosStatus } from '../../../../store/src/types/shared.types'; +import { EndpointIcon } from '../endpoints/endpoint-helpers'; import { entityCatalog } from './../../../../store/src/entity-catalog/entity-catalog'; import { MetricsEndpointProvider } from './services/metrics-service'; diff --git a/src/frontend/packages/core/src/features/metrics/services/metrics-service.ts b/src/frontend/packages/core/src/features/metrics/services/metrics-service.ts index 9e9076acea..e9391d7fae 100644 --- a/src/frontend/packages/core/src/features/metrics/services/metrics-service.ts +++ b/src/frontend/packages/core/src/features/metrics/services/metrics-service.ts @@ -2,12 +2,11 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { map, publishReplay, refCount } from 'rxjs/operators'; +import { getFullEndpointApiUrl } from '../../../../../store/src/endpoint-utils'; import { PaginationMonitor } from '../../../../../store/src/monitors/pagination-monitor'; -import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; +import { stratosEntityCatalog } from '../../../../../store/src/stratos-entity-catalog'; import { APIResource, EntityInfo } from '../../../../../store/src/types/api.types'; -import { endpointListKey, EndpointModel } from '../../../../../store/src/types/endpoint.types'; -import { endpointEntitySchema } from '../../../base-entity-schemas'; -import { getFullEndpointApiUrl } from '../../endpoints/endpoint-helpers'; +import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; export interface MetricsEndpointProvider { provider: EndpointModel; @@ -22,14 +21,8 @@ export class MetricsService { haveNoMetricsEndpoints$: Observable; haveNoConnectedMetricsEndpoints$: Observable; - constructor( - private paginationMonitorFactory: PaginationMonitorFactory - ) { - this.endpointsMonitor = this.paginationMonitorFactory.create( - endpointListKey, - endpointEntitySchema, - true - ); + constructor() { + this.endpointsMonitor = stratosEntityCatalog.endpoint.store.getPaginationMonitor() this.setupObservables(); } diff --git a/src/frontend/packages/core/src/features/setup/setup.module.ts b/src/frontend/packages/core/src/features/setup/setup.module.ts index cd9593967a..c730116607 100644 --- a/src/frontend/packages/core/src/features/setup/setup.module.ts +++ b/src/frontend/packages/core/src/features/setup/setup.module.ts @@ -1,7 +1,7 @@ import { NgModule } from '@angular/core'; +import { ThemeService } from '../../../../store/src/theme.service'; import { CoreModule } from '../../core/core.module'; -import { ThemeService } from '../../core/theme.service'; import { SharedModule } from '../../shared/shared.module'; import { DomainMismatchComponent } from './domain-mismatch/domain-mismatch.component'; import { LocalAccountWizardComponent } from './local-account-wizard/local-account-wizard.component'; diff --git a/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.ts b/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.ts index 54194241ee..75aa2b69bf 100644 --- a/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.ts +++ b/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.ts @@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { AbstractControl, FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms'; import { ErrorStateMatcher, ShowOnDirtyErrorStateMatcher } from '@angular/material/core'; import { Subscription } from 'rxjs'; -import { first, map, take, tap } from 'rxjs/operators'; +import { delay, first, map, take, tap } from 'rxjs/operators'; import { UserProfileInfo, UserProfileInfoUpdates } from '../../../../../store/src/types/user-profile.types'; import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; @@ -119,8 +119,7 @@ export class EditProfileInfoComponent implements OnInit, OnDestroy { updates[key] = this.editProfileForm.value[key]; } } - const obs$ = this.userProfileService.updateProfile(this.profile, updates); - return obs$.pipe( + return this.userProfileService.updateProfile(this.profile, updates).pipe( take(1), map(([profileResult, passwordResult]) => { const okay = !profileResult.error && !passwordResult.error; @@ -131,6 +130,7 @@ export class EditProfileInfoComponent implements OnInit, OnDestroy { message: okay ? '' : `An error occurred whilst updating your profile: ${message}` }; }), + delay(300), // Ensure that the profile is updated before fetching to refresh local copy tap(() => this.userProfileService.fetchUserProfile()) ); } diff --git a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.ts b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.ts index d5984a3886..915ecd6aea 100644 --- a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.ts +++ b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.ts @@ -6,8 +6,8 @@ import { map } from 'rxjs/operators'; import { SetPollingEnabledAction, SetSessionTimeoutAction } from '../../../../../store/src/actions/dashboard-actions'; import { DashboardOnlyAppState } from '../../../../../store/src/app-state'; import { selectDashboardState } from '../../../../../store/src/selectors/dashboard.selectors'; +import { ThemeService } from '../../../../../store/src/theme.service'; import { UserProfileInfo } from '../../../../../store/src/types/user-profile.types'; -import { ThemeService } from '../../../core/theme.service'; import { UserProfileService } from '../../../core/user-profile.service'; import { UserService } from '../../../core/user.service'; import { SetGravatarEnabledAction } from './../../../../../store/src/actions/dashboard-actions'; diff --git a/src/frontend/packages/core/misc/custom/index.html b/src/frontend/packages/core/src/index.html similarity index 95% rename from src/frontend/packages/core/misc/custom/index.html rename to src/frontend/packages/core/src/index.html index b6c415d27c..5756ba3e60 100644 --- a/src/frontend/packages/core/misc/custom/index.html +++ b/src/frontend/packages/core/src/index.html @@ -11,7 +11,7 @@ - +
\ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss b/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss index 8ea9135bb9..62ccddf629 100644 --- a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss @@ -1,9 +1,9 @@ -@import '../../../../suse-theme/sass/custom/suse-colors'; +@import '../../../../suse-theme/sass/colors'; .suse-login { align-items: center; - background-color: $suse-blue; - border-bottom: 10px solid $suse-primary; + background-color: $suse-gray; + color: $suse-gray-fg; display: flex; flex-direction: column; width: 100%; @@ -16,7 +16,7 @@ flex-direction: row; } &__header { - background-color: $suse-secondary; + background-color: $suse-dark-green; color: $suse-text; flex: 0 0 60px; font-size: 24px; @@ -38,19 +38,21 @@ padding: 20px 0; } &__title { + font-family: "Work Sans"; width: 90%; } &__copyright { - color: $suse-text-gray; + color: $suse-gray-fg; flex: 1; } &__logo { img { - width: 79px; + width: 128px; } } &__intro { flex: 1; + font-family: "Work Sans"; margin-right: 24px; } &__box { @@ -64,11 +66,12 @@ display: flex; flex-direction: column; .mat-form-field:not(.mat-form-field-invalid).mat-form-field-appearance-legacy { + color: $suse-gray-fg; .mat-form-field-label { - color: $suse-text-gray; + color: $suse-gray-fg; } .mat-form-field-underline { - background-color: $suse-text-gray; + background-color: $suse-gray-fg; } } } @@ -79,13 +82,15 @@ margin-top: 24px; } &__headline { - color: $suse-primary; + color: $suse-primary-color; + font-family: "Work Sans"; font-size: 52px; font-weight: 300; margin: 0; } &__tagline { - color: $suse-text-gray; + color: $suse-gray-fg; + font-family: "Work Sans"; font-size: 18px; font-weight: 300; line-height: 1.5; @@ -97,7 +102,7 @@ transition: opacity 250ms linear; } &__form-title { - color: $suse-text; + color: $suse-gray-fg; font-size: 30px; font-weight: 300; margin-bottom: 24px; diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-welcome/suse-welcome.component.scss b/src/frontend/packages/suse-extensions/src/custom/suse-welcome/suse-welcome.component.scss index 21b16098cb..c0e5cadd87 100644 --- a/src/frontend/packages/suse-extensions/src/custom/suse-welcome/suse-welcome.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/suse-welcome/suse-welcome.component.scss @@ -1,4 +1,4 @@ -@import '../../../../suse-theme/sass/custom/suse-colors'; +@import '../../../../suse-theme/sass/colors'; .no-endpoints-message { color: $suse-button-gray; @@ -22,7 +22,7 @@ .messages { &__title { - color: $suse-primary; + color: $suse-primary-color; font-size: 20px; font-weight: normal; margin: 20px 0; diff --git a/src/frontend/packages/suse-theme/_index.scss b/src/frontend/packages/suse-theme/_index.scss index a8d3f300e1..55a2786697 100644 --- a/src/frontend/packages/suse-theme/_index.scss +++ b/src/frontend/packages/suse-theme/_index.scss @@ -1,9 +1,41 @@ @import '~@stratosui/theme/helper'; -// Custom Theme -@import './sass/custom'; +@import './sass/colors'; + +// SUSE overrides +@import './sass/suse'; @function stratos-theme() { $theme: stratos-theme-helper($stratos-theme); + + // Modify some of the colors + $app-theme: map-get($theme, app-theme); + $app-theme: map-merge($app-theme, ( + header-background-color: $suse-header-bar, + header-foreground-color: $suse-text, + header-background-span: true, + link-color: $suse-link-color, + link-active-color: darken($suse-link-color, 10%), + stratos-title-show-text: true, + underflow-background-color: $suse-primary-color, + underflow-foreground-color: $suse-text, + user-avatar-background-color: $suse-primary-color, + user-avatar-foreground-color: $suse-text, + user-avatar-header-invert-colors: false, + intro-screen-background-color: $suse-blue, + side-nav: ( + background: $suse-side-nav-bg, + text: $suse-side-nav-text, + active: $suse-side-nav-active-bg, + active-text: $suse-side-nav-active-text, + hover: $suse-side-nav-active-bg, + hover-text: $suse-side-nav-active-text + ) + )); + + $theme: map-merge($theme, ( + app-theme: $app-theme + )); + @return $theme } \ No newline at end of file diff --git a/src/frontend/packages/suse-theme/assets/core/custom/suse_logo.png b/src/frontend/packages/suse-theme/assets/core/custom/suse_logo.png deleted file mode 100644 index d4dfe1e1145b8120839b7f8bf2a94f462551211e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1504 zcmV<61t0o}P)cZh&k2wvhkwqY_FBNO}$tXmJYks7{Uo{SD4F?!)L>`^d| zVlWE8ALpR-1xGOwMe(z{55OD6u@jC?hc9M^w+Bt}ySojd6fQw4eZ*nhfeyXk8`X!9 z9($mrmLn0WAtx#z5Tm2&5PrmHghNZ+Ml!s`Cp^XgbVev*N3}tu#8qgqvFMM;s^Jg3 zLSjVLKPnBP1U^HnO+_a}7J(oHlcJ)CN zM+mf7K~%&wM0OOZkQD!-8zP|8(v=611UhK7t0)0q)WC7!SrP)J_vi>eR~>{8j>8qY zjAr-~iO~pSFcMYp4;mxAs}7?XjP(-7a02(Cm4dMm?NJdeu>fxnguh*J5CM1!xpl~F z;Q0U07WbgVZljp1J}YWKu8lOT49JJL!tFm`fJQzLe#J&qf{!b|aj$^bY~g}n59lCv z0p+AKu&=OpOOY3uQ4$T%6(cbmeNZ1c5D-;9SKNitSNx^FeF981R9K^knP%h;itdd7uO&+)`C53g4h{%9mZ!U?G`SHC=G%i;=x<*9Fz(OYgK|=Z4360 z03lF{KwNhm#t|re!f*Ij8d3xz8>PR`Q2DZh@a%XFv6>d{V+O<~y5lg~L9CN-${P%^ z*#?HuR$8w&E(n@ixQ}=^FWm18V!7Ke{={o21tO<#y=pLKpfP?z0B%F6n3pHJ4x}l> z0?`G(NXPwyrBHeff9aUtkQN23@T2~|X{6iJSonTZK#;@8alav*ihUVc%O5F_*UI(& zwR9lUAQyzA*norh3bBU5{ZnHDLSStP^lNOe_7s<620-kA^zUh~1)*y9B|R7?K?gOJ zwB|=}`XdaMG*0|xP#lz2hQXgpBhbcuiO;|Xu%$R^eiCFgLSU%ds3sgA3m-w+7=~~{ z@YzoL^1*4?(i~gfNB%%Dv_c2eK{|L#$BluZbXJJxfMHCuv5&tY7F*j>a?xv%jM3qv6-o{2p)^mNp~FkV7zh7J2#4Y4a)JuN;9u@}go zfni*R*i)pp)4ncfbr_>zsK?lg8R(31NPz$+4P!hE^$2@09UW0t#l9{Gr@9F&;OFJ& zRGzhj<2uG8kpqX32w$z-*AJCHs1i%Y%Or0!@e3rE(c~oOou diff --git a/src/frontend/packages/suse-theme/assets/core/custom/suse_logo.svg b/src/frontend/packages/suse-theme/assets/core/custom/suse_logo.svg new file mode 100644 index 0000000000..140888bd1a --- /dev/null +++ b/src/frontend/packages/suse-theme/assets/core/custom/suse_logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/frontend/packages/suse-theme/assets/core/custom/susecon-logo.svg b/src/frontend/packages/suse-theme/assets/core/custom/susecon-logo.svg deleted file mode 100644 index 056b4ead7d..0000000000 --- a/src/frontend/packages/suse-theme/assets/core/custom/susecon-logo.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/frontend/packages/suse-theme/assets/core/logo.png b/src/frontend/packages/suse-theme/assets/core/logo.png index 7aa8caef5347fa596bd3563e05d0fb0cc65aba66..3196befd97523c5238578d7f28449421e8852794 100644 GIT binary patch literal 8263 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

E3>UFlOYJ&J@t^e;GQ+b6e9zel`A?YQm2e)ZPj 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 g{vs{w>iS49ElRFnUz@TO`|}~ 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 literal 1475 zcmdUv`8yK~0LSNijfQQK=`A-I+9PkPF-Nmuk{Qd za?{~$N>-Seve)q_y-Xy@nd{xZ@qVA@`~Cd-{ppkL<>?Ai)l~%m03f^@)?3CLS&dZ` zWJ}~b&&u$~%Y%fIarb|czPhSn+1=yO-kv^yuS)tGbT_D~fHD51wdOkNLuaIp#z#?q zh<#}Z`P9mE&xwk>E2fLxPqGm<8GiCF;=Rpj7B{P|vd@qJ0L5%P_5{hF|Fuw3z;@EY zA^v0@Nf%TlV$|HlF<*Y{k_+Grr_@5BMz26XE3JAS-SD0JI`Zd0!cI{CAj3>$BJR6O zfTcwnav12Pv(dxCHfWRA z^4#I+%b~STQ}}l-SxQxtIJh)AifU;ov8I;|KE}9%;4MNSt+ysZeo?et%ze9G%wtR9 zVoSNgC_i3Mcn7)}oB(ADxGt9y@yF<`b6x0iLfE3oQMqlxW=L;b>;ficpIgj~VV46eluJ3WAevHcBxMl-{GMwTw}Kfu4Q0t`Nsu zFWWpUrac{{?3}Nf%A!SqEwwYj>_W}+UOK5nCdEBJrh>1hA5u=ajl-`#mm~PPBMb+g z@VhoV({i&KL#Lk-_OF;2o1pX=^JTF?1dmH=y(;-upOtADX)3nQM$V5=zQv9dDidB! zL=^YL!eDFrb(NuKbizNmY8N|Rg&l@IMp-f)%DA8OZEuncUw z!d83e9R*ewbq#OA?=ohES76SO`9*84<27T)g1Z-(GvOu;_uC)k$4}baZ1$1dx-gEQ z`Z3p`JO{4Sb}5l65`6@j-)IZ<5hFYQ?R#%F>uwCcvsL3a3qFOoJ4G$a)v3HIq=geZ zX7^e{I6;HoAUq1Ry1NL~-qw}gLHe*^-F$;x5fF7OY!V`ugk4s@|En|<#i|kgfAjlH$PPG7kO}sHOpn`bm zqZnv@UzO-ulQWtvTxdSfpr>~hknT0&Kb``~>x9jj$_L+D90cM%%q=&PYc-jYY;tXU zUbI=)?QM-iiqHDY{^HZMuK27&tXODYH9Owqt?1M!M-tWaYjDc8l0AC8bXl4QC-nbB(hC!GC`~NSQctx@7q3p>8`1q=XC1 zBApJ!#5?Q>R-^BD8bRuaASUh5X7U!*Beaa7>~ydP`(|UB9EQ1e=60_{fujg>T0P<6 zM##mM1in<(eJ!5b0jY*N=C38`s~E6+`;~Yx>28Z@v+)?Lw#8m>iTQjPDxn~=t5~v@ zl)BX7|H~BGV^vf)YCFvR!r0WGD2Y2hcCyY)vvPpt{E4zIGm4V>Z+Ugew8tZkVZR@d P=?#F#d17lZfyw^?5q=K) diff --git a/src/frontend/packages/suse-theme/assets/favicon.ico b/src/frontend/packages/suse-theme/assets/favicon.ico index 60f452c054d1f20e09e6134830fa80a90dec97f8..d8c1be09e70e446b543c259b242d26a3f462e4fa 100644 GIT binary patch literal 1150 zcmZQzU<5(|0R|wcz>vYhz#zuJz@P!dKp~(AL>x#lFaYInf&c&i*8wrHpbps_Vl|y zzyJUK|N8p_&Id8R{CW@817iRA`wOVz-~VZ+;y~R0_4};;Z@Jk6W`Fzr5v=a*x5r>U zj5zvm+5fdy8~-1BwEX}2Yc2n80Q~^cbL8O?u=*({qyJAn5ea5D?zjDa`soIs+7Q-E?`ak_t0$zQUyA8p5 z=A23Yzvu1@m^c2{?X|+I4`v652HDej*bC@R)UX8krRRu0hB>VVo&RsW*$a%@&tSP# zS8D(N`}Y@a_W(2CX^;$efb$U<`oLPy@*pYhfal)_>1m+)!km!eWQQD97M~f_57b)t84e|nGO)W9aIk7Oqv<4|| zZG+4(TNj&w!w=APL%S7{S{dej9?}SB6``ef$lL26)t$$VYq{8XaUZtVo`k07F3LKt2WKv-Fe<@+d#2en9O%*L*!GyFXL!TK%tfWqVVLD5!+SKl0z7xR8E zdR=dDZrFenvm9&NPC(Rh1naqe#PyLV!Twp+Q}3Jm9CjQxlp|G>3sHR*5^hQ%(jSA! zm;;gSDAMo9G4kURX~X(AB!^;R+U?a1Jw{j{pDw diff --git a/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-300.eot b/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-300.eot new file mode 100644 index 0000000000000000000000000000000000000000..f42fa58c9cf7f3d1614302a661cc764292da6475 GIT binary patch literal 29376 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 literal 0 HcmV?d00001 diff --git a/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-300.svg b/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-300.svg new file mode 100644 index 0000000000..dbd591fe80 --- /dev/null +++ b/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-300.svg @@ -0,0 +1,334 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-300.ttf b/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-300.ttf new file mode 100644 index 0000000000000000000000000000000000000000..5fdcd056b7d71a0b968b0dd9f7ce0ed2805795e0 GIT binary patch literal 57264 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 RAELRWd{S2B3YRnD`(NurjT!&| literal 0 HcmV?d00001 diff --git a/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-300.woff b/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-300.woff new file mode 100644 index 0000000000000000000000000000000000000000..cf6e7ce85261a3d8d35c19404bb083f2384befa8 GIT binary patch literal 30008 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

r~(1q}|qH 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 Jnl*L+006XGE6@M{ literal 0 HcmV?d00001 diff --git a/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-300.woff2 b/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-300.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..c44811168b1cc6d5953c4b850c7a1e114953da09 GIT binary patch literal 24760 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 Rp1oN41BnMQa!RB}FrXi<>I47) literal 0 HcmV?d00001 diff --git a/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-600.svg b/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-600.svg new file mode 100644 index 0000000000..27f05ee28d --- /dev/null +++ b/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-600.svg @@ -0,0 +1,332 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-600.ttf b/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-600.ttf new file mode 100644 index 0000000000000000000000000000000000000000..cc006ce1bef6327c69f9f53269f6676aa5dcdb37 GIT binary patch literal 57392 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 literal 0 HcmV?d00001 diff --git a/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-regular.eot b/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-regular.eot new file mode 100644 index 0000000000000000000000000000000000000000..02ec2e0b9ef8d41fdbb932b1aa83dbea1df63a8d GIT binary patch literal 28718 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 ZKAw9Fuu}afp1oN41BnMQa!RB}FrZG&Z$SV6 literal 0 HcmV?d00001 diff --git a/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-regular.svg b/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-regular.svg new file mode 100644 index 0000000000..94ae1d7b83 --- /dev/null +++ b/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-regular.svg @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-regular.ttf b/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2326e9018ca9e28c6660850e1e2a97dd890e789e GIT binary patch literal 57280 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 literal 0 HcmV?d00001 diff --git a/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-regular.woff b/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-regular.woff new file mode 100644 index 0000000000000000000000000000000000000000..5de8ad6b03dd96b2e624a1f37bca3d7413771453 GIT binary patch literal 29400 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 Z7OpFYpI!(W+nl*L+001{5b@%`P literal 0 HcmV?d00001 diff --git a/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-regular.woff2 b/src/frontend/packages/suse-theme/assets/fonts/work-sans/work-sans-v8-latin-regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..f63457d797c1038e29eacef0ddb0afe9947f24dc GIT binary patch literal 24212 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

U$u`P1v7;UD|LT%=gZM{x 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 literal 0 HcmV?d00001 diff --git a/src/frontend/packages/suse-theme/loader/loading.css b/src/frontend/packages/suse-theme/loader/loading.css index 034324c227..8d22c162f3 100644 --- a/src/frontend/packages/suse-theme/loader/loading.css +++ b/src/frontend/packages/suse-theme/loader/loading.css @@ -1,6 +1,6 @@ .stratos-initial-load-spinner-container { align-items: center; - background-color: #0D2C40; + background-color: #f2f2f2; display: flex; justify-content: center; height: 100%; @@ -16,9 +16,9 @@ .stratos-initial-load-spinner { animation: stratos-initial-load-animate 1s infinite linear; box-sizing: border-box; - border: 5px solid rgba(86, 107, 121,0.45); + border: 5px solid rgba(12, 50, 44, 0.45); border-radius: 50%; - border-top-color: #00B2E2; + border-top-color: #30BA78; display: inline-block; height: 128px; margin: 0 auto; diff --git a/src/frontend/packages/suse-theme/package.json b/src/frontend/packages/suse-theme/package.json index 76385dc742..3ccb7df39c 100644 --- a/src/frontend/packages/suse-theme/package.json +++ b/src/frontend/packages/suse-theme/package.json @@ -1,17 +1,19 @@ { - "name": "@suse/theme", - "version": "0.0.1", + "name": "@susestratos/theme", + "version": "0.0.0", "stratos": { "assets": { "assets/core": "core/assets", - "assets/favicon.ico": "favicon.ico" + "assets/favicon.ico": "favicon.ico", + "assets/fonts": "suse/fonts" }, "theme": { "loadingCss": "loader/loading.css", "loadingHtml": "loader/loading.html" } }, - "peerDependencies": {}, + "peerDependencies": { + }, "scripts": { "build": "rm -rf ../../../../dist/theme && mkdir -p ../../../../dist/theme && cp -R * ../../../../dist/theme" } diff --git a/src/frontend/packages/suse-theme/sass/custom.scss b/src/frontend/packages/suse-theme/sass/colors.scss similarity index 61% rename from src/frontend/packages/suse-theme/sass/custom.scss rename to src/frontend/packages/suse-theme/sass/colors.scss index e59c4bce3d..255c4c4c87 100644 --- a/src/frontend/packages/suse-theme/sass/custom.scss +++ b/src/frontend/packages/suse-theme/sass/colors.scss @@ -1,6 +1,19 @@ -// Custom SUSE Theme +@import '~@angular/material/theming'; +$white: #fff; +$black: #000; +$black-87-opacity: rgba($black, .87); +$white-87-opacity: rgba($white, .87); +$black-12-opacity: rgba($black, .12); +$white-12-opacity: rgba($white, .12); +$black-6-opacity: rgba($black, .06); +$white-6-opacity: rgba($white, .06); + +$suse-secondary: #00243e; +$suse-text-gray: #ccc; +$suse-button-gray: #888; +$suse-dark-blue: #06253A; // SUSE Primary Material Design pallette // From http://mcg.mbitson.com/#!?mcgpalette0=%233f51b5 @@ -13,4 +26,18 @@ $stratos-theme: mat-light-theme($suse-theme-primary, $suse-theme-primary, $suse- $stratos-dark-theme-supported: false; -@import "custom/suse" \ No newline at end of file +// SUSE Brand primary colour +$suse-primary-color: #30BA78; +$suse-text: #fff; +$suse-blue: #073155; +$suse-dark-green: #0c322c; +$suse-header-bar: #0c322c; +$suse-link-color: #008ACF; +$suse-gray: #f2f2f2; +$suse-gray-fg: #333; + +// Side nav +$suse-side-nav-bg: #f2f2f2; +$suse-side-nav-text: #000; +$suse-side-nav-active-bg: $suse-primary-color; +$suse-side-nav-active-text: #fff; diff --git a/src/frontend/packages/suse-theme/sass/custom/suse-colors.scss b/src/frontend/packages/suse-theme/sass/custom/suse-colors.scss deleted file mode 100644 index b444c572fd..0000000000 --- a/src/frontend/packages/suse-theme/sass/custom/suse-colors.scss +++ /dev/null @@ -1,7 +0,0 @@ -$suse-primary: #00c081; -$suse-secondary: #00243e; -$suse-text: #fff; -$suse-text-gray: #ccc; -$suse-button-gray: #888; -$suse-dark-blue: #06253A; -$suse-blue: #073155; \ No newline at end of file diff --git a/src/frontend/packages/suse-theme/sass/custom/suse.scss b/src/frontend/packages/suse-theme/sass/custom/suse.scss deleted file mode 100644 index e658d2fad9..0000000000 --- a/src/frontend/packages/suse-theme/sass/custom/suse.scss +++ /dev/null @@ -1,124 +0,0 @@ -// ***** -// NOTE -// ***** -// Having this "theme" file bad practice. We need to work out if we can do without this. -// Having this file completely sidesteps our theming and angular's component styles, potentially resulting -// in inconsistent and unexpected styling. -// This should only be used as a last resort - NJ. - -// Style overrides -// SUSE Brand primary colour -$suse-primary-color: #00c081; -// Secondary blue colour -$suse-secondary: #00243e; -$suse-text: #fff; -$suse-text-gray: #ccc; -$suse-dark-blue: #06253A; -$suse-blue: #073155; -$suse-side-nav: $suse-secondary; -$suse-side-nav-active: #003358; - - -body.stratos { - .page-content > .page-header { - // Ensure kube icons don't appear above sub header - border-bottom: 1px solid rgba(0, 0, 0, 0.1); - color: #f5f5f5; - } - - img.stratos-title__logo { - width: 160px; - } - - h1.stratos-title__header { - display: initial; - } - - .page-header .mat-toolbar.mat-primary { - background-color: #fff; - color: #333; - } - - .user-avatar__initials { - background-color: $suse-primary-color; - color: $suse-text; - } - - .user-avatar__large .user-avatar__initials { - background-color: $suse-text; - color: $suse-primary-color; - } - - .setup .page-header .mat-toolbar.mat-primary { - background-color: $suse-primary-color; - color: $suse-text; - } - - app-page-subheader { - .page-subheader { - background-color: #fff; - color: #333; - } - - .mat-tab-nav-bar.mat-background-primary { - .mat-tab-links { - background-color: #fff; - color: #333; - } - - .mat-tab-link { - color: #333; - } - - .mat-ink-bar { - background-color: $suse-primary-color; - } - } - } - - // Use SUSE Blue Side Navigation - // This is themeable, we really don't need this. - .side-nav__inner, - .side-nav__top, - .side-nav__item--active:hover { - background-color: $suse-side-nav; - } - - .side-nav__item--active, - .side-nav__item:hover { - background-color: $suse-side-nav-active; - } - .side-nav__top { - border-color: $suse-side-nav-active; - } - .side-nav__bottom { - color: $suse-text-gray; - } - - // Remove green page underflow run on about page - app-about-page .page-header__underflow { - background-color: transparent; - } - - // Remove background image from setup screen (e.g. upgrade screen) - app-intro-screen > .intro { - background-color: $suse-blue; - background-image: none; - } - - .page-header__divider { - color: #333; - } - - .page-header__menu-button-icon { - font-size: 30; - background-color: $suse-primary-color; - color: $suse-text; - } - - .dark-theme { - .page-content > .page-header { - border-bottom: solid 1px rgba(255, 255, 255, 0.12); - } - } -} diff --git a/src/frontend/packages/suse-theme/sass/font.scss b/src/frontend/packages/suse-theme/sass/font.scss new file mode 100644 index 0000000000..888f488882 --- /dev/null +++ b/src/frontend/packages/suse-theme/sass/font.scss @@ -0,0 +1,41 @@ +$work-sans-font-path: '/suse/fonts/work-sans' !default; + +/* work-sans-300 - latin */ +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 300; + src: url('#{$work-sans-font-path}/work-sans-v8-latin-300.eot'); /* IE9 Compat Modes */ + src: local(''), + url('#{$work-sans-font-path}/work-sans-v8-latin-300.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('#{$work-sans-font-path}/work-sans-v8-latin-300.woff2') format('woff2'), /* Super Modern Browsers */ + url('#{$work-sans-font-path}/work-sans-v8-latin-300.woff') format('woff'), /* Modern Browsers */ + url('#{$work-sans-font-path}/work-sans-v8-latin-300.ttf') format('truetype'), /* Safari, Android, iOS */ + url('#{$work-sans-font-path}/work-sans-v8-latin-300.svg#WorkSans') format('svg'); /* Legacy iOS */ +} +/* work-sans-regular - latin */ +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 400; + src: url('#{$work-sans-font-path}/work-sans-v8-latin-regular.eot'); /* IE9 Compat Modes */ + src: local(''), + url('#{$work-sans-font-path}/work-sans-v8-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('#{$work-sans-font-path}/work-sans-v8-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */ + url('#{$work-sans-font-path}/work-sans-v8-latin-regular.woff') format('woff'), /* Modern Browsers */ + url('#{$work-sans-font-path}/work-sans-v8-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */ + url('#{$work-sans-font-path}/work-sans-v8-latin-regular.svg#WorkSans') format('svg'); /* Legacy iOS */ +} +/* work-sans-600 - latin */ +@font-face { + font-family: 'Work Sans'; + font-style: normal; + font-weight: 600; + src: url('#{$work-sans-font-path}/work-sans-v8-latin-600.eot'); /* IE9 Compat Modes */ + src: local(''), + url('#{$work-sans-font-path}/work-sans-v8-latin-600.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('#{$work-sans-font-path}/work-sans-v8-latin-600.woff2') format('woff2'), /* Super Modern Browsers */ + url('#{$work-sans-font-path}/work-sans-v8-latin-600.woff') format('woff'), /* Modern Browsers */ + url('#{$work-sans-font-path}/work-sans-v8-latin-600.ttf') format('truetype'), /* Safari, Android, iOS */ + url('#{$work-sans-font-path}/work-sans-v8-latin-600.svg#WorkSans') format('svg'); /* Legacy iOS */ +} diff --git a/src/frontend/packages/suse-theme/sass/suse.scss b/src/frontend/packages/suse-theme/sass/suse.scss new file mode 100644 index 0000000000..c0cd8bd604 --- /dev/null +++ b/src/frontend/packages/suse-theme/sass/suse.scss @@ -0,0 +1,13 @@ +// Custom SUSE Theme + +// Work Sans font +@import '~../sass/font'; + +body.stratos { + + // Round buttons + button.mat-button { + border-radius: 20px; + } + +} diff --git a/src/tsconfig.json b/src/tsconfig.json index e8defdabb9..0ee21fc3d1 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -33,10 +33,10 @@ "@stratosui/core": ["frontend/packages/core/src/public-api.ts"], "@stratosui/store": ["frontend/packages/store/src/public-api.ts"], "@stratosui/store/testing": ["frontend/packages/store/testing/public-api.ts"], - "@suse/extensions": ["frontend/packages/suse-extensions/src/public-api.ts"], "@stratosui/cloud-foundry": ["frontend/packages/cloud-foundry/src/public_api.ts"], "@stratosui/cf-autoscaler": ["frontend/packages/cf-autoscaler/src/public_api.ts"], "@example/extensions": ["frontend/packages/example-extensions/src/public-api.ts"], + "@susestratos/extensions": ["frontend/packages/suse-extensions/src/public-api.ts"], } } } From 0543700a183b4dd0f64220484a84a03db444f985 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 10 Jul 2020 11:28:05 +0100 Subject: [PATCH 579/648] Update kube dashboard version, allow download link to be configurable (#409) * Fixes after merge * Fix build * Update Kube Dashboard, allow download link to be configurable - Default download link updated to v2.0.3 - Can configured link by setting env var `STRATOS_KUBERNETES_DASHBOARD_IMAGE` - Can configure env var in helm via `console.kubeDashboardImage` - Kube Dashboard now expanded by default (to show namespace drop down) * Fix after merge * Changes following review * Fix expand of kube dashboard header by default * Revert package-lock.json to master version --- deploy/kubernetes/console/README.md | 2 +- .../console/templates/deployment.yaml | 4 - deploy/kubernetes/console/values.yaml | 3 + deploy/kubernetes/custom/__stratos.tpl | 4 +- package-lock.json | 325 ++++-------------- .../kubernetes-dashboard.component.ts | 4 +- src/jetstream/default.config.properties | 5 +- .../plugins/kubernetes/dashboard/configure.go | 16 +- .../plugins/kubernetes/install_release.go | 3 + 9 files changed, 101 insertions(+), 265 deletions(-) diff --git a/deploy/kubernetes/console/README.md b/deploy/kubernetes/console/README.md index cc4a7399d4..3e156e249e 100644 --- a/deploy/kubernetes/console/README.md +++ b/deploy/kubernetes/console/README.md @@ -310,7 +310,7 @@ helm install my-console stratos/console --namespace=console --set console.localA In some scenarios it is useful to be able to add custom annotations and/or labels to the Kubernetes resources that the Stratos Helm chart creates. -The Stratos Helm chart exposes a number of Helm chart values that cabe specified in order to do this - they are: +The Stratos Helm chart exposes a number of Helm chart values that can be specified in order to do this - they are: |Parameter|Description|Default| |----|---|---| diff --git a/deploy/kubernetes/console/templates/deployment.yaml b/deploy/kubernetes/console/templates/deployment.yaml index 8dbef8dc25..05a92ef3e2 100644 --- a/deploy/kubernetes/console/templates/deployment.yaml +++ b/deploy/kubernetes/console/templates/deployment.yaml @@ -81,10 +81,6 @@ spec: value: "{{.Values.consoleVersion}}:{{ .Release.Revision }}" - name: STRATOS_HELM_RELEASE value: "{{ .Release.Name }}" - - name: STRATOS_KUBERNETES_NAMESPACE - value: "{{ .Release.Namespace }}" - - name: STRATOS_KUBERNETES_TERMINAL_IMAGE - value: "{{.Values.kube.registry.hostname}}/{{.Values.kube.organization}}/stratos-kube-terminal:{{.Values.consoleVersion}}" - name: DB_USER valueFrom: secretKeyRef: diff --git a/deploy/kubernetes/console/values.yaml b/deploy/kubernetes/console/values.yaml index dd4b40cbdf..2335e6ccbb 100644 --- a/deploy/kubernetes/console/values.yaml +++ b/deploy/kubernetes/console/values.yaml @@ -108,6 +108,9 @@ console: # Node Selector for console Pod nodeSelector: {} + + # Download link when installing the Kubernetes Dashboard in a targetted Kube Endpoint + kubeDashboardImage: images: console: stratos-console diff --git a/deploy/kubernetes/custom/__stratos.tpl b/deploy/kubernetes/custom/__stratos.tpl index 4999b0e2cb..732932914d 100644 --- a/deploy/kubernetes/custom/__stratos.tpl +++ b/deploy/kubernetes/custom/__stratos.tpl @@ -13,7 +13,9 @@ - name: SYNC_SERVER_URL value: "http://{{ .Release.Name }}-chartsync:8080" - name: STRATOS_KUBERNETES_NAMESPACE - value: "{{ .Release.Namespace }}" + value: "{{ .Release.Namespace }}" - name: STRATOS_KUBERNETES_TERMINAL_IMAGE value: "{{.Values.kube.registry.hostname}}/{{.Values.kube.organization}}/stratos-kube-terminal:{{.Values.consoleVersion}}" +- name: STRATOS_KUBERNETES_DASHBOARD_IMAGE + value: "{{.Values.console.kubeDashboardImage}}" {{- end }} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1376614766..8a0a8847d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,20 +4,6 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "@angular-builders/custom-webpack": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@angular-builders/custom-webpack/-/custom-webpack-9.1.0.tgz", - "integrity": "sha512-Dek6KxNUFBELKqNRO4Im5JIP0/rZF4HmvgA8X+RyqOd9cyDxk16A441WlqTqy3UKX8lcbf6C9RcR5D2dI1ZATQ==", - "dev": true, - "requires": { - "@angular-devkit/architect": ">=0.900.0 < 0.1000.0", - "@angular-devkit/build-angular": ">=0.900.0 < 0.1000.0", - "@angular-devkit/core": "^9.0.0", - "lodash": "^4.17.10", - "ts-node": "^8.5.2", - "webpack-merge": "^4.2.1" - } - }, "@angular-devkit/architect": { "version": "0.901.7", "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.901.7.tgz", @@ -632,17 +618,6 @@ "resolved": "https://registry.npmjs.org/@angular/router/-/router-9.1.9.tgz", "integrity": "sha512-4u+CWMPB4hCkAsFCEzC94YEWT0wVozqGkc/Dortt2hFaqvZpIegg6iJVZlDxuyDjzFYBPnnbTDdgiTTA8ckfuA==" }, - "@apidevtools/json-schema-ref-parser": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.1.tgz", - "integrity": "sha512-Qsdz0W0dyK84BuBh5KZATWXOtVDXIF2EeNRzpyWblPUeAmnIokwWcwrpAm5pTPMjuWoIQt9C67X3Af1OlL6oSw==", - "dev": true, - "requires": { - "@jsdevtools/ono": "^7.1.2", - "call-me-maybe": "^1.0.1", - "js-yaml": "^3.13.1" - } - }, "@babel/code-frame": { "version": "7.5.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", @@ -2798,12 +2773,6 @@ } } }, - "@jsdevtools/ono": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.2.tgz", - "integrity": "sha512-qS/a24RA5FEoiJS9wiv6Pwg2c/kiUo3IVUQcfeM9JvsR6pM8Yx+yl/6xWYLckZCT5jpLNhslgjiA8p/XcGyMRQ==", - "dev": true - }, "@ngrx/effects": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-9.2.0.tgz", @@ -2836,19 +2805,6 @@ "webpack-sources": "1.4.3" }, "dependencies": { - "@angular-devkit/core": { - "version": "9.1.7", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.1.7.tgz", - "integrity": "sha512-guvolu9Cl+qYMTtedLZD9wCqustJjdqzJ2psD2C1Sr1LrX9T0mprmDldR/YnhsitThveJEb6sM/0EvqWxoSvKw==", - "dev": true, - "requires": { - "ajv": "6.12.0", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.5.4", - "source-map": "0.7.3" - } - }, "rxjs": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", @@ -2857,12 +2813,6 @@ "requires": { "tslib": "^1.9.0" } - }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true } } }, @@ -3155,28 +3105,6 @@ "requires": { "@angular-devkit/core": "9.1.7", "@angular-devkit/schematics": "9.1.7" - }, - "dependencies": { - "@angular-devkit/schematics": { - "version": "9.1.7", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-9.1.7.tgz", - "integrity": "sha512-oeHPJePBcPp/bd94jHQeFUnft93PGF5iJiKV9szxqS8WWC5OMZ5eK7icRY0PwvLyfenspAZxdZcNaqJqPMul5A==", - "dev": true, - "requires": { - "@angular-devkit/core": "9.1.7", - "ora": "4.0.3", - "rxjs": "6.5.4" - } - }, - "rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - } } }, "@schematics/update": { @@ -3196,19 +3124,6 @@ "semver-intersect": "1.4.0" }, "dependencies": { - "@angular-devkit/core": { - "version": "9.1.7", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.1.7.tgz", - "integrity": "sha512-guvolu9Cl+qYMTtedLZD9wCqustJjdqzJ2psD2C1Sr1LrX9T0mprmDldR/YnhsitThveJEb6sM/0EvqWxoSvKw==", - "dev": true, - "requires": { - "ajv": "6.12.0", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.5.4", - "source-map": "0.7.3" - } - }, "rxjs": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", @@ -3223,12 +3138,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", "dev": true - }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true } } }, @@ -3256,6 +3165,24 @@ "d3-transition": "^1.3.2" } }, + "@swimlane/ngx-graph": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@swimlane/ngx-graph/-/ngx-graph-7.0.1.tgz", + "integrity": "sha512-V85EuEJr61AM3J24slsiUkg6eak6J8IBy5zeD55fywLl3QbndRELRp3l/T2wu/HNpzyHzrC0/qpkEauqcHtRsA==", + "requires": { + "@swimlane/ngx-charts": "^13.0.1", + "d3-dispatch": "^1.0.3", + "d3-ease": "^1.0.5", + "d3-force": "^1.1.0", + "d3-selection": "^1.2.0", + "d3-shape": "^1.2.0", + "d3-timer": "^1.0.7", + "d3-transition": "^1.1.1", + "dagre": "^0.8.4", + "transformation-matrix": "^1.15.3", + "webcola": "^3.3.8" + } + }, "@szmarczak/http-timer": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", @@ -3884,12 +3811,6 @@ "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", "dev": true }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", - "dev": true - }, "anymatch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", @@ -5211,12 +5132,6 @@ } } }, - "call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", - "dev": true - }, "caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", @@ -5432,20 +5347,6 @@ "integrity": "sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==", "dev": true }, - "cli-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.0.tgz", - "integrity": "sha512-a0VZ8LeraW0jTuCkuAGMNufareGHhyZU9z8OGsW0gXd1hZGi1SRuNRXdbGkraBBKnhyUhyebFWnRbp+dIn0f0A==", - "dev": true, - "requires": { - "ansi-regex": "^2.1.1", - "d": "^1.0.1", - "es5-ext": "^0.10.51", - "es6-iterator": "^2.0.3", - "memoizee": "^0.4.14", - "timers-ext": "^0.1.7" - } - }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -6471,6 +6372,11 @@ "d3-transition": "1" } }, + "d3-collection": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" + }, "d3-color": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", @@ -6495,6 +6401,17 @@ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.6.tgz", "integrity": "sha512-SZ/lVU7LRXafqp7XtIcBdxnWl8yyLpgOmzAk0mWBI9gXNzLDx5ybZgnRbH9dN/yY5tzVBqCQ9avltSnqVwessQ==" }, + "d3-force": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", + "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", + "requires": { + "d3-collection": "1", + "d3-dispatch": "1", + "d3-quadtree": "1", + "d3-timer": "1" + } + }, "d3-format": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.4.tgz", @@ -6518,6 +6435,11 @@ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" }, + "d3-quadtree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz", + "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==" + }, "d3-scale": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.2.1.tgz", @@ -6574,6 +6496,15 @@ "d3-timer": "1" } }, + "dagre": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", + "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", + "requires": { + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, "damerau-levenshtein": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", @@ -9145,6 +9076,14 @@ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, + "graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "requires": { + "lodash": "^4.17.15" + } + }, "gulp": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", @@ -10331,12 +10270,6 @@ "isobject": "^3.0.1" } }, - "is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", - "dev": true - }, "is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", @@ -10971,57 +10904,6 @@ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "dev": true }, - "json-schema-ref-parser": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-9.0.1.tgz", - "integrity": "sha512-KLrCjRjW5hMXxsX4osVBWpwixXL9NtICfpyNNS0eHguN5mP/I4UatI7i7PFS8jU94b1NHF4EbirACdCn0RFPBA==", - "dev": true, - "requires": { - "@apidevtools/json-schema-ref-parser": "9.0.1" - } - }, - "json-schema-to-typescript": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-9.1.0.tgz", - "integrity": "sha512-9/yDXQQyqtRDxohQGRCKht4Wjfg73TALi1yzy651EOo71a6aKFtIm2WUbDWSf8OitFGukUn00dx4t1kg0W6O4Q==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.4", - "cli-color": "^2.0.0", - "glob": "^7.1.6", - "is-glob": "^4.0.1", - "json-schema-ref-parser": "^9.0.1", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.15", - "minimist": "^1.2.5", - "mkdirp": "^1.0.4", - "mz": "^2.7.0", - "prettier": "^2.0.5", - "stdin": "0.0.1" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - } - } - }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -11892,8 +11774,7 @@ "lodash": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "lodash-es": { "version": "4.17.15", @@ -12018,15 +11899,6 @@ } } }, - "lru-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", - "dev": true, - "requires": { - "es5-ext": "~0.10.2" - } - }, "magic-string": { "version": "0.25.7", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", @@ -12341,22 +12213,6 @@ } } }, - "memoizee": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", - "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.45", - "es6-weak-map": "^2.0.2", - "event-emitter": "^0.3.5", - "is-promise": "^2.1", - "lru-queue": "0.1", - "next-tick": "1", - "timers-ext": "^0.1.5" - } - }, "memory-fs": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", @@ -12880,17 +12736,6 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, - "mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "requires": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -15839,12 +15684,6 @@ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, - "prettier": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", - "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", - "dev": true - }, "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -18671,12 +18510,6 @@ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "dev": true }, - "stdin": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/stdin/-/stdin-0.0.1.tgz", - "integrity": "sha1-0wQZgarsPf28d6GzjWNy449ftx4=", - "dev": true - }, "stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", @@ -19653,24 +19486,6 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "thenify": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", - "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", - "dev": true, - "requires": { - "any-promise": "^1.0.0" - } - }, - "thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", - "dev": true, - "requires": { - "thenify": ">= 3.1.0 < 4" - } - }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -19718,16 +19533,6 @@ "setimmediate": "^1.0.4" } }, - "timers-ext": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", - "dev": true, - "requires": { - "es5-ext": "~0.10.46", - "next-tick": "1" - } - }, "timsort": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", @@ -19843,6 +19648,11 @@ "punycode": "^2.1.1" } }, + "transformation-matrix": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/transformation-matrix/-/transformation-matrix-1.15.3.tgz", + "integrity": "sha512-ThJH58GNFKhCw3gIoOtwf3tNwuYjbyEeiGdeq4mNMYWdJctnI896KUqn6PVt7jmNVepqa1bcKQtnMB1HtjsDMA==" + }, "tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -20989,6 +20799,17 @@ "resolved": "https://registry.npmjs.org/web-animations-js/-/web-animations-js-2.3.2.tgz", "integrity": "sha512-TOMFWtQdxzjWp8qx4DAraTWTsdhxVSiWa6NkPFSaPtZ1diKUxTn4yTix73A1euG1WbSOMMPcY51cnjTIHrGtDA==" }, + "webcola": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/webcola/-/webcola-3.4.0.tgz", + "integrity": "sha512-4BiLXjXw3SJHo3Xd+rF+7fyClT6n7I+AR6TkBqyQ4kTsePSAMDLRCXY1f3B/kXJeP9tYn4G1TblxTO+jAt0gaw==", + "requires": { + "d3-dispatch": "^1.0.3", + "d3-drag": "^1.0.4", + "d3-shape": "^1.3.5", + "d3-timer": "^1.0.5" + } + }, "webdriver-js-extender": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz", diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.ts index 0655cfae02..f54541c1b2 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.ts @@ -50,7 +50,7 @@ export class KubernetesDashboardTabComponent implements OnInit { href = ''; isLoading$ = new BehaviorSubject(true); hasError$ = new BehaviorSubject(false); - expanded = false; + expanded = true; private loadCheckTries = 0; private haveSetupEventLister = false; @@ -126,7 +126,7 @@ export class KubernetesDashboardTabComponent implements OnInit { if (hasLoaded) { this.isLoading$.next(false); - this.toggle(false); + this.toggle(true); } } diff --git a/src/jetstream/default.config.properties b/src/jetstream/default.config.properties index 2a430adb51..e6466f8004 100644 --- a/src/jetstream/default.config.properties +++ b/src/jetstream/default.config.properties @@ -73,4 +73,7 @@ INVITE_USER_CLIENT_SECRET= #SYNC_SERVER_URL=http://127.0.0.1:8080" # Simplify development with FDB (value is port of FDB server: 27016 for FDB, 27017 for MongoDB) -#FDB_LOCAL_DEV=27017 \ No newline at end of file +#FDB_LOCAL_DEV=27017 + +# Download link when installing the Kubernetes Dashboard in a targetted Kube Endpoint +# STRATOS_KUBERNETES_DASHBOARD_IMAGE= \ No newline at end of file diff --git a/src/jetstream/plugins/kubernetes/dashboard/configure.go b/src/jetstream/plugins/kubernetes/dashboard/configure.go index 41bcafae81..ed648b29d0 100644 --- a/src/jetstream/plugins/kubernetes/dashboard/configure.go +++ b/src/jetstream/plugins/kubernetes/dashboard/configure.go @@ -14,7 +14,7 @@ import ( "gopkg.in/yaml.v2" ) -const dashboardInstallYAMLDownloadURL = "https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-rc2/aio/deploy/recommended.yaml" +const dashboardInstallYAMLDownloadURL = "https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.3/aio/deploy/recommended.yaml" // Service Account definition - as per kube dashboard docs const serviceAccountDefinition = `{ @@ -43,7 +43,7 @@ const clusterRoleBindingDefinition = `{ "apiGroup": "rbac.authorization.k8s.io", "kind": "ClusterRole", "name": "cluster-admin" - }, + }, "subjects": [ { "kind": "ServiceAccount", @@ -147,10 +147,18 @@ func addErrorMessage(msg, prefix string, response *interfaces.CNSIRequest, err e // InstallDashboard will install the dashboard into a Kubernetes cluster func InstallDashboard(p interfaces.PortalProxy, endpointGUID, userGUID string) error { // Download the Yaml for the dashboard - log.Debugf("InstallDashboardL %s", dashboardInstallYAMLDownloadURL) + kubeDashboardImage := p.Env().String("STRATOS_KUBERNETES_DASHBOARD_IMAGE", "") + if len(kubeDashboardImage) == 0 { + kubeDashboardImage = dashboardInstallYAMLDownloadURL + } + + log.Debugf("InstallDashboard: %s", kubeDashboardImage) http := p.GetHttpClient(false) - resp, err := http.Get(dashboardInstallYAMLDownloadURL) + resp, err := http.Get(kubeDashboardImage) + if err != nil { + return fmt.Errorf("Could not download YAML to install the dashboard: %+v", err) + } if resp.StatusCode != 200 { return fmt.Errorf("Could not download YAML to install the dashboard: %s", resp.Status) } diff --git a/src/jetstream/plugins/kubernetes/install_release.go b/src/jetstream/plugins/kubernetes/install_release.go index 0548fac69d..0505f51f06 100644 --- a/src/jetstream/plugins/kubernetes/install_release.go +++ b/src/jetstream/plugins/kubernetes/install_release.go @@ -65,6 +65,9 @@ func (c *KubernetesSpecification) InstallRelease(ec echo.Context) error { // NWM: Should we look up Helm Repository endpoint and use the value from that httpClient := c.portalProxy.GetHttpClient(false) resp, err := httpClient.Get(downloadURL) + if err != nil { + return interfaces.NewJetstreamErrorf("Could not download Chart Archive: %s", err) + } if resp.StatusCode != 200 { return interfaces.NewJetstreamErrorf("Could not download Chart Archive: %s", resp.Status) } From 864f650488d0114e52e51f654b1cf9f204c222db Mon Sep 17 00:00:00 2001 From: Michal Jura Date: Mon, 20 Jul 2020 12:15:25 +0200 Subject: [PATCH 580/648] Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) --- deploy/containers/nginx/conf/nginx.dev.conf | 5 +++-- deploy/containers/nginx/conf/nginx.k8s.conf | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/deploy/containers/nginx/conf/nginx.dev.conf b/deploy/containers/nginx/conf/nginx.dev.conf index 05a2573e92..23719b11b9 100644 --- a/deploy/containers/nginx/conf/nginx.dev.conf +++ b/deploy/containers/nginx/conf/nginx.dev.conf @@ -47,8 +47,9 @@ http { ssl_certificate /etc/secrets/server.crt; ssl_certificate_key /etc/secrets/server.key; - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_ciphers HIGH:!aNULL:!MD5; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384; + ssl_prefer_server_ciphers on; client_max_body_size 50M; diff --git a/deploy/containers/nginx/conf/nginx.k8s.conf b/deploy/containers/nginx/conf/nginx.k8s.conf index 2d15a1c960..620b3fd3e3 100644 --- a/deploy/containers/nginx/conf/nginx.k8s.conf +++ b/deploy/containers/nginx/conf/nginx.k8s.conf @@ -47,8 +47,9 @@ http { ssl_certificate /CONSOLE_CERT_PATH/tls.crt; ssl_certificate_key /CONSOLE_CERT_PATH/tls.key; - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_ciphers HIGH:!aNULL:!MD5; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384; + ssl_prefer_server_ciphers on; client_max_body_size 50M; From 8f68d22572c8ee91601e1acd2d7dce5c6e0f2641 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 24 Jul 2020 08:57:05 +0100 Subject: [PATCH 581/648] Merge upstream (#414) * Improve recent and favourites display (#4421) * Improve recent and favourites display * Remove debug logging * Remove debug logging/subscription leak * Unit test fix * Tweaks following review * Changes following review - favourite & recent icon changes Co-authored-by: Richard Cox * Add line-height to favourite and recent entity labels (#4438) - missed out from another PR following review * Autoscaler e2e tests: Give better failure error when the test can't find the scaling row (#4424) * Improve autoscaler e2e tests * Remove duplicate fail statement * Helm Chart: Remove encryption key volume (#4355) * Remove encryption key volume * Remove encrpytion key volume migration from config-init job * Improve the logout experience (#4439) * Add support for including breaking changes in the changelog * Improve the logout experience * Fix unit tests * Add request for version info to github issues template (#4443) * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Fix log out page when there's a custom log in page * Fix log out page given previous commit changes Co-authored-by: Neil MacDougall --- deploy/ci/travis/e2e-build-script.sh | 19 +-- deploy/ci/travis/e2e-mc-helper.sh | 3 +- deploy/containers/config-init/config-init.sh | 30 +---- .../console/templates/config-init.yaml | 16 --- .../kubernetes/console/templates/volumes.yaml | 27 +--- docs/issue_template.md | 8 +- package-lock.json | 115 ++++++++++-------- .../packages/core/sass/_all-theme.scss | 3 +- .../packages/core/sass/mat-desktop.scss | 9 ++ src/frontend/packages/core/src/app.routing.ts | 16 ++- .../src/core/extension/extension-service.ts | 3 +- .../log-out-dialog.component.spec.ts | 15 ++- .../log-out-dialog.component.ts | 10 +- src/frontend/packages/core/src/favicon.ico | Bin 15086 -> 0 bytes .../core/src/features/login/login.module.ts | 8 +- .../core/src/features/login/login.routing.ts | 15 --- .../logout-page/logout-page.component.html | 17 +++ .../logout-page/logout-page.component.scss | 14 +++ .../logout-page/logout-page.component.spec.ts | 42 +++++++ .../logout-page/logout-page.component.ts | 35 ++++++ .../profile-info/profile-info.component.scss | 4 +- .../app-action-monitor.component.html | 2 +- .../app-action-monitor.component.ts | 10 +- .../favorites-entity-list.component.html | 2 +- .../favorites-entity-list.component.ts | 9 +- .../favorites-global-list.component.html | 3 +- .../favorites-meta-card.component.scss | 1 + .../json-viewer.component.theme.scss | 15 +++ .../page-header/page-header.component.ts | 3 +- .../recent-entities.component.scss | 1 + .../store/src/reducers/auth.reducer.ts | 6 + .../suse-extensions/src/custom/suse.module.ts | 16 +-- src/frontend/packages/suse-theme/_index.scss | 2 +- .../application-autoscaler-e2e.spec.ts | 11 ++ 34 files changed, 299 insertions(+), 191 deletions(-) delete mode 100644 src/frontend/packages/core/src/favicon.ico delete mode 100644 src/frontend/packages/core/src/features/login/login.routing.ts create mode 100644 src/frontend/packages/core/src/features/login/logout-page/logout-page.component.html create mode 100644 src/frontend/packages/core/src/features/login/logout-page/logout-page.component.scss create mode 100644 src/frontend/packages/core/src/features/login/logout-page/logout-page.component.spec.ts create mode 100644 src/frontend/packages/core/src/features/login/logout-page/logout-page.component.ts create mode 100644 src/frontend/packages/core/src/shared/components/json-viewer/json-viewer.component.theme.scss diff --git a/deploy/ci/travis/e2e-build-script.sh b/deploy/ci/travis/e2e-build-script.sh index 48f58af62f..757bd7dd42 100755 --- a/deploy/ci/travis/e2e-build-script.sh +++ b/deploy/ci/travis/e2e-build-script.sh @@ -50,6 +50,7 @@ function tryGetExistingBuild() { fi } +# Need S3 endpoint - if we don't have it, we don't have the Travis env vars if [ -n "${AWS_ENDPOINT}" ]; then tryGetExistingBuild fi @@ -68,14 +69,16 @@ else npm run build npm run build-backend - set +e - tar cvfz ${GZIP_NAME} dist/* src/jetstream/jetstream + # Only try to upload if we have the S3 configuration + if [ -n "${AWS_ENDPOINT}" ]; then + set +e + tar cvfz ${GZIP_NAME} dist/* src/jetstream/jetstream - # Upload - mc cp -q --insecure ${GZIP_NAME} ${MC_HOST}/${S3_BUILDS_BUCKET} - - # Ignore error from uploading - should not fail build if we can't upload the build archive - # This just means we won't be able to us this cache next build - exit 0 + # Upload + mc cp -q --insecure ${GZIP_NAME} ${MC_HOST}/${S3_BUILDS_BUCKET} + # Ignore error from uploading - should not fail build if we can't upload the build archive + # This just means we won't be able to us this cache next build + echo "Uploaded builds" + fi fi diff --git a/deploy/ci/travis/e2e-mc-helper.sh b/deploy/ci/travis/e2e-mc-helper.sh index 4dbb3109b6..5201e0a46f 100644 --- a/deploy/ci/travis/e2e-mc-helper.sh +++ b/deploy/ci/travis/e2e-mc-helper.sh @@ -1,6 +1,7 @@ # Helper for mc command -mc version > /dev/null +# Check if mc command is available (don't log error if it is not) +mc version > /dev/null 2>&1 if [ $? -eq 0 ]; then echo "mc command already installed and confgiured" else diff --git a/deploy/containers/config-init/config-init.sh b/deploy/containers/config-init/config-init.sh index 31c7ac8876..d5bc58a4b5 100755 --- a/deploy/containers/config-init/config-init.sh +++ b/deploy/containers/config-init/config-init.sh @@ -11,10 +11,6 @@ echo "RELEASE_NAME : ${RELEASE_NAME}" echo "RELEASE_REVISION : ${RELEASE_REVISION}" echo "IS_UPGRADE : ${IS_UPGRADE}" echo "CONSOLE_TLS_SECRET_NAME : ${CONSOLE_TLS_SECRET_NAME}" -echo "ENCRYPTION_KEY_VOLUME : ${ENCRYPTION_KEY_VOLUME}" -echo "ENCRYPTION_KEY_FILENAME : ${ENCRYPTION_KEY_FILENAME}" -echo "CONSOLE_PROXY_CERT_PATH : ${CONSOLE_PROXY_CERT_PATH}" -echo "CONSOLE_PROXY_CERT_KEY_PATH : ${CONSOLE_PROXY_CERT_KEY_PATH}" echo "" echo "============================================" echo "" @@ -44,15 +40,6 @@ EOF } function generateCert { - if [ -n "${CONSOLE_PROXY_CERT_PATH}" ] && [ -n "${CONSOLE_PROXY_CERT_KEY_PATH}" ]; then - if [ -f "${CONSOLE_PROXY_CERT_PATH}" ] && [ -f "${CONSOLE_PROXY_CERT_KEY_PATH}" ]; then - echo "Found existing certificate on encryption key volume - going to use it" - CERT_CRT=$(cat ${CONSOLE_PROXY_CERT_PATH} | base64 -w 0) - CERT_KEY=$(cat ${CONSOLE_PROXY_CERT_KEY_PATH} | base64 -w 0) - return - fi - fi - echo "Using cert generator to generate a self-signed certificate ..." export CERTS_PATH=./certs export DEV_CERTS_DOMAIN=tls @@ -97,20 +84,9 @@ if [ $EXISTS -eq 0 ]; then else echo "Fresh installation - generating a new Encryption Key secret" - # Migrate existing key from the legacy encryption key volume if there is one - if [ ${ENCRYPTION_KEY_VOLUME} -a ${ENCRYPTION_KEY_FILENAME} ]; then - ekFile="${ENCRYPTION_KEY_VOLUME}/${ENCRYPTION_KEY_FILENAME}" - if [ -f "${ekFile}" ]; then - echo "Found encryption key file on the legacy encryption key volume" - KEY=$(cat ${ekFile} | base64 -w 0) - fi - fi - - if [ -z $KEY ]; then - # Generate a random encryption key - echo "Generating a new Encryption Key ..." - KEY=$(openssl enc -aes-256-cbc -k secret -P -md sha1 | grep key | cut -d '=' -f2 | base64 -w 0) - fi + # Generate a random encryption key + echo "Generating a new Encryption Key ..." + KEY=$(openssl enc -aes-256-cbc -k secret -P -md sha1 | grep key | cut -d '=' -f2 | base64 -w 0) # We will create a new secret for the encryption key cat << EOF > create-key-secret.yaml diff --git a/deploy/kubernetes/console/templates/config-init.yaml b/deploy/kubernetes/console/templates/config-init.yaml index 0cc82ee389..6ed17f1e88 100644 --- a/deploy/kubernetes/console/templates/config-init.yaml +++ b/deploy/kubernetes/console/templates/config-init.yaml @@ -118,26 +118,14 @@ spec: value: "{{ .Chart.AppVersion }}" - name: "HELM_CHART" value: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" - - name: ENCRYPTION_KEY_VOLUME - value: "/{{ .Release.Name }}-encryption-key-volume" - - name: ENCRYPTION_KEY_FILENAME - value: key - name: CONSOLE_TLS_SECRET_NAME value: "{{ default "" .Values.console.tlsSecretName }}" - - name: CONSOLE_PROXY_CERT_PATH - value: "/{{ .Release.Name }}-encryption-key-volume/console.crt" - - name: CONSOLE_PROXY_CERT_KEY_PATH - value: "/{{ .Release.Name }}-encryption-key-volume/console.key" image: {{.Values.kube.registry.hostname}}/{{.Values.kube.organization}}/{{default "stratos-config-init" .Values.images.configInit}}:{{.Values.consoleVersion}} command: ["/config-init.sh"] imagePullPolicy: {{.Values.imagePullPolicy}} livenessProbe: ~ name: "config-init" readinessProbe: ~ - volumeMounts: - - mountPath: "/{{ .Release.Name }}-encryption-key-volume" - name: "{{ .Release.Name }}-encryption-key-volume" - readOnly: true {{- if and .Values.kube.registry.username .Values.kube.registry.password }} imagePullSecrets: - name: {{.Values.dockerRegistrySecret}} @@ -147,10 +135,6 @@ spec: serviceAccountName: "config-init" {{- end }} terminationGracePeriodSeconds: 600 - volumes: - - name: "{{ .Release.Name }}-encryption-key-volume" - persistentVolumeClaim: - claimName: "{{ .Release.Name }}-encryption-key-volume" --- {{- if .Values.autoCleanup }} # Cleanup job will delete the created secret when the release is deleted diff --git a/deploy/kubernetes/console/templates/volumes.yaml b/deploy/kubernetes/console/templates/volumes.yaml index 9e0c9b3500..5483e79ae9 100644 --- a/deploy/kubernetes/console/templates/volumes.yaml +++ b/deploy/kubernetes/console/templates/volumes.yaml @@ -1,31 +1,6 @@ ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: "{{ .Release.Name }}-encryption-key-volume" - labels: - app.kubernetes.io/name: "stratos" - app.kubernetes.io/instance: "{{ .Release.Name }}" - app.kubernetes.io/version: "{{ .Chart.AppVersion }}" - app.kubernetes.io/component: "console-encryption-volume" - helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" - annotations: - {{- if .Values.storageClass }} - volume.beta.kubernetes.io/storage-class: {{ .Values.storageClass | quote }} - {{- else if .Values.kube.storage_class.persistent }} - volume.beta.kubernetes.io/storage-class: {{ .Values.kube.storage_class.persistent | quote }} - {{- else }} - volume.alpha.kubernetes.io/storage-class: default - {{- end }} -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 20Mi ---- {{- if (not .Values.mariadb.external) }} {{- if and .Values.mariadb.persistence.enabled (not .Values.mariadb.persistence.existingClaim) }} +--- kind: PersistentVolumeClaim apiVersion: v1 metadata: diff --git a/docs/issue_template.md b/docs/issue_template.md index 9950dfe804..dffaf1add3 100644 --- a/docs/issue_template.md +++ b/docs/issue_template.md @@ -1,4 +1,8 @@ +### Stratos Version + + + ### Frontend Deployment type @@ -21,11 +25,11 @@ ### Actual behaviour -### Steps to reproduce the behavior +### Steps to reproduce the behaviour ### Log output covering before error and any error statements ``` -Insert log hereCopy +Insert your log here ``` diff --git a/package-lock.json b/package-lock.json index 8a0a8847d4..fdfea562d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6995,7 +6995,7 @@ }, "duplexer": { "version": "0.1.1", - "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, @@ -7698,7 +7698,7 @@ }, "event-stream": { "version": "3.3.4", - "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", "dev": true, "requires": { @@ -10808,6 +10808,66 @@ "integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==", "dev": true }, + "jasmine-protractor-browser-log-reporter": { + "version": "github:cf-stratos/jasmine-protractor-browser-log-reporter#5c9170221db6562f6f2eb128f0652e1c3cf2d668", + "from": "github:cf-stratos/jasmine-protractor-browser-log-reporter", + "dev": true, + "requires": { + "chalk": "^4.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "jasmine-spec-reporter": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-5.0.1.tgz", @@ -12022,7 +12082,7 @@ }, "map-stream": { "version": "0.1.0", - "resolved": "http://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", "dev": true }, @@ -14815,7 +14875,7 @@ }, "pause-stream": { "version": "0.0.11", - "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", "dev": true, "requires": { @@ -15915,49 +15975,6 @@ } } }, - "protractor-console": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/protractor-console/-/protractor-console-3.0.0.tgz", - "integrity": "sha512-2BTh751CMjEAMxuZXb86jvs0TDWjvCk7fCnKTyb5vX/KE5f+olTeVCmcFm+4Aretpc6q/6yryuSJ8wjgL9QTKw==", - "dev": true, - "requires": { - "chalk": "^1.1.0", - "lodash": "^3.10.0" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", - "dev": true - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, "proxy-addr": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", @@ -18398,7 +18415,7 @@ }, "split": { "version": "0.3.3", - "resolved": "http://registry.npmjs.org/split/-/split-0.3.3.tgz", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", "dev": true, "requires": { @@ -18575,7 +18592,7 @@ }, "stream-combiner": { "version": "0.0.4", - "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", "dev": true, "requires": { diff --git a/src/frontend/packages/core/sass/_all-theme.scss b/src/frontend/packages/core/sass/_all-theme.scss index c611d62d9a..c929676d41 100644 --- a/src/frontend/packages/core/sass/_all-theme.scss +++ b/src/frontend/packages/core/sass/_all-theme.scss @@ -18,7 +18,7 @@ @import '../src/shared/components/list/list-table/table-row/table-row.component.theme'; @import '../src/shared/components/no-content-message/no-content-message.component.theme'; @import '../src/shared/components/boolean-indicator/boolean-indicator.component.theme'; - +@import '../src/shared/components/json-viewer/json-viewer.component.theme'; @import '../src/shared/components/loading-page/loading-page.component.theme'; @import '../src/shared/components/log-viewer/log-viewer.component.theme'; @import '../src/shared/components/chips/chips.component.theme'; @@ -126,5 +126,6 @@ @include restore-endpoints-theme($theme, $app-theme); @include metrics-component-theme($theme, $app-theme); @include intro-screen-theme($theme, $app-theme); + @include app-json-view-theme($theme, $app-theme); } diff --git a/src/frontend/packages/core/sass/mat-desktop.scss b/src/frontend/packages/core/sass/mat-desktop.scss index f47438d989..3538518e13 100644 --- a/src/frontend/packages/core/sass/mat-desktop.scss +++ b/src/frontend/packages/core/sass/mat-desktop.scss @@ -107,4 +107,13 @@ $desktop-toggle-button-item-height: $desktop-menu-item-height - 2px; flex: 0 0 $desktop-page-header-height; height: $desktop-page-header-height; } + + mat-drawer.dashboard__side_preview { + top: $desktop-page-header-height; + height: calc(100vw - #{$desktop-page-header-height} - 1px); + } + + app-profile-info .user-profile { + top: $desktop-page-header-height; + } } diff --git a/src/frontend/packages/core/src/app.routing.ts b/src/frontend/packages/core/src/app.routing.ts index 9441eb4583..2020929d92 100644 --- a/src/frontend/packages/core/src/app.routing.ts +++ b/src/frontend/packages/core/src/app.routing.ts @@ -10,6 +10,8 @@ import { PageNotFoundComponentComponent } from './core/page-not-found-component/ import { CustomRoutingImportModule } from './custom-import.module'; import { DashboardBaseComponent } from './features/dashboard/dashboard-base/dashboard-base.component'; import { HomePageComponent } from './features/home/home/home-page.component'; +import { LoginPageComponent } from './features/login/login-page/login-page.component'; +import { LogoutPageComponent } from './features/login/logout-page/logout-page.component'; import { NoEndpointsNonAdminComponent } from './features/no-endpoints-non-admin/no-endpoints-non-admin.component'; import { DomainMismatchComponent } from './features/setup/domain-mismatch/domain-mismatch.component'; import { LocalAccountWizardComponent } from './features/setup/local-account-wizard/local-account-wizard.component'; @@ -40,7 +42,19 @@ const appRoutes: Routes = [ }, { path: 'upgrade', component: UpgradePageComponent }, { path: 'domainMismatch', component: DomainMismatchComponent }, - { path: 'login', loadChildren: () => import('./features/login/login.module').then(m => m.LoginModule) }, + { + path: 'login', + children: [ + { + path: '', + component: LoginPageComponent + }, + { + path: 'logout', + component: LogoutPageComponent + }, + ] + }, { path: '', component: DashboardBaseComponent, diff --git a/src/frontend/packages/core/src/core/extension/extension-service.ts b/src/frontend/packages/core/src/core/extension/extension-service.ts index b43f57a0bf..c2d198c7d6 100644 --- a/src/frontend/packages/core/src/core/extension/extension-service.ts +++ b/src/frontend/packages/core/src/core/extension/extension-service.ts @@ -167,7 +167,8 @@ export class ExtensionService { if (extensionMetadata.loginComponent) { // Override the component used for the login route - const loginRoute = routeConfig.find(r => r.path === 'login') || {}; + const loginRouteRoot = routeConfig.find(r => r.path === 'login') || { children: [] }; + const loginRoute = loginRouteRoot.children.find(c => c.path === ''); loginRoute.component = extensionMetadata.loginComponent; needsReset = true; } diff --git a/src/frontend/packages/core/src/core/log-out-dialog/log-out-dialog.component.spec.ts b/src/frontend/packages/core/src/core/log-out-dialog/log-out-dialog.component.spec.ts index b497e9540e..7d15220d14 100644 --- a/src/frontend/packages/core/src/core/log-out-dialog/log-out-dialog.component.spec.ts +++ b/src/frontend/packages/core/src/core/log-out-dialog/log-out-dialog.component.spec.ts @@ -1,19 +1,21 @@ import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { Store } from '@ngrx/store'; +import { Router } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../../test-framework/core-test.modules'; import { SharedModule } from '../../shared/shared.module'; import { CoreModule } from '../core.module'; +import { RouteModule } from './../../app.routing'; import { LogOutDialogComponent } from './log-out-dialog.component'; describe('LogOutDialogComponent', () => { let component: LogOutDialogComponent; let fixture: ComponentFixture; let element: HTMLElement; - let store: any; + let router: any; class MatDialogRefMock { } @@ -30,6 +32,8 @@ describe('LogOutDialogComponent', () => { ], imports: [ CoreModule, + RouterTestingModule, + RouteModule, SharedModule, MatDialogModule, NoopAnimationsModule, @@ -42,7 +46,7 @@ describe('LogOutDialogComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(LogOutDialogComponent); - store = TestBed.get(Store); + router = TestBed.get(Router); component = fixture.componentInstance; fixture.detectChanges(); element = fixture.nativeElement; @@ -52,8 +56,8 @@ describe('LogOutDialogComponent', () => { expect(component).toBeTruthy(); }); - it('should dispatch logout action after countdown', fakeAsync(() => { - const spy = spyOn(store, 'dispatch'); + it('should naivgate after countdown', fakeAsync(() => { + const spy = spyOn(router, 'navigate'); component.data = { expiryDate: Date.now() + 1000, @@ -65,6 +69,7 @@ describe('LogOutDialogComponent', () => { expect(spy).not.toHaveBeenCalled(); tick(1500); expect(spy).toHaveBeenCalled(); + expect(spy).toHaveBeenCalledWith(['/login/logout']); })); afterEach(() => { diff --git a/src/frontend/packages/core/src/core/log-out-dialog/log-out-dialog.component.ts b/src/frontend/packages/core/src/core/log-out-dialog/log-out-dialog.component.ts index 477fc342d5..34574f7c51 100644 --- a/src/frontend/packages/core/src/core/log-out-dialog/log-out-dialog.component.ts +++ b/src/frontend/packages/core/src/core/log-out-dialog/log-out-dialog.component.ts @@ -1,12 +1,9 @@ import { Component, Inject, OnDestroy, OnInit, Optional } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { Store } from '@ngrx/store'; +import { Router } from '@angular/router'; import { interval, Subscription } from 'rxjs'; import { tap } from 'rxjs/operators'; -import { Logout } from '../../../../store/src/actions/auth.actions'; -import { GeneralEntityAppState } from '../../../../store/src/app-state'; - @Component({ selector: 'app-log-out-dialog', templateUrl: './log-out-dialog.component.html', @@ -16,7 +13,8 @@ export class LogOutDialogComponent implements OnInit, OnDestroy { constructor( public dialogRef: MatDialogRef, @Optional() @Inject(MAT_DIALOG_DATA) public data: any, - private store: Store) { } + private router: Router + ) { } private autoLogout: Subscription; private countDown: number; @@ -33,7 +31,7 @@ export class LogOutDialogComponent implements OnInit, OnDestroy { this.countDown = this.calcCountdown(); if (this.countDown <= 0) { this.autoLogout.unsubscribe(); - this.store.dispatch(new Logout()); + this.router.navigate(['/login/logout']); } else { this.percentage = ((this.countdownTotal - this.countDown) / this.countdownTotal) * 100; } diff --git a/src/frontend/packages/core/src/favicon.ico b/src/frontend/packages/core/src/favicon.ico deleted file mode 100644 index 21f49bc43b08f557f085d0ba170f388e5956373f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 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 + + +

+ +
+
Logging out
+
+ +
+
+
+
+ diff --git a/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.scss b/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.scss new file mode 100644 index 0000000000..c1b0168e6d --- /dev/null +++ b/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.scss @@ -0,0 +1,14 @@ +.logout { + &__card { + padding: 0; + width: 300px; + } + &__body { + padding: 24px; + text-align: center; + } + &__msg { + font-size: 18px; + padding-bottom: 20px; + } +} diff --git a/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.spec.ts b/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.spec.ts new file mode 100644 index 0000000000..fff7bac83e --- /dev/null +++ b/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.spec.ts @@ -0,0 +1,42 @@ +import { CommonModule } from '@angular/common'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { CoreModule } from '@angular/flex-layout'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { RouterTestingModule } from '@angular/router/testing'; +import { StoreModule } from '@ngrx/store'; + +import { appReducers } from '../../../../../store/src/reducers.module'; +import { SharedModule } from '../../../public-api'; +import { LogoutPageComponent } from './logout-page.component'; + +describe('LogoutPageComponent', () => { + let component: LogoutPageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ LogoutPageComponent ], + imports: [ + CommonModule, + CoreModule, + SharedModule, + RouterTestingModule, + NoopAnimationsModule, + StoreModule.forRoot( + appReducers + ) + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LogoutPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.ts b/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.ts new file mode 100644 index 0000000000..ed30fc6c6d --- /dev/null +++ b/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.ts @@ -0,0 +1,35 @@ +import { Component, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { Logout } from '../../../../../store/src/actions/auth.actions'; +import { AppState } from '../../../../../store/src/app-state'; + +@Component({ + selector: 'app-logout-page', + templateUrl: './logout-page.component.html', + styleUrls: ['./logout-page.component.scss'] +}) +export class LogoutPageComponent implements OnInit { + + public error$: Observable; + + constructor(private store: Store) { + this.error$ = this.store.select(s => s.auth).pipe( + map(auth => auth.error) + ); + } + + ngOnInit() { + // Dispatch the logout action after 1 second - give the logging out screen time to show + setTimeout(() => { + this.store.dispatch(new Logout()); + }, 1000) + } + + reload() { + window.location.assign(window.location.origin); + } + +} diff --git a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.scss b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.scss index 0c5079208e..ba56f7161a 100644 --- a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.scss +++ b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.scss @@ -1,5 +1,3 @@ -@import '../../../../sass/mat-desktop'; - $user-profile-avatar-size: 48px; .user-profile { @@ -9,7 +7,7 @@ $user-profile-avatar-size: 48px; left: 0; position: absolute; right: 0; - top: $desktop-page-header-height; + top: 56px; mat-card:not(:first-child) { margin-top: 24px; } diff --git a/src/frontend/packages/core/src/shared/components/app-action-monitor/app-action-monitor.component.html b/src/frontend/packages/core/src/shared/components/app-action-monitor/app-action-monitor.component.html index 1c9713853c..a13bfa2bf3 100644 --- a/src/frontend/packages/core/src/shared/components/app-action-monitor/app-action-monitor.component.html +++ b/src/frontend/packages/core/src/shared/components/app-action-monitor/app-action-monitor.component.html @@ -1,4 +1,4 @@ -
+
\ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/app-action-monitor/app-action-monitor.component.ts b/src/frontend/packages/core/src/shared/components/app-action-monitor/app-action-monitor.component.ts index 345af4a5c6..84b00b4d77 100644 --- a/src/frontend/packages/core/src/shared/components/app-action-monitor/app-action-monitor.component.ts +++ b/src/frontend/packages/core/src/shared/components/app-action-monitor/app-action-monitor.component.ts @@ -27,6 +27,8 @@ export class AppActionMonitorComponent implements OnInit { @Input() public data$: Observable> = observableNever(); + public replayData$: Observable>; + @Input() public entityKey: string; @@ -81,16 +83,16 @@ export class AppActionMonitorComponent implements OnInit { cellFlex: '0 0 24px' }; - // Some obs will only ever emit once, once consumed in template this meant table never received emitted data - // so wrap in publish replay - const replayData = this.data$.pipe( + // Some data$ obs only ever emit once. If we subscribed directly to this then that emit would be consumed and will not be available + // in the data source connect subscription. So wrap it in a replay to ensure the last emitted value is available + this.replayData$ = this.data$.pipe( publishReplay(1), refCount() ) this.allColumns = [...this.columns, monitorColumn]; this.dataSource = { - connect: () => replayData, + connect: () => this.replayData$, disconnect: () => { }, trackBy: (index, item) => { const fn = monitorColumn.cellConfig(item).getId; diff --git a/src/frontend/packages/core/src/shared/components/favorites-entity-list/favorites-entity-list.component.html b/src/frontend/packages/core/src/shared/components/favorites-entity-list/favorites-entity-list.component.html index ab46c56ea4..450f368b5f 100644 --- a/src/frontend/packages/core/src/shared/components/favorites-entity-list/favorites-entity-list.component.html +++ b/src/frontend/packages/core/src/shared/components/favorites-entity-list/favorites-entity-list.component.html @@ -20,7 +20,7 @@ [endpointDisconnected]="endpointDisconnected">
-
+ *ngFor="let favGroup of entityGroups;trackBy: trackByEndpointId; last as isLast; count as count">
diff --git a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.scss b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.scss index 91e9b98d85..84f88ba55f 100644 --- a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.scss +++ b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.scss @@ -2,6 +2,7 @@ min-height: 160px; outline: none; &__header-text { + line-height: 24px; margin-top: 0; } &__type { diff --git a/src/frontend/packages/core/src/shared/components/json-viewer/json-viewer.component.theme.scss b/src/frontend/packages/core/src/shared/components/json-viewer/json-viewer.component.theme.scss new file mode 100644 index 0000000000..d1b982ae10 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/json-viewer/json-viewer.component.theme.scss @@ -0,0 +1,15 @@ +@mixin app-json-view-theme($theme, $app-theme) { + $is-dark: map-get($theme, is-dark); + + @if $is-dark == true { + // Keep this simple, otherwise there's a LOT of overrides. + // Additionally would need to bring in the non-dark mode colours to ensure the selectors work + app-json-viewer { + // There can be child json-viewers, ensure this only applies to the top level + &:not([root='false']) { + background-color: #ffffffeb; + border-radius: 3px; + } + } + } +} diff --git a/src/frontend/packages/core/src/shared/components/page-header/page-header.component.ts b/src/frontend/packages/core/src/shared/components/page-header/page-header.component.ts index a0ee69b500..46be7b2b61 100644 --- a/src/frontend/packages/core/src/shared/components/page-header/page-header.component.ts +++ b/src/frontend/packages/core/src/shared/components/page-header/page-header.component.ts @@ -6,7 +6,6 @@ import * as moment from 'moment'; import { Observable } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; -import { Logout } from '../../../../../store/src/actions/auth.actions'; import { ToggleSideNav } from '../../../../../store/src/actions/dashboard-actions'; import { AddRecentlyVisitedEntityAction } from '../../../../../store/src/actions/recently-visited.actions'; import { AppState } from '../../../../../store/src/app-state'; @@ -142,7 +141,7 @@ export class PageHeaderComponent implements OnDestroy, AfterViewInit { } logout() { - this.store.dispatch(new Logout()); + this.router.navigate(['/login/logout']); } public toggleSidenav() { diff --git a/src/frontend/packages/core/src/shared/components/recent-entities/recent-entities.component.scss b/src/frontend/packages/core/src/shared/components/recent-entities/recent-entities.component.scss index dd660731ee..41dac047dd 100644 --- a/src/frontend/packages/core/src/shared/components/recent-entities/recent-entities.component.scss +++ b/src/frontend/packages/core/src/shared/components/recent-entities/recent-entities.component.scss @@ -20,6 +20,7 @@ $spacing: 10px; } &--name { + line-height: 24px; word-break: break-all; } &__icon { diff --git a/src/frontend/packages/store/src/reducers/auth.reducer.ts b/src/frontend/packages/store/src/reducers/auth.reducer.ts index c8ae1c2dd7..e06f551e5a 100644 --- a/src/frontend/packages/store/src/reducers/auth.reducer.ts +++ b/src/frontend/packages/store/src/reducers/auth.reducer.ts @@ -3,6 +3,7 @@ import { LOGIN_FAILED, LOGIN_SUCCESS, LoginFailed, + LOGOUT_FAILED, RESET_AUTH, SESSION_INVALID, SESSION_VERIFIED, @@ -12,6 +13,7 @@ import { RouterActions, RouterNav } from '../actions/router.actions'; import { GET_SYSTEM_INFO_SUCCESS } from '../actions/system.actions'; import { AuthOnlyAppState } from '../app-state'; import { SessionData } from '../types/auth.types'; +import { LogoutFailed } from './../actions/auth.actions'; import { RouterRedirect } from './routing.reducer'; export interface AuthUser { @@ -51,6 +53,10 @@ export function authReducer(state: AuthState = defaultState, action): AuthState case LOGIN_FAILED: const loginFailed = action as LoginFailed; return { ...state, error: true, errorResponse: loginFailed.error, loggingIn: false, loggedIn: false }; + case LOGOUT_FAILED: + const logoutFailed = action as LogoutFailed; + console.error(logoutFailed.error); + return { ...state, loggingIn: false, loggedIn: true, error: true, errorResponse: logoutFailed.error }; case VERIFY_SESSION: return { ...state, error: false, errorResponse: undefined, verifying: true }; case SESSION_VERIFIED: diff --git a/src/frontend/packages/suse-extensions/src/custom/suse.module.ts b/src/frontend/packages/suse-extensions/src/custom/suse.module.ts index 09e8c82911..e0d45321b1 100644 --- a/src/frontend/packages/suse-extensions/src/custom/suse.module.ts +++ b/src/frontend/packages/suse-extensions/src/custom/suse.module.ts @@ -1,5 +1,4 @@ import { NgModule } from '@angular/core'; -import { Router } from '@angular/router'; import { CoreModule } from '../../../core/src/core/core.module'; import { CustomizationService, CustomizationsMetadata } from '../../../core/src/core/customizations.types'; @@ -43,20 +42,7 @@ const SuseCustomizations: CustomizationsMetadata = { ] }) export class SuseModule { - - static init = false; - - constructor(router: Router, cs: CustomizationService) { + constructor(cs: CustomizationService) { cs.set(SuseCustomizations); - - // Only update the routes once - if (!SuseModule.init) { - // Override the component used for the login route - const routeConfig = [...router.config]; - const loginRoute = routeConfig.find(r => r.path === 'login') || {}; - loginRoute.component = SuseLoginComponent; - router.resetConfig(routeConfig); - SuseModule.init = true; - } } } diff --git a/src/frontend/packages/suse-theme/_index.scss b/src/frontend/packages/suse-theme/_index.scss index 4ee0c30016..4c2b94668c 100644 --- a/src/frontend/packages/suse-theme/_index.scss +++ b/src/frontend/packages/suse-theme/_index.scss @@ -29,7 +29,7 @@ user-avatar-background-color: $suse-primary-color, user-avatar-foreground-color: $suse-text, user-avatar-header-invert-colors: false, - intro-screen-background-color: $suse-blue, + intro-screen-background-color: $suse-gray, side-nav: ( background: $suse-side-nav-bg, text: $suse-side-nav-text, diff --git a/src/test-e2e/application/application-autoscaler-e2e.spec.ts b/src/test-e2e/application/application-autoscaler-e2e.spec.ts index e4c148492f..c2656ac9a4 100644 --- a/src/test-e2e/application/application-autoscaler-e2e.spec.ts +++ b/src/test-e2e/application/application-autoscaler-e2e.spec.ts @@ -479,6 +479,8 @@ describe('Autoscaler -', () => { } function waitForRow() { + // Timeout after 32 attempts (each 5 seconds, which is just under 3 minutes) + let retries = 32; const sub = timer(5000, 5000).pipe( switchMap(() => promise.all([ findRow(), @@ -492,15 +494,24 @@ describe('Autoscaler -', () => { console.log(`${moment().toString()}: Waiting for event row: Skip actions... list is refreshing`); return; } + retries--; if (foundRow) { console.log(`${moment().toString()}: Waiting for event row: Found row!`); sub.unsubscribe(); } else { console.log(`${moment().toString()}: Waiting for event row: manually refreshing list`); eventPageBase.list.header.refresh(); + if (retries === 0) { + sub.unsubscribe(); + } } }); browser.wait(() => sub.closed); + // Fail the test if the retry count made it down to 0 + if (retries === 0) { + e2e.debugLog('Timed out waiting for event row'); + fail('Timed out waiting for event row'); + } } it('Go to events page', () => { From 8cde7b627cce568edf247aa603705e089f1fb4d7 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 24 Jul 2020 14:01:31 +0100 Subject: [PATCH 582/648] Patch 2.7.0 image to update nginx SSL configuration (#412) * Patch 2.7.0 image * Move patch files to better place --- deploy/suse/patches/nginx/Dockerfile | 4 ++ deploy/suse/patches/nginx/README.md | 3 ++ deploy/suse/patches/nginx/build.sh | 3 ++ deploy/suse/patches/nginx/nginx.conf | 76 ++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+) create mode 100644 deploy/suse/patches/nginx/Dockerfile create mode 100644 deploy/suse/patches/nginx/README.md create mode 100755 deploy/suse/patches/nginx/build.sh create mode 100644 deploy/suse/patches/nginx/nginx.conf diff --git a/deploy/suse/patches/nginx/Dockerfile b/deploy/suse/patches/nginx/Dockerfile new file mode 100644 index 0000000000..9b8cf0af03 --- /dev/null +++ b/deploy/suse/patches/nginx/Dockerfile @@ -0,0 +1,4 @@ +FROM registry.suse.com/cap/stratos-console:2.7.0-35f5964bd-cap + +COPY ./nginx.conf /etc/nginx/nginx.conf +RUN echo "Stratos 2.7.1 NGINX configuration patch" > /patches.log diff --git a/deploy/suse/patches/nginx/README.md b/deploy/suse/patches/nginx/README.md new file mode 100644 index 0000000000..d6418e6ad5 --- /dev/null +++ b/deploy/suse/patches/nginx/README.md @@ -0,0 +1,3 @@ +# 2.7.0 Patch + +These files were used to create a patch for 2.7.0 that fixes the TLS ciphers used by nginx. \ No newline at end of file diff --git a/deploy/suse/patches/nginx/build.sh b/deploy/suse/patches/nginx/build.sh new file mode 100755 index 0000000000..0214b442bd --- /dev/null +++ b/deploy/suse/patches/nginx/build.sh @@ -0,0 +1,3 @@ + +docker build -f Dockerfile . -t registry.suse.com/cap-staging/stratos-console-1:2.7.0-35f5964bd-cap +docker push registry.suse.com/cap-staging/stratos-console-1:2.7.0-35f5964bd-cap diff --git a/deploy/suse/patches/nginx/nginx.conf b/deploy/suse/patches/nginx/nginx.conf new file mode 100644 index 0000000000..eb33fc4fed --- /dev/null +++ b/deploy/suse/patches/nginx/nginx.conf @@ -0,0 +1,76 @@ +worker_processes 2; + +events { + worker_connections 4096; + use epoll; +} + +http { + + upstream portalproxy { + least_conn; + server localhost:3003; + keepalive 32; + } + + include mime.types; + default_type application/octet-stream; + keepalive_timeout 70; + proxy_read_timeout 200; + sendfile off; + tcp_nopush on; + tcp_nodelay on; + gzip on; + gzip_min_length 1000; + gzip_proxied any; + gzip_types text/plain text/html text/css text/xml + application/x-javascript application/xml + application/atom+xml text/javascript; + + proxy_next_upstream error; + + map $http_upgrade $connection_upgrade { + default upgrade; + '' ''; + } + + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + server { + listen 80; + return 301 https://$host$request_uri; + } + + server { + listen 443 ssl; + + ssl_certificate /ENCRYPTION_KEY_VOLUME/console.crt; + ssl_certificate_key /ENCRYPTION_KEY_VOLUME/console.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384; + ssl_prefer_server_ciphers on; + + client_max_body_size 50M; + + location /pp/ { + proxy_pass_header Server; + proxy_set_header Host $http_host; + proxy_redirect off; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Scheme $scheme; + proxy_pass https://portalproxy/pp/; + proxy_intercept_errors on; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } + + location / { + root /usr/share/nginx/html; + add_header Cache-Control no-cache; + add_header X-Frame-Options SAMEORIGIN; + try_files $uri$args $uri$args/ /index.html; + } + } +} \ No newline at end of file From 72986426b3f25c25df2a7a7a921579dd263b459d Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 24 Jul 2020 14:31:55 +0100 Subject: [PATCH 583/648] Merge upstream - #2 (#416) * Improve recent and favourites display (#4421) * Improve recent and favourites display * Remove debug logging * Remove debug logging/subscription leak * Unit test fix * Tweaks following review * Changes following review - favourite & recent icon changes Co-authored-by: Richard Cox * Add line-height to favourite and recent entity labels (#4438) - missed out from another PR following review * Autoscaler e2e tests: Give better failure error when the test can't find the scaling row (#4424) * Improve autoscaler e2e tests * Remove duplicate fail statement * Helm Chart: Remove encryption key volume (#4355) * Remove encryption key volume * Remove encrpytion key volume migration from config-init job * Improve the logout experience (#4439) * Add support for including breaking changes in the changelog * Improve the logout experience * Fix unit tests * Add request for version info to github issues template (#4443) * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Convert scripts to nodejs so they work on Windows (#4462) * Convert scripts to js so they work on Windows * Improve initial developer experience * Fix scss compile on windows * Fix windows build * Use fs not fs-extra where possible (can work without npm install) * Remove gulp * Cleaner fixes for Windows * Reset * Fix whitespace * Address PR feedback Co-authored-by: Neil MacDougall * Shorten file paths containing `cloudfoundry`/`cloud-foundry` (#4466) * Fix 'too long' file paths * Fix build issues * Convert scripts to js so they work on Windows * Fix scss compile on windows * Improve initial developer experience * Fix scss compile on windows * Fix windows build * Use fs not fs-extra where possible (can work without npm install) * Remove gulp * Cleaner fixes for Windows * Reset * Fix whitespace * Fix merge Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall * Fix log out page when there's a custom log in page * Fix log out page given previous commit changes Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall --- build/clean-symlinks.js | 40 +++++++++ build/clean-symlinks.sh | 28 ------- build/dev-setup.js | 30 +++++++ build/fe-build.js | 82 ------------------- build/gulp.config.js | 14 ---- build/prebuild-zip.js | 16 ++++ build/store-git-metadata.js | 38 +++++++++ deploy/ci/travis/check-e2e-pr.sh | 4 +- .../packages/backend/pre_packaging | 2 +- docs/developers-guide.md | 4 +- gulpfile.js | 4 - package-lock.json | 31 +++---- package.json | 19 ++--- .../cloud-foundry/sass/_all-theme.scss | 4 +- .../src/actions/quota-definitions.actions.ts | 2 +- .../src/cloud-foundry-routing.module.ts | 2 +- .../src/cloud-foundry-test.module.ts | 2 +- .../quota-definition.action-builders.ts | 2 +- .../space-quota.action-builders.ts | 2 +- .../application-delete.component.ts | 2 +- ...te-instances-routes-list-config.service.ts | 2 +- .../instances-tab/instances-tab.component.ts | 4 +- .../add-edit-space-step-base.ts | 10 +-- .../add-organization.component.html | 0 .../add-organization.component.scss | 0 .../add-organization.component.spec.ts | 0 .../add-organization.component.ts | 0 .../create-organization-step.component.html | 0 .../create-organization-step.component.scss | 0 ...create-organization-step.component.spec.ts | 0 .../create-organization-step.component.ts | 12 ++- .../add-quota/add-quota.component.html | 0 .../add-quota/add-quota.component.scss | 0 .../add-quota/add-quota.component.spec.ts | 0 .../add-quota/add-quota.component.ts | 0 .../create-quota-step.component.html | 0 .../create-quota-step.component.scss | 0 .../create-quota-step.component.spec.ts | 0 .../create-quota-step.component.ts | 6 +- .../add-space-quota.component.html | 0 .../add-space-quota.component.scss | 0 .../add-space-quota.component.spec.ts | 0 .../add-space-quota.component.ts | 0 .../create-space-quota-step.component.html | 0 .../create-space-quota-step.component.scss | 0 .../create-space-quota-step.component.spec.ts | 0 .../create-space-quota-step.component.ts | 6 +- .../add-space/add-space.component.html | 0 .../add-space/add-space.component.scss | 0 .../add-space/add-space.component.spec.ts | 0 .../add-space/add-space.component.ts | 0 .../create-space-step.component.html | 0 .../create-space-step.component.scss | 0 .../create-space-step.component.spec.ts | 0 .../create-space-step.component.ts | 2 +- .../{cloud-foundry => cf}/cf-cell.helpers.ts | 2 +- .../{cloud-foundry => cf}/cf-page.types.ts | 0 .../{cloud-foundry => cf}/cf.helpers.ts | 14 ++-- .../cli-info-cloud-foundry.component.html | 0 .../cli-info-cloud-foundry.component.scss | 0 .../cli-info-cloud-foundry.component.spec.ts | 0 .../cli-info-cloud-foundry.component.ts | 4 +- .../cloud-foundry-base.component.html | 0 .../cloud-foundry-base.component.scss | 0 .../cloud-foundry-base.component.spec.ts | 0 .../cloud-foundry-base.component.ts | 0 .../cloud-foundry-section.module.ts | 68 ++++++++------- .../cloud-foundry-section.routing.ts | 64 +++++++-------- .../cloud-foundry-tabs-base.component.html | 0 .../cloud-foundry-tabs-base.component.scss | 0 .../cloud-foundry-tabs-base.component.spec.ts | 0 .../cloud-foundry-tabs-base.component.ts | 0 .../cloud-foundry.component.html | 0 .../cloud-foundry.component.scss | 0 .../cloud-foundry.component.spec.ts | 0 .../cloud-foundry/cloud-foundry.component.ts | 2 +- .../edit-organization-step.component.html | 0 .../edit-organization-step.component.scss | 0 .../edit-organization-step.component.spec.ts | 0 .../edit-organization-step.component.ts | 8 +- .../edit-organization.component.html | 0 .../edit-organization.component.scss | 0 .../edit-organization.component.spec.ts | 0 .../edit-organization.component.ts | 0 .../edit-quota-step.component.html | 0 .../edit-quota-step.component.scss | 0 .../edit-quota-step.component.spec.ts | 2 +- .../edit-quota-step.component.ts | 6 +- .../edit-quota/edit-quota.component.html | 0 .../edit-quota/edit-quota.component.scss | 0 .../edit-quota/edit-quota.component.spec.ts | 0 .../edit-quota/edit-quota.component.ts | 0 .../edit-space-quota-step.component.html | 0 .../edit-space-quota-step.component.scss | 0 .../edit-space-quota-step.component.spec.ts | 2 +- .../edit-space-quota-step.component.ts | 6 +- .../edit-space-quota.component.html | 0 .../edit-space-quota.component.scss | 0 .../edit-space-quota.component.spec.ts | 0 .../edit-space-quota.component.ts | 0 .../edit-space-step.component.html | 0 .../edit-space-step.component.scss | 0 .../edit-space-step.component.spec.ts | 0 .../edit-space-step.component.ts | 2 +- .../edit-space/edit-space.component.html | 0 .../edit-space/edit-space.component.scss | 0 .../edit-space/edit-space.component.spec.ts | 0 .../edit-space/edit-space.component.ts | 0 .../quota-definition-base.component.scss | 0 .../quota-definition-base.component.ts | 0 .../quota-definition-form.component.html | 0 .../quota-definition-form.component.scss | 0 .../quota-definition-form.component.ts | 8 +- .../quota-definition.component.html | 0 .../quota-definition.component.scss | 0 .../quota-definition.component.spec.ts | 0 .../quota-definition.component.ts | 0 .../cloud-foundry-endpoint.service.ts | 28 +++---- .../services/cloud-foundry-org-space-quota.ts | 0 .../cloud-foundry-organization-quota.ts | 2 +- .../cloud-foundry-organization.service.ts | 16 ++-- .../services/cloud-foundry-space-quota.ts | 2 +- .../services/cloud-foundry-space.service.ts | 12 +-- ...space-quota-definition-form.component.html | 0 ...space-quota-definition-form.component.scss | 0 .../space-quota-definition-form.component.ts | 8 +- .../space-quota-definition.component.html | 0 .../space-quota-definition.component.scss | 0 .../space-quota-definition.component.spec.ts | 0 .../space-quota-definition.component.ts | 0 .../cf-admin-add-user-warning.component.html | 0 .../cf-admin-add-user-warning.component.scss | 0 ...f-admin-add-user-warning.component.spec.ts | 0 ...dmin-add-user-warning.component.theme.scss | 0 .../cf-admin-add-user-warning.component.ts | 4 +- .../cloud-foundry-build-packs.component.html | 0 .../cloud-foundry-build-packs.component.scss | 0 ...loud-foundry-build-packs.component.spec.ts | 0 .../cloud-foundry-build-packs.component.ts | 0 .../cloud-foundry-cell-apps.component.html | 0 .../cloud-foundry-cell-apps.component.scss | 0 .../cloud-foundry-cell-apps.component.spec.ts | 0 .../cloud-foundry-cell-apps.component.ts | 0 .../cloud-foundry-cell-base.component.html | 0 .../cloud-foundry-cell-base.component.scss | 0 .../cloud-foundry-cell-base.component.spec.ts | 0 .../cloud-foundry-cell-base.component.ts | 0 .../cloud-foundry-cell-charts.component.html | 0 .../cloud-foundry-cell-charts.component.scss | 0 ...loud-foundry-cell-charts.component.spec.ts | 0 .../cloud-foundry-cell-charts.component.ts | 0 .../cloud-foundry-cell-summary.component.html | 0 .../cloud-foundry-cell-summary.component.scss | 0 ...oud-foundry-cell-summary.component.spec.ts | 0 .../cloud-foundry-cell-summary.component.ts | 0 .../cloud-foundry-cell.service.ts | 0 .../cloud-foundry-cells.component.html | 0 .../cloud-foundry-cells.component.scss | 0 .../cloud-foundry-cells.component.spec.ts | 0 .../cloud-foundry-cells.component.ts | 0 .../cloud-foundry-events.component.html | 0 .../cloud-foundry-events.component.scss | 0 .../cloud-foundry-events.component.spec.ts | 0 .../cloud-foundry-events.component.ts | 0 ...cloud-foundry-feature-flags.component.html | 0 ...cloud-foundry-feature-flags.component.scss | 0 ...ud-foundry-feature-flags.component.spec.ts | 0 .../cloud-foundry-feature-flags.component.ts | 0 .../cloud-foundry-firehose-formatter.ts | 0 .../cloud-foundry-firehose.component.html | 0 .../cloud-foundry-firehose.component.scss | 0 .../cloud-foundry-firehose.component.spec.ts | 0 ...loud-foundry-firehose.component.theme.scss | 0 .../cloud-foundry-firehose.component.ts | 0 .../cloud-foundry-firehose.types.ts | 0 ...y-organization-space-quotas.component.html | 0 ...y-organization-space-quotas.component.scss | 0 ...rganization-space-quotas.component.spec.ts | 0 ...dry-organization-space-quotas.component.ts | 0 ...ud-foundry-invite-user-link.component.html | 0 ...ud-foundry-invite-user-link.component.scss | 0 ...foundry-invite-user-link.component.spec.ts | 0 ...loud-foundry-invite-user-link.component.ts | 0 ...d-foundry-organization-base.component.html | 0 ...d-foundry-organization-base.component.scss | 0 ...oundry-organization-base.component.spec.ts | 0 ...oud-foundry-organization-base.component.ts | 4 +- ...foundry-organization-events.component.html | 0 ...foundry-organization-events.component.scss | 0 ...ndry-organization-events.component.spec.ts | 0 ...d-foundry-organization-events.component.ts | 0 ...foundry-organization-spaces.component.html | 0 ...foundry-organization-spaces.component.scss | 0 ...ndry-organization-spaces.component.spec.ts | 0 ...d-foundry-organization-spaces.component.ts | 0 .../cloud-foundry-space-base.component.html | 0 .../cloud-foundry-space-base.component.scss | 0 ...cloud-foundry-space-base.component.spec.ts | 0 .../cloud-foundry-space-base.component.ts | 6 +- .../cloud-foundry-space-apps.component.html | 0 .../cloud-foundry-space-apps.component.scss | 0 ...cloud-foundry-space-apps.component.spec.ts | 0 .../cloud-foundry-space-apps.component.ts | 0 .../cloud-foundry-space-events.component.html | 0 .../cloud-foundry-space-events.component.scss | 0 ...oud-foundry-space-events.component.spec.ts | 0 .../cloud-foundry-space-events.component.ts | 0 .../cloud-foundry-space-routes.component.html | 0 .../cloud-foundry-space-routes.component.scss | 0 ...oud-foundry-space-routes.component.spec.ts | 0 .../cloud-foundry-space-routes.component.ts | 0 ...dry-space-service-instances.component.html | 0 ...dry-space-service-instances.component.scss | 0 ...-space-service-instances.component.spec.ts | 0 ...undry-space-service-instances.component.ts | 0 ...cloud-foundry-space-summary.component.html | 0 ...cloud-foundry-space-summary.component.scss | 0 ...ud-foundry-space-summary.component.spec.ts | 0 .../cloud-foundry-space-summary.component.ts | 0 ...pace-user-service-instances.component.html | 0 ...pace-user-service-instances.component.scss | 0 ...e-user-service-instances.component.spec.ts | 0 ...-space-user-service-instances.component.ts | 0 .../cloud-foundry-space-users.component.html | 0 .../cloud-foundry-space-users.component.scss | 0 ...loud-foundry-space-users.component.spec.ts | 4 +- .../cloud-foundry-space-users.component.ts | 0 ...oundry-organization-summary.component.html | 0 ...oundry-organization-summary.component.scss | 0 ...dry-organization-summary.component.spec.ts | 0 ...-foundry-organization-summary.component.ts | 2 +- ...-foundry-organization-users.component.html | 0 ...-foundry-organization-users.component.scss | 0 ...undry-organization-users.component.spec.ts | 4 +- ...ud-foundry-organization-users.component.ts | 0 ...cloud-foundry-organizations.component.html | 0 ...cloud-foundry-organizations.component.scss | 0 ...ud-foundry-organizations.component.spec.ts | 0 .../cloud-foundry-organizations.component.ts | 0 .../cloud-foundry-quotas.component.html | 0 .../cloud-foundry-quotas.component.scss | 0 .../cloud-foundry-quotas.component.spec.ts | 10 +-- .../cloud-foundry-quotas.component.ts | 0 .../cloud-foundry-routes.component.html | 0 .../cloud-foundry-routes.component.scss | 0 .../cloud-foundry-routes.component.spec.ts | 0 .../cloud-foundry-routes.component.ts | 0 ...oud-foundry-security-groups.component.html | 0 ...oud-foundry-security-groups.component.scss | 0 ...-foundry-security-groups.component.spec.ts | 0 ...cloud-foundry-security-groups.component.ts | 0 .../cloud-foundry-stacks.component.html | 0 .../cloud-foundry-stacks.component.scss | 0 .../cloud-foundry-stacks.component.spec.ts | 0 .../cloud-foundry-stacks.component.ts | 0 .../cloud-foundry-summary-tab.component.html | 0 .../cloud-foundry-summary-tab.component.scss | 0 ...loud-foundry-summary-tab.component.spec.ts | 0 .../cloud-foundry-summary-tab.component.ts | 2 +- .../cloud-foundry-users.component.html | 0 .../cloud-foundry-users.component.scss | 0 .../cloud-foundry-users.component.spec.ts | 0 .../cloud-foundry-users.component.ts | 6 +- ...invite-configuration-dialog.component.html | 16 +++- ...invite-configuration-dialog.component.scss | 0 ...r-invite-configuration-dialog.component.ts | 1 + .../user-invites/user-invite.service.ts | 2 +- .../invite-users-create.component.html | 0 .../invite-users-create.component.scss | 0 .../invite-users-create.component.spec.ts | 0 .../invite-users-create.component.ts | 0 .../invite-users/invite-users.component.html | 0 .../invite-users/invite-users.component.scss | 0 .../invite-users.component.spec.ts | 0 .../invite-users/invite-users.component.ts | 0 .../manage-users/cf-roles.service.spec.ts | 0 .../users/manage-users/cf-roles.service.ts | 8 +- .../manage-users-confirm.component.html | 0 .../manage-users-confirm.component.scss | 0 .../manage-users-confirm.component.spec.ts | 0 .../manage-users-confirm.component.ts | 0 .../manage-users-modify.component.html | 0 .../manage-users-modify.component.scss | 0 .../manage-users-modify.component.spec.ts | 0 .../manage-users-modify.component.ts | 0 .../space-roles-list-wrapper.component.html | 0 .../space-roles-list-wrapper.component.scss | 0 ...space-roles-list-wrapper.component.spec.ts | 0 .../space-roles-list-wrapper.component.ts | 0 .../manage-users-select.component.html | 0 .../manage-users-select.component.scss | 0 .../manage-users-select.component.spec.ts | 0 .../manage-users-select.component.ts | 0 .../manage-users-set-usernames.component.html | 0 .../manage-users-set-usernames.component.scss | 0 ...nage-users-set-usernames.component.spec.ts | 0 .../manage-users-set-usernames.component.ts | 0 .../manage-users/manage-users.component.html | 0 .../manage-users/manage-users.component.scss | 0 .../manage-users.component.spec.ts | 0 .../manage-users/manage-users.component.ts | 0 .../remove-user/remove-user.component.html | 0 .../remove-user/remove-user.component.scss | 0 .../remove-user/remove-user.component.spec.ts | 2 +- .../remove-user/remove-user.component.ts | 0 .../service-catalog-page.component.ts | 2 +- .../service-catalog/services-helper.ts | 2 +- .../card-cf-info.component.spec.ts | 2 +- .../card-cf-info/card-cf-info.component.ts | 9 +- ...card-cf-org-user-details.component.spec.ts | 4 +- .../card-cf-org-user-details.component.ts | 8 +- .../card-cf-recent-apps.component.spec.ts | 2 +- .../card-cf-recent-apps.component.ts | 5 +- .../compact-app-card.component.spec.ts | 2 +- .../compact-app-card.component.ts | 2 +- .../card-cf-space-details.component.spec.ts | 2 +- .../card-cf-space-details.component.ts | 4 +- .../card-cf-user-info.component.ts | 4 +- .../cf-role-checkbox.component.spec.ts | 4 +- .../cf-role-checkbox.component.ts | 4 +- ...loud-foundry-events-list.component.spec.ts | 4 +- .../cf-app-instances-config.service.ts | 2 +- .../app-service-binding-card.component.ts | 2 +- ...app-service-binding-list-config.service.ts | 2 +- .../list-types/app/cf-apps-data-source.ts | 4 +- .../cf-buildpacks-list-config.service.spec.ts | 2 +- .../cf-buildpacks-list-config.service.ts | 2 +- .../cf-cell-apps-list-config.service.ts | 2 +- .../cf-cell-health-list-config.service.ts | 4 +- .../cf-cells/cf-cells-list-config.service.ts | 4 +- .../types/cf-all-events-config.service.ts | 4 +- .../types/cf-org-events-config.service.ts | 4 +- .../types/cf-space-events-config.service.ts | 2 +- ...-feature-flags-list-config.service.spec.ts | 2 +- .../cf-feature-flags-list-config.service.ts | 2 +- .../cf-org-users-list-config.service.spec.ts | 10 +-- .../cf-org-users-list-config.service.ts | 6 +- .../cf-org-card/cf-org-card.component.ts | 12 +-- .../cf-orgs/cf-orgs-data-source.service.ts | 2 +- .../cf-orgs/cf-orgs-list-config.service.ts | 2 +- .../cf-quotas-list-config.service.ts | 2 +- .../cf-routes/cf-routes-data-source-base.ts | 2 +- .../cf-routes-list-config.service.ts | 2 +- ...cell-route-apps-attached.component.spec.ts | 2 +- .../cf-security-groups-card.component.spec.ts | 2 +- .../cf-security-groups-card.component.ts | 4 +- ...ecurity-groups-list-config.service.spec.ts | 2 +- .../cf-security-groups-list-config.service.ts | 2 +- .../cf-select-users-list-config.service.ts | 4 +- .../cf-service-instances-list-config.base.ts | 2 +- .../cf-services-list-config.service.spec.ts | 2 +- .../cf-services-list-config.service.ts | 4 +- .../cf-user-service-instances-list-config.ts | 2 +- .../cf-space-apps-data-source.service.ts | 2 +- .../cf-space-apps-list-config.service.spec.ts | 2 +- .../cf-space-apps-list-config.service.ts | 2 +- .../cf-space-quotas-list-config.service.ts | 2 +- ...f-space-routes-list-config.service.spec.ts | 2 +- .../cf-space-routes-list-config.service.ts | 2 +- ...cf-space-users-list-config.service.spec.ts | 12 ++- .../cf-space-users-list-config.service.ts | 8 +- ...s-service-instances-list-config.service.ts | 2 +- .../cf-space-card.component.spec.ts | 4 +- .../cf-space-card/cf-space-card.component.ts | 10 +-- .../cf-spaces-list-config.service.ts | 4 +- .../cf-stacks-list-config.service.spec.ts | 2 +- .../cf-stacks-list-config.service.ts | 2 +- ...f-users-space-roles-data-source.service.ts | 2 +- ...able-cell-org-space-role.component.spec.ts | 4 +- .../table-cell-select-org.component.spec.ts | 4 +- .../table-cell-select-org.component.ts | 4 +- .../cf-org-permission-cell.component.ts | 2 +- .../list-types/cf-users/cf-permission-cell.ts | 4 +- .../cf-space-permission-cell.component.ts | 2 +- .../cf-user-list-config.service.spec.ts | 8 +- .../cf-users/cf-user-list-config.service.ts | 4 +- ...vice-instances-wall-list-config.service.ts | 2 +- .../shared/data-services/cf-user.service.ts | 4 +- .../app-name-unique.directive.spec.ts | 2 +- .../cf-user-permission.directive.ts | 2 +- .../services/cf-org-space-label.service.ts | 2 +- ...-foundry-user-provided-services.service.ts | 2 +- .../src/store/cloud-foundry.store.module.ts | 2 +- .../src/store/effects/users-roles.effects.ts | 2 +- .../cloud-foundry-endpoint-service.helper.ts | 9 +- .../cloud-foundry-space.service.mock.ts | 2 +- .../create-endpoint-cf-step-1.component.html | 6 +- .../create-endpoint-cf-step-1.component.ts | 1 + .../edit-endpoint-step.component.html | 14 +++- .../edit-endpoint-step.component.ts | 1 + .../console-uaa-wizard.component.html | 14 +++- .../console-uaa-wizard.component.ts | 9 +- src/frontend/packages/devkit/build.js | 27 ++++++ src/frontend/packages/devkit/build.sh | 16 ---- src/frontend/packages/devkit/package.json | 2 +- .../packages/devkit/src/build/extensions.ts | 16 +++- .../packages/devkit/src/build/sass.ts | 5 +- .../packages/devkit/src/lib/packages.ts | 2 +- src/jetstream/config.dev | 55 +++++++++++++ ...fault.config.properties => config.example} | 2 +- src/jetstream/main.go | 5 +- .../application-autoscaler-e2e.spec.ts | 70 ++++++++++++---- src/test-e2e/po/form.po.ts | 2 - src/test-e2e/po/list.po.ts | 6 +- 404 files changed, 652 insertions(+), 579 deletions(-) create mode 100644 build/clean-symlinks.js delete mode 100755 build/clean-symlinks.sh create mode 100644 build/dev-setup.js delete mode 100644 build/fe-build.js delete mode 100644 build/gulp.config.js create mode 100644 build/prebuild-zip.js create mode 100644 build/store-git-metadata.js delete mode 100644 gulpfile.js rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-edit-space-step-base.ts (89%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-organization/add-organization.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-organization/add-organization.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-organization/add-organization.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-organization/add-organization.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-organization/create-organization-step/create-organization-step.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-organization/create-organization-step/create-organization-step.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-organization/create-organization-step/create-organization-step.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-organization/create-organization-step/create-organization-step.component.ts (89%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-quota/add-quota.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-quota/add-quota.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-quota/add-quota.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-quota/add-quota.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-quota/create-quota-step/create-quota-step.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-quota/create-quota-step/create-quota-step.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-quota/create-quota-step/create-quota-step.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-quota/create-quota-step/create-quota-step.component.ts (83%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-space-quota/add-space-quota.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-space-quota/add-space-quota.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-space-quota/add-space-quota.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-space-quota/add-space-quota.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-space-quota/create-space-quota-step/create-space-quota-step.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-space-quota/create-space-quota-step/create-space-quota-step.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-space-quota/create-space-quota-step/create-space-quota-step.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-space-quota/create-space-quota-step/create-space-quota-step.component.ts (86%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-space/add-space.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-space/add-space.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-space/add-space.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-space/add-space.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-space/create-space-step/create-space-step.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-space/create-space-step/create-space-step.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-space/create-space-step/create-space-step.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/add-space/create-space-step/create-space-step.component.ts (97%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cf-cell.helpers.ts (96%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cf-page.types.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cf.helpers.ts (96%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cli-info-cloud-foundry/cli-info-cloud-foundry.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cli-info-cloud-foundry/cli-info-cloud-foundry.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cli-info-cloud-foundry/cli-info-cloud-foundry.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cli-info-cloud-foundry/cli-info-cloud-foundry.component.ts (96%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cloud-foundry-base/cloud-foundry-base.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cloud-foundry-base/cloud-foundry-base.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cloud-foundry-base/cloud-foundry-base.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cloud-foundry-base/cloud-foundry-base.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cloud-foundry-section.module.ts (68%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cloud-foundry-section.routing.ts (76%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cloud-foundry/cloud-foundry.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cloud-foundry/cloud-foundry.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cloud-foundry/cloud-foundry.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/cloud-foundry/cloud-foundry.component.ts (94%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-organization/edit-organization-step/edit-organization-step.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-organization/edit-organization-step/edit-organization-step.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-organization/edit-organization-step/edit-organization-step.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-organization/edit-organization-step/edit-organization-step.component.ts (94%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-organization/edit-organization.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-organization/edit-organization.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-organization/edit-organization.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-organization/edit-organization.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-quota/edit-quota-step/edit-quota-step.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-quota/edit-quota-step/edit-quota-step.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-quota/edit-quota-step/edit-quota-step.component.spec.ts (93%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-quota/edit-quota-step/edit-quota-step.component.ts (88%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-quota/edit-quota.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-quota/edit-quota.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-quota/edit-quota.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-quota/edit-quota.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.spec.ts (93%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.ts (89%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-space-quota/edit-space-quota.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-space-quota/edit-space-quota.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-space-quota/edit-space-quota.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-space-quota/edit-space-quota.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-space/edit-space-step/edit-space-step.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-space/edit-space-step/edit-space-step.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-space/edit-space-step/edit-space-step.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-space/edit-space-step/edit-space-step.component.ts (98%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-space/edit-space.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-space/edit-space.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-space/edit-space.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/edit-space/edit-space.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/quota-definition-base/quota-definition-base.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/quota-definition-base/quota-definition-base.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/quota-definition-form/quota-definition-form.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/quota-definition-form/quota-definition-form.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/quota-definition-form/quota-definition-form.component.ts (88%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/quota-definition/quota-definition.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/quota-definition/quota-definition.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/quota-definition/quota-definition.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/quota-definition/quota-definition.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/services/cloud-foundry-endpoint.service.ts (95%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/services/cloud-foundry-org-space-quota.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/services/cloud-foundry-organization-quota.ts (95%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/services/cloud-foundry-organization.service.ts (98%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/services/cloud-foundry-space-quota.ts (95%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/services/cloud-foundry-space.service.ts (98%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/space-quota-definition-form/space-quota-definition-form.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/space-quota-definition-form/space-quota-definition-form.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/space-quota-definition-form/space-quota-definition-form.component.ts (88%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/space-quota-definition/space-quota-definition.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/space-quota-definition/space-quota-definition.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/space-quota-definition/space-quota-definition.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/space-quota-definition/space-quota-definition.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.theme.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.ts (87%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-build-packs => cf/tabs/cf-build-packs}/cloud-foundry-build-packs.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-build-packs => cf/tabs/cf-build-packs}/cloud-foundry-build-packs.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-build-packs => cf/tabs/cf-build-packs}/cloud-foundry-build-packs.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-build-packs => cf/tabs/cf-build-packs}/cloud-foundry-build-packs.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cell/cloud-foundry-cell.service.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cells.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cells.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cells.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-cells => cf/tabs/cf-cells}/cloud-foundry-cells.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-events => cf/tabs/cf-events}/cloud-foundry-events.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-events => cf/tabs/cf-events}/cloud-foundry-events.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-events => cf/tabs/cf-events}/cloud-foundry-events.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-events => cf/tabs/cf-events}/cloud-foundry-events.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-feature-flags => cf/tabs/cf-feature-flags}/cloud-foundry-feature-flags.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-feature-flags => cf/tabs/cf-feature-flags}/cloud-foundry-feature-flags.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-feature-flags => cf/tabs/cf-feature-flags}/cloud-foundry-feature-flags.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-feature-flags => cf/tabs/cf-feature-flags}/cloud-foundry-feature-flags.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-firehose => cf/tabs/cf-firehose}/cloud-foundry-firehose-formatter.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-firehose => cf/tabs/cf-firehose}/cloud-foundry-firehose.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-firehose => cf/tabs/cf-firehose}/cloud-foundry-firehose.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-firehose => cf/tabs/cf-firehose}/cloud-foundry-firehose.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-firehose => cf/tabs/cf-firehose}/cloud-foundry-firehose.component.theme.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-firehose => cf/tabs/cf-firehose}/cloud-foundry-firehose.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-firehose => cf/tabs/cf-firehose}/cloud-foundry-firehose.types.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organization-space-quotas => cf/tabs/cf-organization-space-quotas}/cloud-foundry-organization-space-quotas.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organization-space-quotas => cf/tabs/cf-organization-space-quotas}/cloud-foundry-organization-space-quotas.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organization-space-quotas => cf/tabs/cf-organization-space-quotas}/cloud-foundry-organization-space-quotas.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organization-space-quotas => cf/tabs/cf-organization-space-quotas}/cloud-foundry-organization-space-quotas.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-invite-user-link => cf/tabs/cf-organizations/cf-invite-user-link}/cloud-foundry-invite-user-link.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-invite-user-link => cf/tabs/cf-organizations/cf-invite-user-link}/cloud-foundry-invite-user-link.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-invite-user-link => cf/tabs/cf-organizations/cf-invite-user-link}/cloud-foundry-invite-user-link.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-invite-user-link => cf/tabs/cf-organizations/cf-invite-user-link}/cloud-foundry-invite-user-link.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-base => cf/tabs/cf-organizations/cf-organization-base}/cloud-foundry-organization-base.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-base => cf/tabs/cf-organizations/cf-organization-base}/cloud-foundry-organization-base.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-base => cf/tabs/cf-organizations/cf-organization-base}/cloud-foundry-organization-base.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-base => cf/tabs/cf-organizations/cf-organization-base}/cloud-foundry-organization-base.component.ts (95%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-events => cf/tabs/cf-organizations/cf-organization-events}/cloud-foundry-organization-events.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-events => cf/tabs/cf-organizations/cf-organization-events}/cloud-foundry-organization-events.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-events => cf/tabs/cf-organizations/cf-organization-events}/cloud-foundry-organization-events.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-events => cf/tabs/cf-organizations/cf-organization-events}/cloud-foundry-organization-events.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/cloud-foundry-organization-spaces.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/cloud-foundry-organization-spaces.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/cloud-foundry-organization-spaces.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/cloud-foundry-organization-spaces.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/cloud-foundry-space-base/cloud-foundry-space-base.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/cloud-foundry-space-base/cloud-foundry-space-base.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/cloud-foundry-space-base/cloud-foundry-space-base.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/cloud-foundry-space-base/cloud-foundry-space-base.component.ts (95%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.spec.ts (93%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces => cf/tabs/cf-organizations/cf-organization-spaces}/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary => cf/tabs/cf-organizations/cf-organization-summary}/cloud-foundry-organization-summary.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary => cf/tabs/cf-organizations/cf-organization-summary}/cloud-foundry-organization-summary.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary => cf/tabs/cf-organizations/cf-organization-summary}/cloud-foundry-organization-summary.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary => cf/tabs/cf-organizations/cf-organization-summary}/cloud-foundry-organization-summary.component.ts (97%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users => cf/tabs/cf-organizations/cf-organization-users}/cloud-foundry-organization-users.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users => cf/tabs/cf-organizations/cf-organization-users}/cloud-foundry-organization-users.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users => cf/tabs/cf-organizations/cf-organization-users}/cloud-foundry-organization-users.component.spec.ts (92%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users => cf/tabs/cf-organizations/cf-organization-users}/cloud-foundry-organization-users.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations => cf/tabs/cf-organizations}/cloud-foundry-organizations.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations => cf/tabs/cf-organizations}/cloud-foundry-organizations.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations => cf/tabs/cf-organizations}/cloud-foundry-organizations.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-organizations => cf/tabs/cf-organizations}/cloud-foundry-organizations.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-quotas => cf/tabs/cf-quotas}/cloud-foundry-quotas.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-quotas => cf/tabs/cf-quotas}/cloud-foundry-quotas.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-quotas => cf/tabs/cf-quotas}/cloud-foundry-quotas.component.spec.ts (71%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-quotas => cf/tabs/cf-quotas}/cloud-foundry-quotas.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-routes => cf/tabs/cf-routes}/cloud-foundry-routes.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-routes => cf/tabs/cf-routes}/cloud-foundry-routes.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-routes => cf/tabs/cf-routes}/cloud-foundry-routes.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-routes => cf/tabs/cf-routes}/cloud-foundry-routes.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-security-groups => cf/tabs/cf-security-groups}/cloud-foundry-security-groups.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-security-groups => cf/tabs/cf-security-groups}/cloud-foundry-security-groups.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-security-groups => cf/tabs/cf-security-groups}/cloud-foundry-security-groups.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-security-groups => cf/tabs/cf-security-groups}/cloud-foundry-security-groups.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-stacks => cf/tabs/cf-stacks}/cloud-foundry-stacks.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-stacks => cf/tabs/cf-stacks}/cloud-foundry-stacks.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-stacks => cf/tabs/cf-stacks}/cloud-foundry-stacks.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-stacks => cf/tabs/cf-stacks}/cloud-foundry-stacks.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-summary-tab => cf/tabs/cf-summary-tab}/cloud-foundry-summary-tab.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-summary-tab => cf/tabs/cf-summary-tab}/cloud-foundry-summary-tab.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-summary-tab => cf/tabs/cf-summary-tab}/cloud-foundry-summary-tab.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-summary-tab => cf/tabs/cf-summary-tab}/cloud-foundry-summary-tab.component.ts (93%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-users => cf/tabs/cf-users}/cloud-foundry-users.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-users => cf/tabs/cf-users}/cloud-foundry-users.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-users => cf/tabs/cf-users}/cloud-foundry-users.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry/tabs/cloud-foundry-users => cf/tabs/cf-users}/cloud-foundry-users.component.ts (84%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/user-invites/configuration-dialog/user-invite-configuration-dialog.component.html (63%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/user-invites/configuration-dialog/user-invite-configuration-dialog.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/user-invites/configuration-dialog/user-invite-configuration-dialog.component.ts (98%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/user-invites/user-invite.service.ts (98%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/invite-users/invite-users-create/invite-users-create.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/invite-users/invite-users-create/invite-users-create.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/invite-users/invite-users-create/invite-users-create.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/invite-users/invite-users-create/invite-users-create.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/invite-users/invite-users.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/invite-users/invite-users.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/invite-users/invite-users.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/invite-users/invite-users.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/cf-roles.service.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/cf-roles.service.ts (99%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users-confirm/manage-users-confirm.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users-confirm/manage-users-confirm.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users-confirm/manage-users-confirm.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users-confirm/manage-users-confirm.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users-modify/manage-users-modify.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users-modify/manage-users-modify.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users-modify/manage-users-modify.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users-modify/manage-users-modify.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users-select/manage-users-select.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users-select/manage-users-select.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users-select/manage-users-select.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users-select/manage-users-select.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users.component.spec.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/manage-users/manage-users.component.ts (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/remove-user/remove-user.component.html (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/remove-user/remove-user.component.scss (100%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/remove-user/remove-user.component.spec.ts (96%) rename src/frontend/packages/cloud-foundry/src/features/{cloud-foundry => cf}/users/remove-user/remove-user.component.ts (100%) create mode 100644 src/frontend/packages/devkit/build.js delete mode 100755 src/frontend/packages/devkit/build.sh create mode 100644 src/jetstream/config.dev rename src/jetstream/{default.config.properties => config.example} (98%) diff --git a/build/clean-symlinks.js b/build/clean-symlinks.js new file mode 100644 index 0000000000..9ae0c0aed1 --- /dev/null +++ b/build/clean-symlinks.js @@ -0,0 +1,40 @@ +// Clean any symlinks from a pre 4.0 Stratos +// These are no longer used for customization and need to be removed + +// Implemented as a single script here so that it works on Windows, Linux and Mac + +const path = require('path'); +const fs = require('fs'); + +// __dirname is the folder where build.js is located +const STRATOS_DIR= path.resolve(__dirname, '..'); + +function processFile(filepath) { + if (fs.existsSync(filepath)) { + const stats = fs.lstatSync(filepath); + if (stats.isSymbolicLink()) { + console.log(`Removing symlink ${filepath}`); + fs.unlinkSync(filepath); + } + } +} + +function processFolder(dir) { + if (!fs.existsSync(dir)) { + return + } + fs.readdirSync(dir).forEach( f => { + let dirPath = path.join(dir, f); + const realPath = fs.realpathSync(dirPath); + const stats = fs.lstatSync(realPath); + if (stats.isDirectory()) { + processFolder(dirPath); + } else { + processFile(dirPath); + } + }); +}; + +processFolder(path.join(STRATOS_DIR, 'src', 'frontend', 'packages', 'core', 'sass')); +processFolder(path.join(STRATOS_DIR, 'src', 'frontend', 'packages', 'core', 'assets')); +processFile(path.join(STRATOS_DIR, 'src', 'frontend', 'packages', 'core', 'favicon.ico')); diff --git a/build/clean-symlinks.sh b/build/clean-symlinks.sh deleted file mode 100755 index 0ff35b104b..0000000000 --- a/build/clean-symlinks.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash - -# Clean any symlinks from a pre 4.0 Stratos -# These are no longer used for customization and need to be removed - -set -euo pipefail - -# Script folder -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" -STRATOS="`cd "${DIR}/..";pwd`" - -function processFile { - filename=$1 - if [ -L "$filename" ]; then - echo Removing symlink $filename - rm $filename - fi -} - -function processFolder { - for filename in $1; do - processFile $filename - done -} - -processFolder "${STRATOS}/src/frontend/packages/core/sass/*.*" -processFolder "${STRATOS}/src/frontend/packages/core/assets/*.*" -processFile "${STRATOS}/src/frontend/packages/core/favicon.ico" diff --git a/build/dev-setup.js b/build/dev-setup.js new file mode 100644 index 0000000000..77a7d6abc1 --- /dev/null +++ b/build/dev-setup.js @@ -0,0 +1,30 @@ +// Copy files required for developer quick start +// Implemented as a single script here so that it works on Windows, Linux and Mac + +const path = require('path'); +const fs = require('fs'); + +// __dirname is the folder where build.js is located +const STRATOS_DIR= path.resolve(__dirname, '..'); + +// Only copy files if they are not already there - just make sure initial versions are in place for developer + +// Proxy config file +const PROXY_CONF = path.join(STRATOS_DIR, 'proxy.conf.js'); +if (!fs.existsSync(PROXY_CONF)) { + let err = fs.copyFileSync(path.join(__dirname, 'proxy.conf.localdev.js'), PROXY_CONF); + if (err) { + console.log(err); + } +} + +// config.properties +const BACKEND_DIR = path.join(STRATOS_DIR, 'src', 'jetstream'); +const BACKEND_CONF = path.join(BACKEND_DIR, 'config.properties'); +const BACKEND_CONF_DEV = path.join(BACKEND_DIR, 'config.dev'); +if (!fs.existsSync(BACKEND_CONF)) { + let err = fs.copyFileSync(BACKEND_CONF_DEV, BACKEND_CONF); + if (err) { + console.log(err); + } +} diff --git a/build/fe-build.js b/build/fe-build.js deleted file mode 100644 index 75946dd93c..0000000000 --- a/build/fe-build.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Gulp build file for Angular 2 Front End UI Code - */ -/* eslint-disable angular/log,no-console,no-process-env,angular/json-functions,no-sync */ -(function () { - 'use strict'; - - var gulp = require('gulp'); - // var _ = require('lodash'); - var del = require('delete'); - var spawn = require('child_process').spawn; - var path = require('path'); - var os = require('os'); - var zip = require('gulp-zip'); - var fs = require('fs-extra'); - - var config = require('./gulp.config'); - var paths = config.paths; - - // Clean dist dir - gulp.task('clean', function (next) { - del(paths.dist + '**/*', { - force: true - }, next); - }); - - // Package pre-built UI for the buildpack to detect - gulp.task('package-prebuild', function () { - return gulp.src('dist/**/*') - .pipe(zip('stratos-frontend-prebuild.zip')) - .pipe(gulp.dest('.')) - }); - - gulp.task('dev-setup', function (cb) { - // Copy proxy.conf.js so the front-end is all ready to go against a local backend - if not already exsiting - var proxyConf = path.resolve(__dirname, '../proxy.conf.js'); - var localProxyConf = path.resolve(__dirname, './proxy.conf.localdev.js'); - if (!fs.existsSync(proxyConf)) { - fs.copySync(localProxyConf, proxyConf); - } - cb(); - }); - - // Legacy task name - gulp.task('clean:dist', gulp.series('clean')); - - gulp.task('ng-build', function (cb) { - var rootFolder = path.resolve(__dirname, '..'); - var cmd = 'npm'; - var windowsEnvironment = os.platform().startsWith('win'); - if (windowsEnvironment) { - cmd = 'npm.cmd'; - } - var child = spawn(cmd, ['run', 'build'], { - cwd: rootFolder - }); - child.stdout.on('data', function (data) { - console.log(data.toString()); - }); - child.stderr.on('data', function (data) { - console.log(data.toString()); - }); - child.on('error', function (err) { - console.log(err); - cb(err); - }); - child.on('close', function (code) { - var err = code === 0 ? undefined : 'Build exited with code: ' + code; - cb(err); - }); - }); - - // Production build - gulp.task('build', gulp.series( - 'clean', - 'ng-build' - )); - - // Default task is to build for production - gulp.task('default', gulp.series('build')); - -})(); diff --git a/build/gulp.config.js b/build/gulp.config.js deleted file mode 100644 index 683b24bbb0..0000000000 --- a/build/gulp.config.js +++ /dev/null @@ -1,14 +0,0 @@ -(function () { - 'use strict'; - - // This stores all the configuration information for Gulp - var paths = { - dist: './dist/', - ui: './ui' - }; - - // Now returned as an object so require always returns same object - module.exports = { - paths: paths - }; -})(); diff --git a/build/prebuild-zip.js b/build/prebuild-zip.js new file mode 100644 index 0000000000..6fb3253396 --- /dev/null +++ b/build/prebuild-zip.js @@ -0,0 +1,16 @@ +// Zip the dist folder +// Implemented as a single script here so that it works on Windows, Linux and Mac + +const path = require('path'); +const fs = require('fs'); +const AdmZip = require('adm-zip'); + +// __dirname is the folder where build.js is located +const STRATOS_DIR= path.resolve(__dirname, '..'); +const DIST_DIR= path.join(STRATOS_DIR, 'dist'); +const ZIP_FILE= path.join(STRATOS_DIR, 'stratos-frontend-prebuild.zip'); + +var zip = new AdmZip(); + +zip.addLocalFolder(DIST_DIR); +zip.writeZip(path.join(ZIP_FILE)); \ No newline at end of file diff --git a/build/store-git-metadata.js b/build/store-git-metadata.js new file mode 100644 index 0000000000..36825cd654 --- /dev/null +++ b/build/store-git-metadata.js @@ -0,0 +1,38 @@ +// Generate the git metadata file + +// Implemented as a single script here so that it works on Windows, Linux and Mac + +const path = require('path'); +const fs = require('fs'); +const execSync = require('child_process').execSync; + +// __dirname is the folder where build.js is located +const STRATOS_DIR = path.resolve(__dirname, '..'); +const GIT_FOLDER = path.join(STRATOS_DIR, '.git'); +const GIT_METADATA = path.join(STRATOS_DIR, '.stratos-git-metadata.json'); + +function execGit(cmd) { + try { + var response = execSync(cmd); + return response.toString().trim(); + } catch (e) { + console.log(e) + return ''; + } +} + +// We can only do this if we have a git repository checkout +// We'll store this in a file which we will then use - when in environments like Docker, we will run this +// in the host environment so that we can pick it up when we're running in the Docker world +// Do we have a git folder? +if (!fs.existsSync(GIT_FOLDER)) { + console.log(' + Unable to store git repository metadata - .git folder not found'); + return; +} +var gitMetadata = { + project: execGit('git config --get remote.origin.url'), + branch: execGit('git rev-parse --abbrev-ref HEAD'), + commit: execGit('git rev-parse HEAD') +}; + +fs.writeFileSync(GIT_METADATA, JSON.stringify(gitMetadata, null, 2)); diff --git a/deploy/ci/travis/check-e2e-pr.sh b/deploy/ci/travis/check-e2e-pr.sh index 89ab5b6346..5ba336a72c 100755 --- a/deploy/ci/travis/check-e2e-pr.sh +++ b/deploy/ci/travis/check-e2e-pr.sh @@ -2,8 +2,8 @@ if [ -n "${TRAVIS_PULL_REQUEST}" ]; then if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then - echo "Checking labels on ${TRAVIS_PULL_REQUEST_SLUG} #${TRAVIS_PULL_REQUEST}" - LABEL=$(curl -s "https://api.github.com/repos/${TRAVIS_PULL_REQUEST_SLUG}/pulls/${TRAVIS_PULL_REQUEST}" | jq -r '.labels[] | select(.name == "e2e-debug") | .name') + echo "Checking labels on ${TRAVIS_REPO_SLUG} #${TRAVIS_PULL_REQUEST}" + LABEL=$(curl -s "https://api.github.com/repos/${TRAVIS_REPO_SLUG}/pulls/${TRAVIS_PULL_REQUEST}" | jq -r '.labels[] | select(.name == "e2e-debug") | .name') if [ "${LABEL}" == "e2e-debug" ]; then echo "PR has the 'e2e-debug' label - enabling debug logging for E2E tests" export STRATOS_E2E_DEBUG=true diff --git a/deploy/stratos-ui-release/packages/backend/pre_packaging b/deploy/stratos-ui-release/packages/backend/pre_packaging index ba252553a7..65abaaabb3 100644 --- a/deploy/stratos-ui-release/packages/backend/pre_packaging +++ b/deploy/stratos-ui-release/packages/backend/pre_packaging @@ -11,7 +11,7 @@ curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh # Build backend npm install export PATH=$PATH:$PWD/node_modules/.bin -npm run bosh-build-backend +npm run build-backend find ../stratos/deploy -type d ! -path '../stratos/deploy' ! -path '*/db' -maxdepth 1 | xargs rm -rf diff --git a/docs/developers-guide.md b/docs/developers-guide.md index eb36575687..9d9929d16e 100644 --- a/docs/developers-guide.md +++ b/docs/developers-guide.md @@ -212,7 +212,7 @@ db provider this can be done by deleting `src/jetstream/console-database.db` #### Configure by Environment Variables and/or Config File -By default, the configuration in file `src/jetstream/default.config.properties` will be used. These can be changed by environment variables +By default, the configuration in file `src/jetstream/config.properties` will be used. These can be changed by environment variables or an overrides file. ##### Environment variable @@ -238,7 +238,7 @@ If you have a custom uaa, ensure you have set the following environment variable ##### Config File -To easily persist configuration settings copy `src/jetstream/default.config.properties` to `src/jetstream/config.properties`. The backend will load its +To easily persist configuration settings copy `src/jetstream/config.example` to `src/jetstream/config.properties`. The backend will load its configuration from this file in preference to the default config file, if it exists. You can also modify individual configuration settings by setting the corresponding environment variable. diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index d5e126f981..0000000000 --- a/gulpfile.js +++ /dev/null @@ -1,4 +0,0 @@ -(function () { - 'use strict'; - require('./build/fe-build'); -})(); diff --git a/package-lock.json b/package-lock.json index fdfea562d5..ae80a769d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3193,9 +3193,9 @@ } }, "@tootallnate/once": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.0.0.tgz", - "integrity": "sha512-KYyTT/T6ALPkIRd2Ge080X/BsXvy9O0hcWTtMWkPvwAwF99+vn6Dv4GzrFT/Nn1LePr+FFDbRXXlqmsy9lw2zA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true }, "@tweenjs/tween.js": { @@ -3630,9 +3630,9 @@ } }, "adm-zip": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.14.tgz", - "integrity": "sha512-/9aQCnQHF+0IiCl0qhXoK7qs//SwYE7zX8lsr/DNk1BRAHYxeLZPL4pguwK29gUEqasYQjqPtEpDRSWEkdHn9g==", + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", + "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", "dev": true }, "after": { @@ -5464,9 +5464,9 @@ "dev": true }, "codecov": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/codecov/-/codecov-3.7.0.tgz", - "integrity": "sha512-uIixKofG099NbUDyzRk1HdGtaG8O+PBUAg3wfmjwXw2+ek+PZp+puRvbTohqrVfuudaezivJHFgTtSC3M8MXww==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/codecov/-/codecov-3.7.1.tgz", + "integrity": "sha512-JHWxyPTkMLLJn9SmKJnwAnvY09kg2Os2+Ux+GG7LwZ9g8gzDDISpIN5wAsH1UBaafA/yGcd3KofMaorE8qd6Lw==", "dev": true, "requires": { "argv": "0.0.2", @@ -11832,9 +11832,10 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "dev": true }, "lodash-es": { "version": "4.17.15", @@ -19302,9 +19303,9 @@ }, "dependencies": { "agent-base": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", - "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", + "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", "dev": true, "requires": { "debug": "4" diff --git a/package.json b/package.json index 53e0ea9544..32d6ab56b1 100644 --- a/package.json +++ b/package.json @@ -8,13 +8,13 @@ "build-chartsync": "./build/chartsync-build.sh", "full-build-dev": "npm run build-dev; npm run build-backend", "fetch-backend-deps": "./build/bk-fetch-deps.sh", - "bosh-build-backend": "gulp bosh-build-backend", "test-backend": "./build/bk-build.sh test", "update-webdriver": "webdriver-manager update", "build": "node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng build --prod", "build-cf": "node --max_old_space_size=1500 --gc_interval=100 node_modules/@angular/cli/bin/ng build --prod", "build-dev": "ng build --dev", - "prebuild-ui": "npm run build && gulp package-prebuild", + "prebuild-ui": "npm run build && npm run prebuild-zip", + "prebuild-zip": "node build/prebuild-zip.js", "ng": "ng", "start": "ng serve", "start-high-mem": "node --max_old_space_size=8192 node_modules/@angular/cli/bin/ng serve", @@ -38,10 +38,11 @@ "headless-e2e": "xvfb-run --server-args='-screen 0 1920x1080x24' protractor ./protractor.conf.js", "climate": "codeclimate analyze $(git diff --name-only master)", "gate-check": "npm run lint && npm run test-headless", - "store-git-metadata": "gulp store-git-metadata", - "postinstall": "gulp dev-setup && npm run build-devkit && npm run clean-symlinks", + "store-git-metadata": "node build/store-git-metadata.js", + "postinstall": "npm run dev-setup && npm run build-devkit && npm run clean-symlinks && npm run store-git-metadata", "build-devkit": "cd src/frontend/packages/devkit && npm run build", - "clean-symlinks": "./build/clean-symlinks.sh" + "clean-symlinks": "node build/clean-symlinks.js", + "dev-setup": "node build/dev-setup.js" }, "author": "", "license": "Apache-2.0", @@ -111,15 +112,14 @@ "@types/node": "^13.11.1", "@types/request": "^2.48.4", "acorn": "^7.1.1", + "adm-zip": "^0.4.16", "browserstack-local": "^1.4.5", - "codecov": "^3.6.5", + "codecov": "^3.7.1", "codelyzer": "^5.1.2", "copy-webpack-plugin": "5.1.1", "delete": "^1.1.0", "fs-extra": "^9.0.0", "globby": "^11.0.0", - "gulp": "^4.0.2", - "gulp-zip": "^5.0.1", "istanbul": "^0.4.5", "istanbul-api": "2.1.6", "istanbul-reports": "3.0.2", @@ -136,7 +136,7 @@ "karma-jasmine-html-reporter": "^1.5.3", "karma-spec-reporter": "0.0.32", "kind-of": "^6.0.3", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "mem": "6.1.0", "mktemp": "^1.0.0", "ng-packagr": "^9.1.1", @@ -145,7 +145,6 @@ "protractor": "^5.4.3", "ps-node": "^0.1.6", "q": "^1.4.1", - "replace-in-file": "^5.0.2", "request": "^2.88.2", "request-promise-native": "^1.0.8", "rxjs-tslint": "^0.1.8", diff --git a/src/frontend/packages/cloud-foundry/sass/_all-theme.scss b/src/frontend/packages/cloud-foundry/sass/_all-theme.scss index b344095253..8818f59d8a 100644 --- a/src/frontend/packages/cloud-foundry/sass/_all-theme.scss +++ b/src/frontend/packages/cloud-foundry/sass/_all-theme.scss @@ -7,10 +7,10 @@ @import '../src/shared/components/list/list-types/cf-security-groups/cf-security-groups-card/cf-security-groups-card.component.theme'; @import '../src/shared/components/schema-form/schema-form.component.theme'; -@import '../src/features/cloud-foundry/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.theme'; +@import '../src/features/cf/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.theme'; @import '../src/features/applications/deploy-application/deploy-application.component.theme'; @import '../src/features/applications/deploy-application/deploy-application-step2/deploy-application-fs/deploy-application-fs.component.theme'; -@import '../src/features/cloud-foundry/tabs/cloud-foundry-firehose/cloud-foundry-firehose.component.theme'; +@import '../src/features/cf/tabs/cf-firehose/cloud-foundry-firehose.component.theme'; @mixin apply-theme-stratos-cloud-foundry($stratos-theme) { diff --git a/src/frontend/packages/cloud-foundry/src/actions/quota-definitions.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/quota-definitions.actions.ts index e06451854c..84ddf52a57 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/quota-definitions.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/quota-definitions.actions.ts @@ -7,7 +7,7 @@ import { cfEntityFactory } from '../cf-entity-factory'; import { organizationEntityType, quotaDefinitionEntityType, spaceQuotaEntityType } from '../cf-entity-types'; import { CFEntityConfig } from '../cf-types'; import { EntityInlineChildAction, EntityInlineParentAction } from '../entity-relations/entity-relations.types'; -import { QuotaFormValues } from '../features/cloud-foundry/quota-definition-form/quota-definition-form.component'; +import { QuotaFormValues } from '../features/cf/quota-definition-form/quota-definition-form.component'; import { CFStartAction } from './cf-action.types'; export const GET_QUOTA_DEFINITION = '[QuotaDefinition] Get one'; diff --git a/src/frontend/packages/cloud-foundry/src/cloud-foundry-routing.module.ts b/src/frontend/packages/cloud-foundry/src/cloud-foundry-routing.module.ts index 0266310650..61bfb111fb 100644 --- a/src/frontend/packages/cloud-foundry/src/cloud-foundry-routing.module.ts +++ b/src/frontend/packages/cloud-foundry/src/cloud-foundry-routing.module.ts @@ -42,7 +42,7 @@ const customRoutes: Routes = [ }, { path: 'cloud-foundry', - loadChildren: () => import('./features/cloud-foundry/cloud-foundry-section.module').then(m => m.CloudFoundrySectionModule), + loadChildren: () => import('./features/cf/cloud-foundry-section.module').then(m => m.CloudFoundrySectionModule), data: { stratosNavigation: { label: 'Cloud Foundry', diff --git a/src/frontend/packages/cloud-foundry/src/cloud-foundry-test.module.ts b/src/frontend/packages/cloud-foundry/src/cloud-foundry-test.module.ts index 5bb932f851..2bc284c36c 100644 --- a/src/frontend/packages/cloud-foundry/src/cloud-foundry-test.module.ts +++ b/src/frontend/packages/cloud-foundry/src/cloud-foundry-test.module.ts @@ -11,7 +11,7 @@ import { generateStratosEntities } from '../../store/src/stratos-entity-generato import { testSCFEndpointGuid } from '../../store/testing/public-api'; import { BaseCfOrgSpaceRouteMock } from '../test-framework/cloud-foundry-endpoint-service.helper'; import { generateCFEntities } from './cf-entity-generator'; -import { ActiveRouteCfOrgSpace } from './features/cloud-foundry/cf-page.types'; +import { ActiveRouteCfOrgSpace } from './features/cf/cf-page.types'; import { CfUserService } from './shared/data-services/cf-user.service'; import { LongRunningCfOperationsService } from './shared/data-services/long-running-cf-op.service'; import { GitSCMService } from './shared/data-services/scm/scm.service'; diff --git a/src/frontend/packages/cloud-foundry/src/entity-action-builders/quota-definition.action-builders.ts b/src/frontend/packages/cloud-foundry/src/entity-action-builders/quota-definition.action-builders.ts index 4641cb90da..4576dd019f 100644 --- a/src/frontend/packages/cloud-foundry/src/entity-action-builders/quota-definition.action-builders.ts +++ b/src/frontend/packages/cloud-foundry/src/entity-action-builders/quota-definition.action-builders.ts @@ -6,7 +6,7 @@ import { UpdateQuotaDefinition, } from '../actions/quota-definitions.actions'; import { CFBasePipelineRequestActionMeta } from '../cf-entity-generator'; -import { QuotaFormValues } from '../features/cloud-foundry/quota-definition-form/quota-definition-form.component'; +import { QuotaFormValues } from '../features/cf/quota-definition-form/quota-definition-form.component'; export interface QuotaDefinitionActionBuilder extends OrchestratedActionBuilders { get: ( diff --git a/src/frontend/packages/cloud-foundry/src/entity-action-builders/space-quota.action-builders.ts b/src/frontend/packages/cloud-foundry/src/entity-action-builders/space-quota.action-builders.ts index 68d96b2c80..aec68cfd2e 100644 --- a/src/frontend/packages/cloud-foundry/src/entity-action-builders/space-quota.action-builders.ts +++ b/src/frontend/packages/cloud-foundry/src/entity-action-builders/space-quota.action-builders.ts @@ -8,7 +8,7 @@ import { UpdateSpaceQuotaDefinition, } from '../actions/quota-definitions.actions'; import { CFBasePipelineRequestActionMeta } from '../cf-entity-generator'; -import { QuotaFormValues } from '../features/cloud-foundry/quota-definition-form/quota-definition-form.component'; +import { QuotaFormValues } from '../features/cf/quota-definition-form/quota-definition-form.component'; export interface SpaceQuotaDefinitionActionBuilders extends OrchestratedActionBuilders { get: ( diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/application-delete.component.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/application-delete.component.ts index 956f90192e..b12daa358f 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/application-delete.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/application-delete.component.ts @@ -47,7 +47,7 @@ import { import { TableCellTCPRouteComponent, } from '../../../shared/components/list/list-types/cf-routes/table-cell-tcproute/table-cell-tcproute.component'; -import { isServiceInstance, isUserProvidedServiceInstance } from '../../cloud-foundry/cf.helpers'; +import { isServiceInstance, isUserProvidedServiceInstance } from '../../cf/cf.helpers'; import { ApplicationService } from '../application.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-instances/app-delete-instances-routes-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-instances/app-delete-instances-routes-list-config.service.ts index f8b961dfba..f3db600307 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-instances/app-delete-instances-routes-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/delete-app-instances/app-delete-instances-routes-list-config.service.ts @@ -21,7 +21,7 @@ import { AppServiceBindingListConfigService, } from '../../../../shared/components/list/list-types/app-sevice-bindings/app-service-binding-list-config.service'; import { ServiceActionHelperService } from '../../../../shared/data-services/service-action-helper.service'; -import { fetchTotalResults } from '../../../cloud-foundry/cf.helpers'; +import { fetchTotalResults } from '../../../cf/cf.helpers'; import { ApplicationService } from '../../application.service'; @Injectable() diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/instances-tab/instances-tab.component.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/instances-tab/instances-tab.component.ts index bff084c141..a9be4072a6 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/instances-tab/instances-tab.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/instances-tab/instances-tab.component.ts @@ -5,8 +5,8 @@ import { CF_GUID } from '../../../../../../../../core/src/shared/entity.tokens'; import { CfAppInstancesConfigService, } from '../../../../../../shared/components/list/list-types/app-instance/cf-app-instances-config.service'; -import { ActiveRouteCfOrgSpace } from '../../../../../cloud-foundry/cf-page.types'; -import { CloudFoundryEndpointService } from '../../../../../cloud-foundry/services/cloud-foundry-endpoint.service'; +import { ActiveRouteCfOrgSpace } from '../../../../../cf/cf-page.types'; +import { CloudFoundryEndpointService } from '../../../../../cf/services/cloud-foundry-endpoint.service'; import { ApplicationMonitorService } from '../../../../application-monitor.service'; @Component({ diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-edit-space-step-base.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-edit-space-step-base.ts similarity index 89% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-edit-space-step-base.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/add-edit-space-step-base.ts index b625a0b8a0..b40983c572 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-edit-space-step-base.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/add-edit-space-step-base.ts @@ -4,14 +4,14 @@ import { Store } from '@ngrx/store'; import { Observable, Subscription } from 'rxjs'; import { filter, first, map, tap } from 'rxjs/operators'; -import { CFAppState } from '../../../../cloud-foundry/src/cf-app-state'; -import { organizationEntityType } from '../../../../cloud-foundry/src/cf-entity-types'; -import { createEntityRelationPaginationKey } from '../../../../cloud-foundry/src/entity-relations/entity-relations.types'; import { StepOnNextResult } from '../../../../core/src/shared/components/stepper/step/step.component'; import { getPaginationKey } from '../../../../store/src/actions/pagination.actions'; import { APIResource } from '../../../../store/src/types/api.types'; import { ISpaceQuotaDefinition } from '../../cf-api.types'; +import { CFAppState } from '../../cf-app-state'; import { cfEntityCatalog } from '../../cf-entity-catalog'; +import { organizationEntityType } from '../../cf-entity-types'; +import { createEntityRelationPaginationKey } from '../../entity-relations/entity-relations.types'; import { ActiveRouteCfOrgSpace } from './cf-page.types'; export class AddEditSpaceStepBase { @@ -35,8 +35,8 @@ export class AddEditSpaceStepBase { this.orgGuid, this.cfGuid, getPaginationKey(organizationEntityType, this.orgGuid), { - flatten: true, - } + flatten: true, + } ).entities$.pipe( filter(spaces => !!spaces), map(spaces => spaces.map(space => space.entity.name)), diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/add-organization.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/add-organization/add-organization.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/add-organization.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/add-organization/add-organization.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/add-organization.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/add-organization/add-organization.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/add-organization.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/add-organization/add-organization.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/add-organization.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-organization/add-organization.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/add-organization.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/add-organization/add-organization.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/add-organization.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-organization/add-organization.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/add-organization.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/add-organization/add-organization.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/create-organization-step/create-organization-step.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/add-organization/create-organization-step/create-organization-step.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/create-organization-step/create-organization-step.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/add-organization/create-organization-step/create-organization-step.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/create-organization-step/create-organization-step.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/add-organization/create-organization-step/create-organization-step.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/create-organization-step/create-organization-step.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/add-organization/create-organization-step/create-organization-step.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/create-organization-step/create-organization-step.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-organization/create-organization-step/create-organization-step.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/create-organization-step/create-organization-step.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/add-organization/create-organization-step/create-organization-step.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/create-organization-step/create-organization-step.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-organization/create-organization-step/create-organization-step.component.ts similarity index 89% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/create-organization-step/create-organization-step.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/add-organization/create-organization-step/create-organization-step.component.ts index acc4f55a0c..c13b3bdef2 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-organization/create-organization-step/create-organization-step.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/add-organization/create-organization-step/create-organization-step.component.ts @@ -5,22 +5,20 @@ import { Store } from '@ngrx/store'; import { Observable, Subscription } from 'rxjs'; import { filter, map, tap } from 'rxjs/operators'; -import { CreateOrganization } from '../../../../../../cloud-foundry/src/actions/organization.actions'; -import { CFAppState } from '../../../../../../cloud-foundry/src/cf-app-state'; -import { organizationEntityType } from '../../../../../../cloud-foundry/src/cf-entity-types'; -import { - createEntityRelationPaginationKey, -} from '../../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; -import { selectCfRequestInfo } from '../../../../../../cloud-foundry/src/store/selectors/api.selectors'; import { StepOnNextFunction } from '../../../../../../core/src/shared/components/stepper/step/step.component'; import { entityCatalog } from '../../../../../../store/src/entity-catalog/entity-catalog'; import { endpointEntityType } from '../../../../../../store/src/helpers/stratos-entity-factory'; import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; import { getPaginationObservables } from '../../../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper'; import { APIResource } from '../../../../../../store/src/types/api.types'; +import { CreateOrganization } from '../../../../actions/organization.actions'; import { IOrganization, IOrgQuotaDefinition } from '../../../../cf-api.types'; +import { CFAppState } from '../../../../cf-app-state'; import { cfEntityCatalog } from '../../../../cf-entity-catalog'; +import { organizationEntityType } from '../../../../cf-entity-types'; import { CF_ENDPOINT_TYPE } from '../../../../cf-types'; +import { createEntityRelationPaginationKey } from '../../../../entity-relations/entity-relations.types'; +import { selectCfRequestInfo } from '../../../../store/selectors/api.selectors'; import { CloudFoundryEndpointService } from '../../services/cloud-foundry-endpoint.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-quota/add-quota.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/add-quota/add-quota.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-quota/add-quota.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/add-quota/add-quota.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-quota/add-quota.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/add-quota/add-quota.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-quota/add-quota.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/add-quota/add-quota.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-quota/add-quota.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-quota/add-quota.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-quota/add-quota.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/add-quota/add-quota.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-quota/add-quota.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-quota/add-quota.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-quota/add-quota.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/add-quota/add-quota.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-quota/create-quota-step/create-quota-step.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/add-quota/create-quota-step/create-quota-step.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-quota/create-quota-step/create-quota-step.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/add-quota/create-quota-step/create-quota-step.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-quota/create-quota-step/create-quota-step.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/add-quota/create-quota-step/create-quota-step.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-quota/create-quota-step/create-quota-step.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/add-quota/create-quota-step/create-quota-step.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-quota/create-quota-step/create-quota-step.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-quota/create-quota-step/create-quota-step.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-quota/create-quota-step/create-quota-step.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/add-quota/create-quota-step/create-quota-step.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-quota/create-quota-step/create-quota-step.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-quota/create-quota-step/create-quota-step.component.ts similarity index 83% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-quota/create-quota-step/create-quota-step.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/add-quota/create-quota-step/create-quota-step.component.ts index 772a75ba57..d5fbe9dbfd 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-quota/create-quota-step/create-quota-step.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/add-quota/create-quota-step/create-quota-step.component.ts @@ -3,11 +3,11 @@ import { FormGroup } from '@angular/forms'; import { Subscription } from 'rxjs'; import { filter, map, pairwise } from 'rxjs/operators'; -import { cfEntityCatalog } from '../../../../../../cloud-foundry/src/cf-entity-catalog'; -import { ActiveRouteCfOrgSpace } from '../../../../../../cloud-foundry/src/features/cloud-foundry/cf-page.types'; -import { getActiveRouteCfOrgSpaceProvider } from '../../../../../../cloud-foundry/src/features/cloud-foundry/cf.helpers'; import { StepOnNextFunction } from '../../../../../../core/src/shared/components/stepper/step/step.component'; import { RequestInfoState } from '../../../../../../store/src/reducers/api-request-reducer/types'; +import { cfEntityCatalog } from '../../../../cf-entity-catalog'; +import { ActiveRouteCfOrgSpace } from '../../cf-page.types'; +import { getActiveRouteCfOrgSpaceProvider } from '../../cf.helpers'; import { QuotaDefinitionFormComponent } from '../../quota-definition-form/quota-definition-form.component'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space-quota/add-space-quota.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/add-space-quota.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space-quota/add-space-quota.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/add-space-quota.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space-quota/add-space-quota.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/add-space-quota.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space-quota/add-space-quota.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/add-space-quota.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space-quota/add-space-quota.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/add-space-quota.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space-quota/add-space-quota.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/add-space-quota.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space-quota/add-space-quota.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/add-space-quota.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space-quota/add-space-quota.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/add-space-quota.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space-quota/create-space-quota-step/create-space-quota-step.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/create-space-quota-step/create-space-quota-step.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space-quota/create-space-quota-step/create-space-quota-step.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/create-space-quota-step/create-space-quota-step.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space-quota/create-space-quota-step/create-space-quota-step.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/create-space-quota-step/create-space-quota-step.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space-quota/create-space-quota-step/create-space-quota-step.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/create-space-quota-step/create-space-quota-step.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space-quota/create-space-quota-step/create-space-quota-step.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/create-space-quota-step/create-space-quota-step.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space-quota/create-space-quota-step/create-space-quota-step.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/create-space-quota-step/create-space-quota-step.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space-quota/create-space-quota-step/create-space-quota-step.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/create-space-quota-step/create-space-quota-step.component.ts similarity index 86% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space-quota/create-space-quota-step/create-space-quota-step.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/create-space-quota-step/create-space-quota-step.component.ts index af6f0b51e1..6d3214c331 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space-quota/create-space-quota-step/create-space-quota-step.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/create-space-quota-step/create-space-quota-step.component.ts @@ -3,13 +3,13 @@ import { ActivatedRoute } from '@angular/router'; import { Observable, Subscription } from 'rxjs'; import { filter, map, pairwise } from 'rxjs/operators'; -import { cfEntityCatalog } from '../../../../../../cloud-foundry/src/cf-entity-catalog'; -import { ActiveRouteCfOrgSpace } from '../../../../../../cloud-foundry/src/features/cloud-foundry/cf-page.types'; -import { getActiveRouteCfOrgSpaceProvider } from '../../../../../../cloud-foundry/src/features/cloud-foundry/cf.helpers'; import { StepOnNextFunction } from '../../../../../../core/src/shared/components/stepper/step/step.component'; import { RequestInfoState } from '../../../../../../store/src/reducers/api-request-reducer/types'; import { APIResource } from '../../../../../../store/src/types/api.types'; import { IQuotaDefinition } from '../../../../cf-api.types'; +import { cfEntityCatalog } from '../../../../cf-entity-catalog'; +import { ActiveRouteCfOrgSpace } from '../../cf-page.types'; +import { getActiveRouteCfOrgSpaceProvider } from '../../cf.helpers'; import { SpaceQuotaDefinitionFormComponent } from '../../space-quota-definition-form/space-quota-definition-form.component'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space/add-space.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/add-space/add-space.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space/add-space.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/add-space/add-space.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space/add-space.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/add-space/add-space.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space/add-space.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/add-space/add-space.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space/add-space.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-space/add-space.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space/add-space.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/add-space/add-space.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space/add-space.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-space/add-space.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space/add-space.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/add-space/add-space.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space/create-space-step/create-space-step.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/add-space/create-space-step/create-space-step.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space/create-space-step/create-space-step.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/add-space/create-space-step/create-space-step.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space/create-space-step/create-space-step.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/add-space/create-space-step/create-space-step.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space/create-space-step/create-space-step.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/add-space/create-space-step/create-space-step.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space/create-space-step/create-space-step.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-space/create-space-step/create-space-step.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space/create-space-step/create-space-step.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/add-space/create-space-step/create-space-step.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space/create-space-step/create-space-step.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-space/create-space-step/create-space-step.component.ts similarity index 97% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space/create-space-step/create-space-step.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/add-space/create-space-step/create-space-step.component.ts index 8d2f3e6d96..5f85a93af3 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/add-space/create-space-step/create-space-step.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/add-space/create-space-step/create-space-step.component.ts @@ -5,9 +5,9 @@ import { Store } from '@ngrx/store'; import { Subscription } from 'rxjs'; import { filter, map, pairwise } from 'rxjs/operators'; -import { CFAppState } from '../../../../../../cloud-foundry/src/cf-app-state'; import { StepOnNextFunction } from '../../../../../../core/src/shared/components/stepper/step/step.component'; import { RequestInfoState } from '../../../../../../store/src/reducers/api-request-reducer/types'; +import { CFAppState } from '../../../../cf-app-state'; import { cfEntityCatalog } from '../../../../cf-entity-catalog'; import { AddEditSpaceStepBase } from '../../add-edit-space-step-base'; import { ActiveRouteCfOrgSpace } from '../../cf-page.types'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cf-cell.helpers.ts b/src/frontend/packages/cloud-foundry/src/features/cf/cf-cell.helpers.ts similarity index 96% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cf-cell.helpers.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/cf-cell.helpers.ts index 375a28581a..5362fa9b69 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cf-cell.helpers.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/cf-cell.helpers.ts @@ -11,7 +11,7 @@ import { IMetrics } from '../../../../store/src/types/base-metric.types'; import { MetricQueryType } from '../../../../store/src/types/metric.types'; import { FetchCFCellMetricsPaginatedAction } from '../../actions/cf-metrics.actions'; import { CFEntityConfig } from '../../cf-types'; -import { CellMetrics } from './tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell.service'; +import { CellMetrics } from './tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell.service'; export class CfCellHelper { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cf-page.types.ts b/src/frontend/packages/cloud-foundry/src/features/cf/cf-page.types.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cf-page.types.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/cf-page.types.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cf.helpers.ts b/src/frontend/packages/cloud-foundry/src/features/cf/cf.helpers.ts similarity index 96% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cf.helpers.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/cf.helpers.ts index e6e64456f7..a4ddeb40cf 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cf.helpers.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/cf.helpers.ts @@ -3,14 +3,6 @@ import { Store } from '@ngrx/store'; import { combineLatest, Observable } from 'rxjs'; import { filter, first, map, publishReplay, refCount, switchMap, tap } from 'rxjs/operators'; -import { CFAppState } from '../../../../cloud-foundry/src/cf-app-state'; -import { getCFEntityKey } from '../../../../cloud-foundry/src/cf-entity-helpers'; -import { applicationEntityType } from '../../../../cloud-foundry/src/cf-entity-types'; -import { - getCurrentUserCFEndpointRolesState, -} from '../../../../cloud-foundry/src/store/selectors/cf-current-user-role.selectors'; -import { ICfRolesState } from '../../../../cloud-foundry/src/store/types/cf-current-user-roles.types'; -import { UserRoleLabels } from '../../../../cloud-foundry/src/store/types/users-roles.types'; import { PermissionConfig } from '../../../../core/src/core/permissions/current-user-permissions.config'; import { CurrentUserPermissionsService } from '../../../../core/src/core/permissions/current-user-permissions.service'; import { getIdFromRoute, pathGet } from '../../../../core/src/core/utils.service'; @@ -29,9 +21,14 @@ import { EndpointModel } from '../../../../store/src/types/endpoint.types'; import { PaginatedAction, PaginationEntityState } from '../../../../store/src/types/pagination.types'; import { IServiceInstance, IUserProvidedServiceInstance } from '../../cf-api-svc.types'; import { CFFeatureFlagTypes, IApp, ISpace } from '../../cf-api.types'; +import { CFAppState } from '../../cf-app-state'; import { cfEntityFactory } from '../../cf-entity-factory'; +import { getCFEntityKey } from '../../cf-entity-helpers'; +import { applicationEntityType } from '../../cf-entity-types'; import { CFEntityConfig } from '../../cf-types'; import { ListCfRoute } from '../../shared/components/list/list-types/cf-routes/cf-routes-data-source-base'; +import { getCurrentUserCFEndpointRolesState } from '../../store/selectors/cf-current-user-role.selectors'; +import { ICfRolesState } from '../../store/types/cf-current-user-roles.types'; import { CfUser, CfUserRoleParams, @@ -40,6 +37,7 @@ import { UserRoleInOrg, UserRoleInSpace, } from '../../store/types/cf-user.types'; +import { UserRoleLabels } from '../../store/types/users-roles.types'; import { CfCurrentUserPermissions, CfPermissionTypes } from '../../user-permissions/cf-user-permissions-checkers'; import { ActiveRouteCfCell, ActiveRouteCfOrgSpace } from './cf-page.types'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/cli-info-cloud-foundry/cli-info-cloud-foundry.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/cli-info-cloud-foundry/cli-info-cloud-foundry.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/cli-info-cloud-foundry/cli-info-cloud-foundry.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/cli-info-cloud-foundry/cli-info-cloud-foundry.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/cli-info-cloud-foundry/cli-info-cloud-foundry.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/cli-info-cloud-foundry/cli-info-cloud-foundry.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/cli-info-cloud-foundry/cli-info-cloud-foundry.component.ts similarity index 96% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/cli-info-cloud-foundry/cli-info-cloud-foundry.component.ts index 3449967904..1f0eb737b6 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cli-info-cloud-foundry/cli-info-cloud-foundry.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/cli-info-cloud-foundry/cli-info-cloud-foundry.component.ts @@ -3,8 +3,6 @@ import { Store } from '@ngrx/store'; import { BehaviorSubject, combineLatest, Observable, of as observableOf } from 'rxjs'; import { first, map } from 'rxjs/operators'; -import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; -import { CFAppCLIInfoContext } from '../../../../../cloud-foundry/src/shared/components/cli-info/cli-info.component'; import { IHeaderBreadcrumb } from '../../../../../core/src/shared/components/page-header/page-header.types'; import { RouterNav } from '../../../../../store/src/actions/router.actions'; import { getFullEndpointApiUrl } from '../../../../../store/src/endpoint-utils'; @@ -12,6 +10,8 @@ import { APIResource, EntityInfo } from '../../../../../store/src/types/api.type import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; import { getPreviousRoutingState } from '../../../../../store/src/types/routing.type'; import { IOrganization, ISpace } from '../../../cf-api.types'; +import { CFAppState } from '../../../cf-app-state'; +import { CFAppCLIInfoContext } from '../../../shared/components/cli-info/cli-info.component'; import { CloudFoundryUserProvidedServicesService, } from '../../../shared/services/cloud-foundry-user-provided-services.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-base/cloud-foundry-base.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-base/cloud-foundry-base.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-base/cloud-foundry-base.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-base/cloud-foundry-base.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-base/cloud-foundry-base.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-base/cloud-foundry-base.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-base/cloud-foundry-base.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-base/cloud-foundry-base.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-base/cloud-foundry-base.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-base/cloud-foundry-base.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-base/cloud-foundry-base.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-base/cloud-foundry-base.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-base/cloud-foundry-base.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-base/cloud-foundry-base.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-base/cloud-foundry-base.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-base/cloud-foundry-base.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-section.module.ts b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-section.module.ts similarity index 68% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-section.module.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-section.module.ts index ca523e3b30..aa22c90c69 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-section.module.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-section.module.ts @@ -41,80 +41,76 @@ import { CloudFoundryOrganizationService } from './services/cloud-foundry-organi import { SpaceQuotaDefinitionFormComponent } from './space-quota-definition-form/space-quota-definition-form.component'; import { SpaceQuotaDefinitionComponent } from './space-quota-definition/space-quota-definition.component'; import { CfAdminAddUserWarningComponent } from './tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component'; -import { CloudFoundryBuildPacksComponent } from './tabs/cloud-foundry-build-packs/cloud-foundry-build-packs.component'; +import { CloudFoundryBuildPacksComponent } from './tabs/cf-build-packs/cloud-foundry-build-packs.component'; import { CloudFoundryCellAppsComponent, -} from './tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component'; +} from './tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component'; import { CloudFoundryCellBaseComponent, -} from './tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component'; +} from './tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component'; import { CloudFoundryCellChartsComponent, -} from './tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component'; +} from './tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component'; import { CloudFoundryCellSummaryComponent, -} from './tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component'; -import { CloudFoundryCellService } from './tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell.service'; -import { CloudFoundryCellsComponent } from './tabs/cloud-foundry-cells/cloud-foundry-cells.component'; -import { CloudFoundryEventsComponent } from './tabs/cloud-foundry-events/cloud-foundry-events.component'; -import { CloudFoundryFeatureFlagsComponent } from './tabs/cloud-foundry-feature-flags/cloud-foundry-feature-flags.component'; -import { CloudFoundryFirehoseComponent } from './tabs/cloud-foundry-firehose/cloud-foundry-firehose.component'; +} from './tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component'; +import { CloudFoundryCellService } from './tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell.service'; +import { CloudFoundryCellsComponent } from './tabs/cf-cells/cloud-foundry-cells.component'; +import { CloudFoundryEventsComponent } from './tabs/cf-events/cloud-foundry-events.component'; +import { CloudFoundryFeatureFlagsComponent } from './tabs/cf-feature-flags/cloud-foundry-feature-flags.component'; +import { CloudFoundryFirehoseComponent } from './tabs/cf-firehose/cloud-foundry-firehose.component'; import { CloudFoundryOrganizationSpaceQuotasComponent, -} from './tabs/cloud-foundry-organization-space-quotas/cloud-foundry-organization-space-quotas.component'; +} from './tabs/cf-organization-space-quotas/cloud-foundry-organization-space-quotas.component'; import { CloudFoundryInviteUserLinkComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-invite-user-link/cloud-foundry-invite-user-link.component'; +} from './tabs/cf-organizations/cf-invite-user-link/cloud-foundry-invite-user-link.component'; import { CloudFoundryOrganizationBaseComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-base/cloud-foundry-organization-base.component'; +} from './tabs/cf-organizations/cf-organization-base/cloud-foundry-organization-base.component'; import { CloudFoundryOrganizationEventsComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-events/cloud-foundry-organization-events.component'; +} from './tabs/cf-organizations/cf-organization-events/cloud-foundry-organization-events.component'; import { CloudFoundryOrganizationSpacesComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-organization-spaces.component'; +} from './tabs/cf-organizations/cf-organization-spaces/cloud-foundry-organization-spaces.component'; import { CloudFoundrySpaceBaseComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component'; +} from './tabs/cf-organizations/cf-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component'; import { CloudFoundrySpaceAppsComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component'; +} from './tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component'; import { CloudFoundrySpaceEventsComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component'; +} from './tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component'; import { CloudFoundrySpaceRoutesComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component'; +} from './tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component'; import { CloudFoundrySpaceServiceInstancesComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component'; +} from './tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component'; import { CloudFoundrySpaceSummaryComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component'; +} from './tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component'; import { CloudFoundrySpaceUserServiceInstancesComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component'; +} from './tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component'; import { CloudFoundrySpaceUsersComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component'; +} from './tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component'; import { CloudFoundryOrganizationSummaryComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component'; +} from './tabs/cf-organizations/cf-organization-summary/cloud-foundry-organization-summary.component'; import { CloudFoundryOrganizationUsersComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-users/cloud-foundry-organization-users.component'; -import { - CloudFoundryOrganizationsComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organizations.component'; -import { CloudFoundryQuotasComponent } from './tabs/cloud-foundry-quotas/cloud-foundry-quotas.component'; -import { CloudFoundryRoutesComponent } from './tabs/cloud-foundry-routes/cloud-foundry-routes.component'; -import { - CloudFoundrySecurityGroupsComponent, -} from './tabs/cloud-foundry-security-groups/cloud-foundry-security-groups.component'; -import { CloudFoundryStacksComponent } from './tabs/cloud-foundry-stacks/cloud-foundry-stacks.component'; -import { CloudFoundrySummaryTabComponent } from './tabs/cloud-foundry-summary-tab/cloud-foundry-summary-tab.component'; -import { CloudFoundryUsersComponent } from './tabs/cloud-foundry-users/cloud-foundry-users.component'; +} from './tabs/cf-organizations/cf-organization-users/cloud-foundry-organization-users.component'; +import { CloudFoundryOrganizationsComponent } from './tabs/cf-organizations/cloud-foundry-organizations.component'; +import { CloudFoundryQuotasComponent } from './tabs/cf-quotas/cloud-foundry-quotas.component'; +import { CloudFoundryRoutesComponent } from './tabs/cf-routes/cloud-foundry-routes.component'; +import { CloudFoundrySecurityGroupsComponent } from './tabs/cf-security-groups/cloud-foundry-security-groups.component'; +import { CloudFoundryStacksComponent } from './tabs/cf-stacks/cloud-foundry-stacks.component'; +import { CloudFoundrySummaryTabComponent } from './tabs/cf-summary-tab/cloud-foundry-summary-tab.component'; +import { CloudFoundryUsersComponent } from './tabs/cf-users/cloud-foundry-users.component'; import { UserInviteConfigurationDialogComponent, } from './user-invites/configuration-dialog/user-invite-configuration-dialog.component'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-section.routing.ts b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-section.routing.ts similarity index 76% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-section.routing.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-section.routing.ts index 258d2b2163..b8b6a008f3 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-section.routing.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-section.routing.ts @@ -20,76 +20,72 @@ import { EditSpaceQuotaComponent } from './edit-space-quota/edit-space-quota.com import { EditSpaceComponent } from './edit-space/edit-space.component'; import { QuotaDefinitionComponent } from './quota-definition/quota-definition.component'; import { SpaceQuotaDefinitionComponent } from './space-quota-definition/space-quota-definition.component'; -import { CloudFoundryBuildPacksComponent } from './tabs/cloud-foundry-build-packs/cloud-foundry-build-packs.component'; +import { CloudFoundryBuildPacksComponent } from './tabs/cf-build-packs/cloud-foundry-build-packs.component'; import { CloudFoundryCellAppsComponent, -} from './tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component'; +} from './tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component'; import { CloudFoundryCellBaseComponent, -} from './tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component'; +} from './tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component'; import { CloudFoundryCellChartsComponent, -} from './tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component'; +} from './tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component'; import { CloudFoundryCellSummaryComponent, -} from './tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component'; -import { CloudFoundryCellsComponent } from './tabs/cloud-foundry-cells/cloud-foundry-cells.component'; -import { CloudFoundryEventsComponent } from './tabs/cloud-foundry-events/cloud-foundry-events.component'; -import { CloudFoundryFeatureFlagsComponent } from './tabs/cloud-foundry-feature-flags/cloud-foundry-feature-flags.component'; -import { CloudFoundryFirehoseComponent } from './tabs/cloud-foundry-firehose/cloud-foundry-firehose.component'; +} from './tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component'; +import { CloudFoundryCellsComponent } from './tabs/cf-cells/cloud-foundry-cells.component'; +import { CloudFoundryEventsComponent } from './tabs/cf-events/cloud-foundry-events.component'; +import { CloudFoundryFeatureFlagsComponent } from './tabs/cf-feature-flags/cloud-foundry-feature-flags.component'; +import { CloudFoundryFirehoseComponent } from './tabs/cf-firehose/cloud-foundry-firehose.component'; import { CloudFoundryOrganizationSpaceQuotasComponent, -} from './tabs/cloud-foundry-organization-space-quotas/cloud-foundry-organization-space-quotas.component'; +} from './tabs/cf-organization-space-quotas/cloud-foundry-organization-space-quotas.component'; import { CloudFoundryOrganizationBaseComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-base/cloud-foundry-organization-base.component'; +} from './tabs/cf-organizations/cf-organization-base/cloud-foundry-organization-base.component'; import { CloudFoundryOrganizationEventsComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-events/cloud-foundry-organization-events.component'; +} from './tabs/cf-organizations/cf-organization-events/cloud-foundry-organization-events.component'; import { CloudFoundryOrganizationSpacesComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-organization-spaces.component'; +} from './tabs/cf-organizations/cf-organization-spaces/cloud-foundry-organization-spaces.component'; import { CloudFoundrySpaceBaseComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component'; +} from './tabs/cf-organizations/cf-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component'; import { CloudFoundrySpaceAppsComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component'; +} from './tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component'; import { CloudFoundrySpaceEventsComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component'; +} from './tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component'; import { CloudFoundrySpaceRoutesComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component'; +} from './tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component'; import { CloudFoundrySpaceServiceInstancesComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component'; +} from './tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component'; import { CloudFoundrySpaceSummaryComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component'; +} from './tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component'; import { CloudFoundrySpaceUserServiceInstancesComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component'; +} from './tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component'; import { CloudFoundrySpaceUsersComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component'; +} from './tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component'; import { CloudFoundryOrganizationSummaryComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component'; +} from './tabs/cf-organizations/cf-organization-summary/cloud-foundry-organization-summary.component'; import { CloudFoundryOrganizationUsersComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organization-users/cloud-foundry-organization-users.component'; -import { - CloudFoundryOrganizationsComponent, -} from './tabs/cloud-foundry-organizations/cloud-foundry-organizations.component'; -import { CloudFoundryQuotasComponent } from './tabs/cloud-foundry-quotas/cloud-foundry-quotas.component'; -import { CloudFoundryRoutesComponent } from './tabs/cloud-foundry-routes/cloud-foundry-routes.component'; -import { - CloudFoundrySecurityGroupsComponent, -} from './tabs/cloud-foundry-security-groups/cloud-foundry-security-groups.component'; -import { CloudFoundryStacksComponent } from './tabs/cloud-foundry-stacks/cloud-foundry-stacks.component'; -import { CloudFoundrySummaryTabComponent } from './tabs/cloud-foundry-summary-tab/cloud-foundry-summary-tab.component'; -import { CloudFoundryUsersComponent } from './tabs/cloud-foundry-users/cloud-foundry-users.component'; +} from './tabs/cf-organizations/cf-organization-users/cloud-foundry-organization-users.component'; +import { CloudFoundryOrganizationsComponent } from './tabs/cf-organizations/cloud-foundry-organizations.component'; +import { CloudFoundryQuotasComponent } from './tabs/cf-quotas/cloud-foundry-quotas.component'; +import { CloudFoundryRoutesComponent } from './tabs/cf-routes/cloud-foundry-routes.component'; +import { CloudFoundrySecurityGroupsComponent } from './tabs/cf-security-groups/cloud-foundry-security-groups.component'; +import { CloudFoundryStacksComponent } from './tabs/cf-stacks/cloud-foundry-stacks.component'; +import { CloudFoundrySummaryTabComponent } from './tabs/cf-summary-tab/cloud-foundry-summary-tab.component'; +import { CloudFoundryUsersComponent } from './tabs/cf-users/cloud-foundry-users.component'; import { InviteUsersComponent } from './users/invite-users/invite-users.component'; import { UsersRolesComponent } from './users/manage-users/manage-users.component'; import { RemoveUserComponent } from './users/remove-user/remove-user.component'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry/cloud-foundry.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry/cloud-foundry.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry/cloud-foundry.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry/cloud-foundry.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry/cloud-foundry.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry/cloud-foundry.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry/cloud-foundry.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry/cloud-foundry.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry/cloud-foundry.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry/cloud-foundry.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry/cloud-foundry.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry/cloud-foundry.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry/cloud-foundry.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry/cloud-foundry.component.ts similarity index 94% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry/cloud-foundry.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry/cloud-foundry.component.ts index 716ad78c4a..e1b4af53cb 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry/cloud-foundry.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry/cloud-foundry.component.ts @@ -3,9 +3,9 @@ import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { first, map } from 'rxjs/operators'; -import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; import { ListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; import { RouterNav } from '../../../../../store/src/actions/router.actions'; +import { CFAppState } from '../../../cf-app-state'; import { CFEndpointsListConfigService, } from '../../../shared/components/list/list-types/cf-endpoints/cf-endpoints-list-config.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization-step/edit-organization-step.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization-step/edit-organization-step.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization-step/edit-organization-step.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization-step/edit-organization-step.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization-step/edit-organization-step.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization-step/edit-organization-step.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization-step/edit-organization-step.component.ts similarity index 94% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization-step/edit-organization-step.component.ts index 5a91738c56..9d206f596b 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization-step/edit-organization-step.component.ts @@ -4,11 +4,6 @@ import { Store } from '@ngrx/store'; import { Observable, Subscription } from 'rxjs'; import { filter, map, pairwise, take, tap } from 'rxjs/operators'; -import { CFAppState } from '../../../../../../cloud-foundry/src/cf-app-state'; -import { organizationEntityType } from '../../../../../../cloud-foundry/src/cf-entity-types'; -import { - createEntityRelationPaginationKey, -} from '../../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; import { safeUnsubscribe } from '../../../../../../core/src/core/utils.service'; import { StepOnNextFunction } from '../../../../../../core/src/shared/components/stepper/step/step.component'; import { endpointEntityType } from '../../../../../../store/src/helpers/stratos-entity-factory'; @@ -17,8 +12,11 @@ import { ActionState } from '../../../../../../store/src/reducers/api-request-re import { getPaginationObservables } from '../../../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper'; import { APIResource } from '../../../../../../store/src/types/api.types'; import { IOrganization, IOrgQuotaDefinition } from '../../../../cf-api.types'; +import { CFAppState } from '../../../../cf-app-state'; import { cfEntityCatalog } from '../../../../cf-entity-catalog'; import { cfEntityFactory } from '../../../../cf-entity-factory'; +import { organizationEntityType } from '../../../../cf-entity-types'; +import { createEntityRelationPaginationKey } from '../../../../entity-relations/entity-relations.types'; import { CloudFoundryUserProvidedServicesService, } from '../../../../shared/services/cloud-foundry-user-provided-services.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-quota/edit-quota-step/edit-quota-step.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota-step/edit-quota-step.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-quota/edit-quota-step/edit-quota-step.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota-step/edit-quota-step.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-quota/edit-quota-step/edit-quota-step.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota-step/edit-quota-step.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-quota/edit-quota-step/edit-quota-step.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota-step/edit-quota-step.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-quota/edit-quota-step/edit-quota-step.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota-step/edit-quota-step.component.spec.ts similarity index 93% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-quota/edit-quota-step/edit-quota-step.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota-step/edit-quota-step.component.spec.ts index 3267ef2916..1216c269d4 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-quota/edit-quota-step/edit-quota-step.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota-step/edit-quota-step.component.spec.ts @@ -1,8 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { CFBaseTestModules } from '../../../../../../cloud-foundry/test-framework/cf-test-helper'; import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; +import { CFBaseTestModules } from '../../../../../test-framework/cf-test-helper'; import { QuotaDefinitionFormComponent } from '../../quota-definition-form/quota-definition-form.component'; import { EditQuotaStepComponent } from './edit-quota-step.component'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-quota/edit-quota-step/edit-quota-step.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota-step/edit-quota-step.component.ts similarity index 88% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-quota/edit-quota-step/edit-quota-step.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota-step/edit-quota-step.component.ts index 822c25a51a..01c463628d 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-quota/edit-quota-step/edit-quota-step.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota-step/edit-quota-step.component.ts @@ -4,15 +4,15 @@ import { Store } from '@ngrx/store'; import { Observable, Subscription } from 'rxjs'; import { filter, first, map, pairwise, tap } from 'rxjs/operators'; -import { cfEntityCatalog } from '../../../../../../cloud-foundry/src/cf-entity-catalog'; -import { ActiveRouteCfOrgSpace } from '../../../../../../cloud-foundry/src/features/cloud-foundry/cf-page.types'; -import { getActiveRouteCfOrgSpaceProvider } from '../../../../../../cloud-foundry/src/features/cloud-foundry/cf.helpers'; import { safeUnsubscribe } from '../../../../../../core/src/core/utils.service'; import { StepOnNextFunction } from '../../../../../../core/src/shared/components/stepper/step/step.component'; import { AppState } from '../../../../../../store/src/app-state'; import { ActionState } from '../../../../../../store/src/reducers/api-request-reducer/types'; import { APIResource } from '../../../../../../store/src/types/api.types'; import { IOrgQuotaDefinition } from '../../../../cf-api.types'; +import { cfEntityCatalog } from '../../../../cf-entity-catalog'; +import { ActiveRouteCfOrgSpace } from '../../cf-page.types'; +import { getActiveRouteCfOrgSpaceProvider } from '../../cf.helpers'; import { QuotaDefinitionFormComponent } from '../../quota-definition-form/quota-definition-form.component'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-quota/edit-quota.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-quota/edit-quota.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-quota/edit-quota.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-quota/edit-quota.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-quota/edit-quota.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-quota/edit-quota.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-quota/edit-quota.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-quota/edit-quota.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.spec.ts similarity index 93% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.spec.ts index 052372456f..3b26f9d45b 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.spec.ts @@ -1,8 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { CFBaseTestModules } from '../../../../../../cloud-foundry/test-framework/cf-test-helper'; import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; +import { CFBaseTestModules } from '../../../../../test-framework/cf-test-helper'; import { SpaceQuotaDefinitionFormComponent } from '../../space-quota-definition-form/space-quota-definition-form.component'; import { EditSpaceQuotaStepComponent } from './edit-space-quota-step.component'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.ts similarity index 89% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.ts index 30746139e2..8c0123ad3d 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.ts @@ -4,15 +4,15 @@ import { Store } from '@ngrx/store'; import { Observable, Subscription } from 'rxjs'; import { filter, map, pairwise, tap } from 'rxjs/operators'; -import { cfEntityCatalog } from '../../../../../../cloud-foundry/src/cf-entity-catalog'; -import { ActiveRouteCfOrgSpace } from '../../../../../../cloud-foundry/src/features/cloud-foundry/cf-page.types'; -import { getActiveRouteCfOrgSpaceProvider } from '../../../../../../cloud-foundry/src/features/cloud-foundry/cf.helpers'; import { safeUnsubscribe } from '../../../../../../core/src/core/utils.service'; import { StepOnNextFunction } from '../../../../../../core/src/shared/components/stepper/step/step.component'; import { AppState } from '../../../../../../store/src/app-state'; import { ActionState } from '../../../../../../store/src/reducers/api-request-reducer/types'; import { APIResource } from '../../../../../../store/src/types/api.types'; import { ISpaceQuotaDefinition } from '../../../../cf-api.types'; +import { cfEntityCatalog } from '../../../../cf-entity-catalog'; +import { ActiveRouteCfOrgSpace } from '../../cf-page.types'; +import { getActiveRouteCfOrgSpaceProvider } from '../../cf.helpers'; import { SpaceQuotaDefinitionFormComponent } from '../../space-quota-definition-form/space-quota-definition-form.component'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space-quota/edit-space-quota.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space-quota/edit-space-quota.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space-quota/edit-space-quota.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space-quota/edit-space-quota.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space-quota/edit-space-quota.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space-quota/edit-space-quota.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space-quota/edit-space-quota.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space-quota/edit-space-quota.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space-step/edit-space-step.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space-step/edit-space-step.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space-step/edit-space-step.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space-step/edit-space-step.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space-step/edit-space-step.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space-step/edit-space-step.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space-step/edit-space-step.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space-step/edit-space-step.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space-step/edit-space-step.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space-step/edit-space-step.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space-step/edit-space-step.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space-step/edit-space-step.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space-step/edit-space-step.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space-step/edit-space-step.component.ts similarity index 98% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space-step/edit-space-step.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space-step/edit-space-step.component.ts index ab2f460630..b4930646e2 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space-step/edit-space-step.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space-step/edit-space-step.component.ts @@ -5,9 +5,9 @@ import { Store } from '@ngrx/store'; import { Observable, of, Subscription } from 'rxjs'; import { filter, map, pairwise, switchMap, take, tap } from 'rxjs/operators'; -import { CFAppState } from '../../../../../../cloud-foundry/src/cf-app-state'; import { StepOnNextFunction } from '../../../../../../core/src/shared/components/stepper/step/step.component'; import { ActionState } from '../../../../../../store/src/reducers/api-request-reducer/types'; +import { CFAppState } from '../../../../cf-app-state'; import { cfEntityCatalog } from '../../../../cf-entity-catalog'; import { AddEditSpaceStepBase } from '../../add-edit-space-step-base'; import { ActiveRouteCfOrgSpace } from '../../cf-page.types'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition-base/quota-definition-base.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/quota-definition-base/quota-definition-base.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition-base/quota-definition-base.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/quota-definition-base/quota-definition-base.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition-base/quota-definition-base.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/quota-definition-base/quota-definition-base.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition-base/quota-definition-base.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/quota-definition-base/quota-definition-base.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition-form/quota-definition-form.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/quota-definition-form/quota-definition-form.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition-form/quota-definition-form.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/quota-definition-form/quota-definition-form.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition-form/quota-definition-form.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/quota-definition-form/quota-definition-form.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition-form/quota-definition-form.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/quota-definition-form/quota-definition-form.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition-form/quota-definition-form.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/quota-definition-form/quota-definition-form.component.ts similarity index 88% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition-form/quota-definition-form.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/quota-definition-form/quota-definition-form.component.ts index d330a31aec..778a19c696 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition-form/quota-definition-form.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/quota-definition-form/quota-definition-form.component.ts @@ -3,13 +3,13 @@ import { AbstractControl, FormControl, FormGroup, ValidatorFn, Validators } from import { Subscription } from 'rxjs'; import { filter, map, tap } from 'rxjs/operators'; -import { cfEntityCatalog } from '../../../../../cloud-foundry/src/cf-entity-catalog'; -import { createEntityRelationPaginationKey } from '../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; -import { ActiveRouteCfOrgSpace } from '../../../../../cloud-foundry/src/features/cloud-foundry/cf-page.types'; -import { getActiveRouteCfOrgSpaceProvider } from '../../../../../cloud-foundry/src/features/cloud-foundry/cf.helpers'; import { safeUnsubscribe } from '../../../../../core/src/core/utils.service'; import { endpointEntityType } from '../../../../../store/src/helpers/stratos-entity-factory'; import { IQuotaDefinition } from '../../../cf-api.types'; +import { cfEntityCatalog } from '../../../cf-entity-catalog'; +import { createEntityRelationPaginationKey } from '../../../entity-relations/entity-relations.types'; +import { ActiveRouteCfOrgSpace } from '../cf-page.types'; +import { getActiveRouteCfOrgSpaceProvider } from '../cf.helpers'; export interface QuotaFormValues { name: string; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition/quota-definition.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/quota-definition/quota-definition.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition/quota-definition.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/quota-definition/quota-definition.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition/quota-definition.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/quota-definition/quota-definition.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition/quota-definition.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/quota-definition/quota-definition.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition/quota-definition.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/quota-definition/quota-definition.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition/quota-definition.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/quota-definition/quota-definition.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition/quota-definition.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/quota-definition/quota-definition.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/quota-definition/quota-definition.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/quota-definition/quota-definition.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts b/src/frontend/packages/cloud-foundry/src/features/cf/services/cloud-foundry-endpoint.service.ts similarity index 95% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/services/cloud-foundry-endpoint.service.ts index eec79aba70..2a26f72b91 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/services/cloud-foundry-endpoint.service.ts @@ -3,20 +3,6 @@ import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { filter, first, map, publishReplay, refCount } from 'rxjs/operators'; -import { GetAllApplications } from '../../../../../cloud-foundry/src/actions/application.actions'; -import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; -import { - domainEntityType, - organizationEntityType, - privateDomainsEntityType, - quotaDefinitionEntityType, - spaceEntityType, -} from '../../../../../cloud-foundry/src/cf-entity-types'; -import { - createEntityRelationKey, - createEntityRelationPaginationKey, -} from '../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; -import { CfApplicationState } from '../../../../../cloud-foundry/src/store/types/application.types'; import { EntityService } from '../../../../../store/src/entity-service'; import { endpointEntityType } from '../../../../../store/src/helpers/stratos-entity-factory'; import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; @@ -26,13 +12,27 @@ import { stratosEntityCatalog } from '../../../../../store/src/stratos-entity-ca import { APIResource, EntityInfo } from '../../../../../store/src/types/api.types'; import { EndpointModel, EndpointUser } from '../../../../../store/src/types/endpoint.types'; import { PaginatedAction } from '../../../../../store/src/types/pagination.types'; +import { GetAllApplications } from '../../../actions/application.actions'; import { GetAllRoutes } from '../../../actions/route.actions'; import { GetSpaceRoutes } from '../../../actions/space.actions'; import { IApp, ICfV2Info, IOrganization, ISpace } from '../../../cf-api.types'; +import { CFAppState } from '../../../cf-app-state'; import { cfEntityCatalog } from '../../../cf-entity-catalog'; import { cfEntityFactory } from '../../../cf-entity-factory'; +import { + domainEntityType, + organizationEntityType, + privateDomainsEntityType, + quotaDefinitionEntityType, + spaceEntityType, +} from '../../../cf-entity-types'; +import { + createEntityRelationKey, + createEntityRelationPaginationKey, +} from '../../../entity-relations/entity-relations.types'; import { CfUserService } from '../../../shared/data-services/cf-user.service'; import { QParam, QParamJoiners } from '../../../shared/q-param'; +import { CfApplicationState } from '../../../store/types/application.types'; import { ActiveRouteCfOrgSpace } from '../cf-page.types'; import { fetchTotalResults } from '../cf.helpers'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-org-space-quota.ts b/src/frontend/packages/cloud-foundry/src/features/cf/services/cloud-foundry-org-space-quota.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-org-space-quota.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/services/cloud-foundry-org-space-quota.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization-quota.ts b/src/frontend/packages/cloud-foundry/src/features/cf/services/cloud-foundry-organization-quota.ts similarity index 95% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization-quota.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/services/cloud-foundry-organization-quota.ts index 9c3bcbf845..20ddb603d7 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization-quota.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/services/cloud-foundry-organization-quota.ts @@ -1,10 +1,10 @@ import { Observable } from 'rxjs'; -import { organizationEntityType } from '../../../../../cloud-foundry/src/cf-entity-types'; import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; import { APIResource } from '../../../../../store/src/types/api.types'; import { StratosStatus } from '../../../../../store/src/types/shared.types'; import { IApp, IOrganization } from '../../../cf-api.types'; +import { organizationEntityType } from '../../../cf-entity-types'; import { getEntityFlattenedList, getStartedAppInstanceCount } from '../../../cf.helpers'; import { CloudFoundryEndpointService } from './cloud-foundry-endpoint.service'; import { OrgSpaceQuotaHelper } from './cloud-foundry-org-space-quota'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts b/src/frontend/packages/cloud-foundry/src/features/cf/services/cloud-foundry-organization.service.ts similarity index 98% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/services/cloud-foundry-organization.service.ts index 07848a222f..52db165c63 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/services/cloud-foundry-organization.service.ts @@ -4,14 +4,6 @@ import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { filter, map, publishReplay, refCount, switchMap } from 'rxjs/operators'; -import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; -import { - domainEntityType, - organizationEntityType, - privateDomainsEntityType, - quotaDefinitionEntityType, - spaceEntityType, -} from '../../../../../cloud-foundry/src/cf-entity-types'; import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; import { APIResource, EntityInfo } from '../../../../../store/src/types/api.types'; import { @@ -22,7 +14,15 @@ import { ISpace, ISpaceQuotaDefinition, } from '../../../cf-api.types'; +import { CFAppState } from '../../../cf-app-state'; import { cfEntityCatalog } from '../../../cf-entity-catalog'; +import { + domainEntityType, + organizationEntityType, + privateDomainsEntityType, + quotaDefinitionEntityType, + spaceEntityType, +} from '../../../cf-entity-types'; import { getEntityFlattenedList, getStartedAppInstanceCount } from '../../../cf.helpers'; import { createEntityRelationKey } from '../../../entity-relations/entity-relations.types'; import { CfUserService } from '../../../shared/data-services/cf-user.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space-quota.ts b/src/frontend/packages/cloud-foundry/src/features/cf/services/cloud-foundry-space-quota.ts similarity index 95% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space-quota.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/services/cloud-foundry-space-quota.ts index 692fad26d8..e10bbc9337 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space-quota.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/services/cloud-foundry-space-quota.ts @@ -1,10 +1,10 @@ import { Observable } from 'rxjs'; -import { spaceEntityType } from '../../../../../cloud-foundry/src/cf-entity-types'; import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; import { APIResource } from '../../../../../store/src/types/api.types'; import { StratosStatus } from '../../../../../store/src/types/shared.types'; import { IApp, ISpace } from '../../../cf-api.types'; +import { spaceEntityType } from '../../../cf-entity-types'; import { getStartedAppInstanceCount } from '../../../cf.helpers'; import { CloudFoundryEndpointService } from './cloud-foundry-endpoint.service'; import { OrgSpaceQuotaHelper } from './cloud-foundry-org-space-quota'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts b/src/frontend/packages/cloud-foundry/src/features/cf/services/cloud-foundry-space.service.ts similarity index 98% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/services/cloud-foundry-space.service.ts index f3faadd465..656508eb2c 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/services/cloud-foundry-space.service.ts @@ -3,7 +3,11 @@ import { Store } from '@ngrx/store'; import { combineLatest, Observable, of } from 'rxjs'; import { filter, map, publishReplay, refCount, switchMap } from 'rxjs/operators'; -import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; +import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; +import { APIResource, EntityInfo } from '../../../../../store/src/types/api.types'; +import { IApp, IOrgQuotaDefinition, IRoute, ISpace, ISpaceQuotaDefinition } from '../../../cf-api.types'; +import { CFAppState } from '../../../cf-app-state'; +import { cfEntityCatalog } from '../../../cf-entity-catalog'; import { applicationEntityType, routeEntityType, @@ -11,11 +15,7 @@ import { serviceInstancesEntityType, spaceEntityType, spaceQuotaEntityType, -} from '../../../../../cloud-foundry/src/cf-entity-types'; -import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; -import { APIResource, EntityInfo } from '../../../../../store/src/types/api.types'; -import { IApp, IOrgQuotaDefinition, IRoute, ISpace, ISpaceQuotaDefinition } from '../../../cf-api.types'; -import { cfEntityCatalog } from '../../../cf-entity-catalog'; +} from '../../../cf-entity-types'; import { getStartedAppInstanceCount } from '../../../cf.helpers'; import { createEntityRelationKey } from '../../../entity-relations/entity-relations.types'; import { CfUserService } from '../../../shared/data-services/cf-user.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition-form/space-quota-definition-form.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/space-quota-definition-form/space-quota-definition-form.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition-form/space-quota-definition-form.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/space-quota-definition-form/space-quota-definition-form.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition-form/space-quota-definition-form.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/space-quota-definition-form/space-quota-definition-form.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition-form/space-quota-definition-form.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/space-quota-definition-form/space-quota-definition-form.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition-form/space-quota-definition-form.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/space-quota-definition-form/space-quota-definition-form.component.ts similarity index 88% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition-form/space-quota-definition-form.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/space-quota-definition-form/space-quota-definition-form.component.ts index 5e648aba80..67b37496eb 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition-form/space-quota-definition-form.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/space-quota-definition-form/space-quota-definition-form.component.ts @@ -4,13 +4,13 @@ import { ActivatedRoute } from '@angular/router'; import { Observable, Subscription } from 'rxjs'; import { filter, map, tap } from 'rxjs/operators'; -import { cfEntityCatalog } from '../../../../../cloud-foundry/src/cf-entity-catalog'; -import { createEntityRelationPaginationKey } from '../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; -import { ActiveRouteCfOrgSpace } from '../../../../../cloud-foundry/src/features/cloud-foundry/cf-page.types'; -import { getActiveRouteCfOrgSpaceProvider } from '../../../../../cloud-foundry/src/features/cloud-foundry/cf.helpers'; import { safeUnsubscribe } from '../../../../../core/src/core/utils.service'; import { endpointEntityType } from '../../../../../store/src/helpers/stratos-entity-factory'; import { IQuotaDefinition } from '../../../cf-api.types'; +import { cfEntityCatalog } from '../../../cf-entity-catalog'; +import { createEntityRelationPaginationKey } from '../../../entity-relations/entity-relations.types'; +import { ActiveRouteCfOrgSpace } from '../cf-page.types'; +import { getActiveRouteCfOrgSpaceProvider } from '../cf.helpers'; @Component({ diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition/space-quota-definition.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/space-quota-definition/space-quota-definition.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition/space-quota-definition.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/space-quota-definition/space-quota-definition.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition/space-quota-definition.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/space-quota-definition/space-quota-definition.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition/space-quota-definition.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/space-quota-definition/space-quota-definition.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition/space-quota-definition.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/space-quota-definition/space-quota-definition.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition/space-quota-definition.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/space-quota-definition/space-quota-definition.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition/space-quota-definition.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/space-quota-definition/space-quota-definition.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/space-quota-definition/space-quota-definition.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/space-quota-definition/space-quota-definition.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.theme.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.theme.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.theme.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.theme.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.ts similarity index 87% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.ts index 7cb9cae72d..6a87f4f761 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component.ts @@ -3,8 +3,8 @@ import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { filter, map, switchMap } from 'rxjs/operators'; -import { GetAllCfUsersAsAdmin } from '../../../../../../cloud-foundry/src/actions/users.actions'; -import { CFAppState } from '../../../../../../cloud-foundry/src/cf-app-state'; +import { GetAllCfUsersAsAdmin } from '../../../../actions/users.actions'; +import { CFAppState } from '../../../../cf-app-state'; import { CfUserService } from '../../../../shared/data-services/cf-user.service'; import { ActiveRouteCfOrgSpace } from '../../cf-page.types'; import { waitForCFPermissions } from '../../cf.helpers'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-build-packs/cloud-foundry-build-packs.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-build-packs/cloud-foundry-build-packs.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-build-packs/cloud-foundry-build-packs.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-build-packs/cloud-foundry-build-packs.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-build-packs/cloud-foundry-build-packs.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-build-packs/cloud-foundry-build-packs.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-build-packs/cloud-foundry-build-packs.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-build-packs/cloud-foundry-build-packs.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-build-packs/cloud-foundry-build-packs.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-build-packs/cloud-foundry-build-packs.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-build-packs/cloud-foundry-build-packs.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-build-packs/cloud-foundry-build-packs.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-build-packs/cloud-foundry-build-packs.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-build-packs/cloud-foundry-build-packs.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-build-packs/cloud-foundry-build-packs.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-build-packs/cloud-foundry-build-packs.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-apps/cloud-foundry-cell-apps.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-charts/cloud-foundry-cell-charts.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-summary/cloud-foundry-cell-summary.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell.service.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell.service.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell.service.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell.service.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cells.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cells.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cells.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cells.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cells.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cells.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cells.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cells.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cells.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cells.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cells.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cells.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cells.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cells.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cells.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cells.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-events/cloud-foundry-events.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-events/cloud-foundry-events.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-events/cloud-foundry-events.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-events/cloud-foundry-events.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-events/cloud-foundry-events.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-events/cloud-foundry-events.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-events/cloud-foundry-events.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-events/cloud-foundry-events.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-events/cloud-foundry-events.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-events/cloud-foundry-events.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-events/cloud-foundry-events.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-events/cloud-foundry-events.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-events/cloud-foundry-events.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-events/cloud-foundry-events.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-events/cloud-foundry-events.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-events/cloud-foundry-events.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-feature-flags/cloud-foundry-feature-flags.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-feature-flags/cloud-foundry-feature-flags.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-feature-flags/cloud-foundry-feature-flags.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-feature-flags/cloud-foundry-feature-flags.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-feature-flags/cloud-foundry-feature-flags.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-feature-flags/cloud-foundry-feature-flags.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-feature-flags/cloud-foundry-feature-flags.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-feature-flags/cloud-foundry-feature-flags.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-feature-flags/cloud-foundry-feature-flags.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-feature-flags/cloud-foundry-feature-flags.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-feature-flags/cloud-foundry-feature-flags.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-feature-flags/cloud-foundry-feature-flags.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-feature-flags/cloud-foundry-feature-flags.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-feature-flags/cloud-foundry-feature-flags.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-feature-flags/cloud-foundry-feature-flags.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-feature-flags/cloud-foundry-feature-flags.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-firehose/cloud-foundry-firehose-formatter.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose-formatter.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-firehose/cloud-foundry-firehose-formatter.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose-formatter.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-firehose/cloud-foundry-firehose.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-firehose/cloud-foundry-firehose.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-firehose/cloud-foundry-firehose.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-firehose/cloud-foundry-firehose.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-firehose/cloud-foundry-firehose.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-firehose/cloud-foundry-firehose.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-firehose/cloud-foundry-firehose.component.theme.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose.component.theme.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-firehose/cloud-foundry-firehose.component.theme.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose.component.theme.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-firehose/cloud-foundry-firehose.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-firehose/cloud-foundry-firehose.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-firehose/cloud-foundry-firehose.types.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose.types.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-firehose/cloud-foundry-firehose.types.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose.types.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organization-space-quotas/cloud-foundry-organization-space-quotas.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organization-space-quotas/cloud-foundry-organization-space-quotas.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organization-space-quotas/cloud-foundry-organization-space-quotas.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organization-space-quotas/cloud-foundry-organization-space-quotas.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organization-space-quotas/cloud-foundry-organization-space-quotas.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organization-space-quotas/cloud-foundry-organization-space-quotas.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organization-space-quotas/cloud-foundry-organization-space-quotas.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organization-space-quotas/cloud-foundry-organization-space-quotas.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organization-space-quotas/cloud-foundry-organization-space-quotas.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organization-space-quotas/cloud-foundry-organization-space-quotas.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organization-space-quotas/cloud-foundry-organization-space-quotas.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organization-space-quotas/cloud-foundry-organization-space-quotas.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organization-space-quotas/cloud-foundry-organization-space-quotas.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organization-space-quotas/cloud-foundry-organization-space-quotas.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organization-space-quotas/cloud-foundry-organization-space-quotas.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organization-space-quotas/cloud-foundry-organization-space-quotas.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-invite-user-link/cloud-foundry-invite-user-link.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-invite-user-link/cloud-foundry-invite-user-link.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-invite-user-link/cloud-foundry-invite-user-link.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-invite-user-link/cloud-foundry-invite-user-link.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-invite-user-link/cloud-foundry-invite-user-link.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-invite-user-link/cloud-foundry-invite-user-link.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-invite-user-link/cloud-foundry-invite-user-link.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-invite-user-link/cloud-foundry-invite-user-link.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-invite-user-link/cloud-foundry-invite-user-link.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-invite-user-link/cloud-foundry-invite-user-link.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-invite-user-link/cloud-foundry-invite-user-link.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-invite-user-link/cloud-foundry-invite-user-link.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-invite-user-link/cloud-foundry-invite-user-link.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-invite-user-link/cloud-foundry-invite-user-link.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-invite-user-link/cloud-foundry-invite-user-link.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-invite-user-link/cloud-foundry-invite-user-link.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-base/cloud-foundry-organization-base.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-base/cloud-foundry-organization-base.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-base/cloud-foundry-organization-base.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-base/cloud-foundry-organization-base.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-base/cloud-foundry-organization-base.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-base/cloud-foundry-organization-base.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-base/cloud-foundry-organization-base.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-base/cloud-foundry-organization-base.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-base/cloud-foundry-organization-base.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-base/cloud-foundry-organization-base.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-base/cloud-foundry-organization-base.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-base/cloud-foundry-organization-base.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-base/cloud-foundry-organization-base.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-base/cloud-foundry-organization-base.component.ts similarity index 95% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-base/cloud-foundry-organization-base.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-base/cloud-foundry-organization-base.component.ts index 00792c21cc..682da43532 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-base/cloud-foundry-organization-base.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-base/cloud-foundry-organization-base.component.ts @@ -2,8 +2,6 @@ import { Component } from '@angular/core'; import { Observable } from 'rxjs'; import { filter, first, map } from 'rxjs/operators'; -import { organizationEntityType } from '../../../../../../../cloud-foundry/src/cf-entity-types'; -import { IOrgFavMetadata } from '../../../../../../../cloud-foundry/src/cf-metadata-types'; import { getActionsFromExtensions, getTabsFromExtensions, @@ -19,6 +17,8 @@ import { EntitySchema } from '../../../../../../../store/src/helpers/entity-sche import { UserFavorite } from '../../../../../../../store/src/types/user-favorites.types'; import { getFavoriteFromEntity } from '../../../../../../../store/src/user-favorite-helpers'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; +import { organizationEntityType } from '../../../../../cf-entity-types'; +import { IOrgFavMetadata } from '../../../../../cf-metadata-types'; import { CF_ENDPOINT_TYPE } from '../../../../../cf-types'; import { CfUserService } from '../../../../../shared/data-services/cf-user.service'; import { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-events/cloud-foundry-organization-events.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-events/cloud-foundry-organization-events.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-events/cloud-foundry-organization-events.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-events/cloud-foundry-organization-events.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-events/cloud-foundry-organization-events.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-events/cloud-foundry-organization-events.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-events/cloud-foundry-organization-events.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-events/cloud-foundry-organization-events.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-events/cloud-foundry-organization-events.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-events/cloud-foundry-organization-events.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-events/cloud-foundry-organization-events.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-events/cloud-foundry-organization-events.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-events/cloud-foundry-organization-events.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-events/cloud-foundry-organization-events.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-events/cloud-foundry-organization-events.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-events/cloud-foundry-organization-events.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-organization-spaces.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-organization-spaces.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-organization-spaces.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-organization-spaces.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-organization-spaces.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-organization-spaces.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-organization-spaces.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-organization-spaces.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-organization-spaces.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-organization-spaces.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-organization-spaces.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-organization-spaces.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-organization-spaces.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-organization-spaces.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-organization-spaces.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-organization-spaces.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.ts similarity index 95% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.ts index e4b352225d..c1cea96630 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.ts @@ -3,9 +3,6 @@ import { Store } from '@ngrx/store'; import { combineLatest, Observable, of, Subscription } from 'rxjs'; import { first, map, tap } from 'rxjs/operators'; -import { CFAppState } from '../../../../../../../../cloud-foundry/src/cf-app-state'; -import { spaceEntityType } from '../../../../../../../../cloud-foundry/src/cf-entity-types'; -import { ISpaceFavMetadata } from '../../../../../../../../cloud-foundry/src/cf-metadata-types'; import { getActionsFromExtensions, getTabsFromExtensions, @@ -21,7 +18,10 @@ import { RouterNav } from '../../../../../../../../store/src/actions/router.acti import { FavoritesConfigMapper } from '../../../../../../../../store/src/favorite-config-mapper'; import { UserFavorite } from '../../../../../../../../store/src/types/user-favorites.types'; import { getFavoriteFromEntity } from '../../../../../../../../store/src/user-favorite-helpers'; +import { CFAppState } from '../../../../../../cf-app-state'; import { cfEntityFactory } from '../../../../../../cf-entity-factory'; +import { spaceEntityType } from '../../../../../../cf-entity-types'; +import { ISpaceFavMetadata } from '../../../../../../cf-metadata-types'; import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; import { CfUserService } from '../../../../../../shared/data-services/cf-user.service'; import { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-apps/cloud-foundry-space-apps.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-routes/cloud-foundry-space-routes.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-user-service-instances/cloud-foundry-space-user-service-instances.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.spec.ts similarity index 93% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.spec.ts index 3e7cbb2f19..0df268c9e1 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.spec.ts @@ -12,9 +12,7 @@ import { CloudFoundrySpaceServiceMock } from '../../../../../../../../test-frame import { CloudFoundryOrganizationService } from '../../../../../services/cloud-foundry-organization.service'; import { CloudFoundrySpaceService } from '../../../../../services/cloud-foundry-space.service'; import { CfAdminAddUserWarningComponent } from '../../../../cf-admin-add-user-warning/cf-admin-add-user-warning.component'; -import { - CloudFoundryInviteUserLinkComponent, -} from '../../../cloud-foundry-invite-user-link/cloud-foundry-invite-user-link.component'; +import { CloudFoundryInviteUserLinkComponent } from '../../../cf-invite-user-link/cloud-foundry-invite-user-link.component'; import { CloudFoundrySpaceUsersComponent } from './cloud-foundry-space-users.component'; describe('CloudFoundrySpaceUsersComponent', () => { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-summary/cloud-foundry-organization-summary.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-summary/cloud-foundry-organization-summary.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-summary/cloud-foundry-organization-summary.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-summary/cloud-foundry-organization-summary.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-summary/cloud-foundry-organization-summary.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-summary/cloud-foundry-organization-summary.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-summary/cloud-foundry-organization-summary.component.ts similarity index 97% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-summary/cloud-foundry-organization-summary.component.ts index 163a261a4a..2d9867947f 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-summary/cloud-foundry-organization-summary.component.ts @@ -4,12 +4,12 @@ import { Store } from '@ngrx/store'; import { combineLatest, Observable } from 'rxjs'; import { filter, first, map, pairwise, startWith, tap } from 'rxjs/operators'; -import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { ConfirmationDialogConfig } from '../../../../../../../core/src/shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from '../../../../../../../core/src/shared/components/confirmation-dialog.service'; import { RouterNav } from '../../../../../../../store/src/actions/router.actions'; import { entityCatalog } from '../../../../../../../store/src/entity-catalog/entity-catalog'; import { selectDeletionInfo } from '../../../../../../../store/src/selectors/api.selectors'; +import { CFAppState } from '../../../../../cf-app-state'; import { organizationEntityType } from '../../../../../cf-entity-types'; import { CF_ENDPOINT_TYPE } from '../../../../../cf-types'; import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users/cloud-foundry-organization-users.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-users/cloud-foundry-organization-users.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users/cloud-foundry-organization-users.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-users/cloud-foundry-organization-users.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users/cloud-foundry-organization-users.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-users/cloud-foundry-organization-users.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users/cloud-foundry-organization-users.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-users/cloud-foundry-organization-users.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users/cloud-foundry-organization-users.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-users/cloud-foundry-organization-users.component.spec.ts similarity index 92% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users/cloud-foundry-organization-users.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-users/cloud-foundry-organization-users.component.spec.ts index 352fc409a2..8c989eab9b 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users/cloud-foundry-organization-users.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-users/cloud-foundry-organization-users.component.spec.ts @@ -10,9 +10,7 @@ import { } from '../../../../../../test-framework/cloud-foundry-organization.service.mock'; import { CloudFoundryOrganizationService } from '../../../services/cloud-foundry-organization.service'; import { CfAdminAddUserWarningComponent } from '../../cf-admin-add-user-warning/cf-admin-add-user-warning.component'; -import { - CloudFoundryInviteUserLinkComponent, -} from '../cloud-foundry-invite-user-link/cloud-foundry-invite-user-link.component'; +import { CloudFoundryInviteUserLinkComponent } from '../cf-invite-user-link/cloud-foundry-invite-user-link.component'; import { CloudFoundryOrganizationUsersComponent } from './cloud-foundry-organization-users.component'; describe('CloudFoundryOrganizationUsersComponent', () => { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users/cloud-foundry-organization-users.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-users/cloud-foundry-organization-users.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users/cloud-foundry-organization-users.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-users/cloud-foundry-organization-users.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organizations.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cloud-foundry-organizations.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organizations.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cloud-foundry-organizations.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organizations.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cloud-foundry-organizations.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organizations.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cloud-foundry-organizations.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organizations.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cloud-foundry-organizations.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organizations.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cloud-foundry-organizations.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organizations.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cloud-foundry-organizations.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organizations.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cloud-foundry-organizations.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-quotas/cloud-foundry-quotas.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-quotas/cloud-foundry-quotas.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-quotas/cloud-foundry-quotas.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-quotas/cloud-foundry-quotas.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-quotas/cloud-foundry-quotas.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-quotas/cloud-foundry-quotas.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-quotas/cloud-foundry-quotas.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-quotas/cloud-foundry-quotas.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-quotas/cloud-foundry-quotas.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-quotas/cloud-foundry-quotas.component.spec.ts similarity index 71% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-quotas/cloud-foundry-quotas.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-quotas/cloud-foundry-quotas.component.spec.ts index 45606154a2..75913ddedf 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-quotas/cloud-foundry-quotas.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-quotas/cloud-foundry-quotas.component.spec.ts @@ -1,14 +1,10 @@ import { DatePipe } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { - CfOrgsListConfigService, -} from '../../../../../../cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-orgs-list-config.service'; -import { CFBaseTestModules } from '../../../../../../cloud-foundry/test-framework/cf-test-helper'; -import { - generateTestCfEndpointServiceProvider, -} from '../../../../../../cloud-foundry/test-framework/cloud-foundry-endpoint-service.helper'; import { TabNavService } from '../../../../../../core/tab-nav.service'; +import { CFBaseTestModules } from '../../../../../test-framework/cf-test-helper'; +import { generateTestCfEndpointServiceProvider } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; +import { CfOrgsListConfigService } from '../../../../shared/components/list/list-types/cf-orgs/cf-orgs-list-config.service'; import { CloudFoundryQuotasComponent } from './cloud-foundry-quotas.component'; describe('CloudFoundryQuotasComponent', () => { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-quotas/cloud-foundry-quotas.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-quotas/cloud-foundry-quotas.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-quotas/cloud-foundry-quotas.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-quotas/cloud-foundry-quotas.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-routes/cloud-foundry-routes.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-routes/cloud-foundry-routes.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-routes/cloud-foundry-routes.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-routes/cloud-foundry-routes.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-routes/cloud-foundry-routes.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-routes/cloud-foundry-routes.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-routes/cloud-foundry-routes.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-routes/cloud-foundry-routes.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-routes/cloud-foundry-routes.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-routes/cloud-foundry-routes.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-routes/cloud-foundry-routes.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-routes/cloud-foundry-routes.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-routes/cloud-foundry-routes.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-routes/cloud-foundry-routes.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-routes/cloud-foundry-routes.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-routes/cloud-foundry-routes.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-security-groups/cloud-foundry-security-groups.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-security-groups/cloud-foundry-security-groups.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-security-groups/cloud-foundry-security-groups.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-security-groups/cloud-foundry-security-groups.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-security-groups/cloud-foundry-security-groups.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-security-groups/cloud-foundry-security-groups.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-security-groups/cloud-foundry-security-groups.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-security-groups/cloud-foundry-security-groups.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-security-groups/cloud-foundry-security-groups.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-security-groups/cloud-foundry-security-groups.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-security-groups/cloud-foundry-security-groups.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-security-groups/cloud-foundry-security-groups.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-security-groups/cloud-foundry-security-groups.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-security-groups/cloud-foundry-security-groups.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-security-groups/cloud-foundry-security-groups.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-security-groups/cloud-foundry-security-groups.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-stacks/cloud-foundry-stacks.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-stacks/cloud-foundry-stacks.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-stacks/cloud-foundry-stacks.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-stacks/cloud-foundry-stacks.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-stacks/cloud-foundry-stacks.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-stacks/cloud-foundry-stacks.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-stacks/cloud-foundry-stacks.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-stacks/cloud-foundry-stacks.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-stacks/cloud-foundry-stacks.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-stacks/cloud-foundry-stacks.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-stacks/cloud-foundry-stacks.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-stacks/cloud-foundry-stacks.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-stacks/cloud-foundry-stacks.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-stacks/cloud-foundry-stacks.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-stacks/cloud-foundry-stacks.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-stacks/cloud-foundry-stacks.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-summary-tab/cloud-foundry-summary-tab.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-summary-tab/cloud-foundry-summary-tab.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-summary-tab/cloud-foundry-summary-tab.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-summary-tab/cloud-foundry-summary-tab.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-summary-tab/cloud-foundry-summary-tab.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-summary-tab/cloud-foundry-summary-tab.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-summary-tab/cloud-foundry-summary-tab.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-summary-tab/cloud-foundry-summary-tab.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-summary-tab/cloud-foundry-summary-tab.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-summary-tab/cloud-foundry-summary-tab.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-summary-tab/cloud-foundry-summary-tab.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-summary-tab/cloud-foundry-summary-tab.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-summary-tab/cloud-foundry-summary-tab.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-summary-tab/cloud-foundry-summary-tab.component.ts similarity index 93% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-summary-tab/cloud-foundry-summary-tab.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-summary-tab/cloud-foundry-summary-tab.component.ts index 6012c16f79..09c6a63b9f 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-summary-tab/cloud-foundry-summary-tab.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-summary-tab/cloud-foundry-summary-tab.component.ts @@ -3,7 +3,7 @@ import { Store } from '@ngrx/store'; import { combineLatest, Observable } from 'rxjs'; import { filter, map, startWith } from 'rxjs/operators'; -import { CFAppState } from '../../../../../../cloud-foundry/src/cf-app-state'; +import { CFAppState } from '../../../../cf-app-state'; import { goToAppWall } from '../../cf.helpers'; import { CloudFoundryEndpointService } from '../../services/cloud-foundry-endpoint.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-users/cloud-foundry-users.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-users/cloud-foundry-users.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-users/cloud-foundry-users.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-users/cloud-foundry-users.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-users/cloud-foundry-users.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-users/cloud-foundry-users.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-users/cloud-foundry-users.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-users/cloud-foundry-users.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-users/cloud-foundry-users.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-users/cloud-foundry-users.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-users/cloud-foundry-users.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-users/cloud-foundry-users.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-users/cloud-foundry-users.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-users/cloud-foundry-users.component.ts similarity index 84% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-users/cloud-foundry-users.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-users/cloud-foundry-users.component.ts index f184bf4ec8..d8f4896ffa 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-users/cloud-foundry-users.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-users/cloud-foundry-users.component.ts @@ -2,14 +2,12 @@ import { Component } from '@angular/core'; import { Router } from '@angular/router'; import { Store } from '@ngrx/store'; -import { CFAppState } from '../../../../../../cloud-foundry/src/cf-app-state'; import { CurrentUserPermissionsService } from '../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; +import { CFAppState } from '../../../../cf-app-state'; +import { CfUserListConfigService } from '../../../../shared/components/list/list-types/cf-users/cf-user-list-config.service'; import { CfUserService } from '../../../../shared/data-services/cf-user.service'; import { ActiveRouteCfOrgSpace } from '../../cf-page.types'; -import { - CfUserListConfigService, -} from './../../../../shared/components/list/list-types/cf-users/cf-user-list-config.service'; @Component({ selector: 'app-cloud-foundry-users', diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/configuration-dialog/user-invite-configuration-dialog.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/user-invites/configuration-dialog/user-invite-configuration-dialog.component.html similarity index 63% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/configuration-dialog/user-invite-configuration-dialog.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/user-invites/configuration-dialog/user-invite-configuration-dialog.component.html index ed13ca63d5..2e9dc254aa 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/configuration-dialog/user-invite-configuration-dialog.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/cf/user-invites/configuration-dialog/user-invite-configuration-dialog.component.html @@ -1,5 +1,7 @@
- +
@@ -15,13 +17,19 @@

- + + - + + -

+
\ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/configuration-dialog/user-invite-configuration-dialog.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/user-invites/configuration-dialog/user-invite-configuration-dialog.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/configuration-dialog/user-invite-configuration-dialog.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/user-invites/configuration-dialog/user-invite-configuration-dialog.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/configuration-dialog/user-invite-configuration-dialog.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/user-invites/configuration-dialog/user-invite-configuration-dialog.component.ts similarity index 98% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/configuration-dialog/user-invite-configuration-dialog.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/user-invites/configuration-dialog/user-invite-configuration-dialog.component.ts index ba3d856575..638704cc9b 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/configuration-dialog/user-invite-configuration-dialog.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/user-invites/configuration-dialog/user-invite-configuration-dialog.component.ts @@ -37,6 +37,7 @@ export class UserInviteConfigurationDialogComponent { public connectDelay = 1000; guid: string; + public showSecret = false; constructor( public fb: FormBuilder, diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/user-invite.service.ts b/src/frontend/packages/cloud-foundry/src/features/cf/user-invites/user-invite.service.ts similarity index 98% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/user-invite.service.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/user-invites/user-invite.service.ts index 7331193e11..da7ba84675 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/user-invites/user-invite.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/user-invites/user-invite.service.ts @@ -5,12 +5,12 @@ import { Store } from '@ngrx/store'; import { combineLatest, Observable, of as observableOf } from 'rxjs'; import { catchError, filter, map, switchMap } from 'rxjs/operators'; -import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; import { CurrentUserPermissionsService } from '../../../../../core/src/core/permissions/current-user-permissions.service'; import { environment } from '../../../../../core/src/environments/environment.prod'; import { ConfirmationDialogConfig } from '../../../../../core/src/shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from '../../../../../core/src/shared/components/confirmation-dialog.service'; import { stratosEntityCatalog } from '../../../../../store/src/stratos-entity-catalog'; +import { CFAppState } from '../../../cf-app-state'; import { CfCurrentUserPermissions } from '../../../user-permissions/cf-user-permissions-checkers'; import { ActiveRouteCfOrgSpace } from '../cf-page.types'; import { waitForCFPermissions } from '../cf.helpers'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users-create/invite-users-create.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/users/invite-users/invite-users-create/invite-users-create.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users-create/invite-users-create.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/users/invite-users/invite-users-create/invite-users-create.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users-create/invite-users-create.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/users/invite-users/invite-users-create/invite-users-create.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users-create/invite-users-create.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/users/invite-users/invite-users-create/invite-users-create.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users-create/invite-users-create.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/invite-users/invite-users-create/invite-users-create.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users-create/invite-users-create.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/users/invite-users/invite-users-create/invite-users-create.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users-create/invite-users-create.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/invite-users/invite-users-create/invite-users-create.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users-create/invite-users-create.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/users/invite-users/invite-users-create/invite-users-create.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/users/invite-users/invite-users.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/users/invite-users/invite-users.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/users/invite-users/invite-users.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/users/invite-users/invite-users.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/invite-users/invite-users.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/users/invite-users/invite-users.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/invite-users/invite-users.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/users/invite-users/invite-users.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/cf-roles.service.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/cf-roles.service.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/cf-roles.service.ts similarity index 99% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/cf-roles.service.ts index faa4668b08..db59e58a77 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/cf-roles.service.ts @@ -13,10 +13,6 @@ import { switchMap, } from 'rxjs/operators'; -import { - createEntityRelationKey, - createEntityRelationPaginationKey, -} from '../../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; import { CurrentUserPermissionsService } from '../../../../../../core/src/core/permissions/current-user-permissions.service'; import { endpointEntityType } from '../../../../../../store/src/helpers/stratos-entity-factory'; import { APIResource, EntityInfo } from '../../../../../../store/src/types/api.types'; @@ -25,6 +21,10 @@ import { IOrganization, ISpace } from '../../../../cf-api.types'; import { CFAppState } from '../../../../cf-app-state'; import { cfEntityCatalog } from '../../../../cf-entity-catalog'; import { organizationEntityType, spaceEntityType } from '../../../../cf-entity-types'; +import { + createEntityRelationKey, + createEntityRelationPaginationKey, +} from '../../../../entity-relations/entity-relations.types'; import { CfUserService } from '../../../../shared/data-services/cf-user.service'; import { createDefaultOrgRoles, createDefaultSpaceRoles } from '../../../../store/reducers/cf-users-roles.reducer'; import { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-confirm/manage-users-confirm.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-confirm/manage-users-confirm.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-confirm/manage-users-confirm.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-confirm/manage-users-confirm.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-confirm/manage-users-confirm.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-confirm/manage-users-confirm.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-confirm/manage-users-confirm.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-confirm/manage-users-confirm.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-confirm/manage-users-confirm.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-confirm/manage-users-confirm.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-confirm/manage-users-confirm.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-confirm/manage-users-confirm.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-confirm/manage-users-confirm.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-confirm/manage-users-confirm.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-confirm/manage-users-confirm.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-confirm/manage-users-confirm.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/manage-users-modify.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-modify/manage-users-modify.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/manage-users-modify.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-modify/manage-users-modify.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/manage-users-modify.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-modify/manage-users-modify.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/manage-users-modify.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-modify/manage-users-modify.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/manage-users-modify.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-modify/manage-users-modify.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/manage-users-modify.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-modify/manage-users-modify.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/manage-users-modify.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-modify/manage-users-modify.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/manage-users-modify.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-modify/manage-users-modify.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-modify/space-roles-list-wrapper/space-roles-list-wrapper.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-select/manage-users-select.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-select/manage-users-select.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-select/manage-users-select.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-select/manage-users-select.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-select/manage-users-select.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-select/manage-users-select.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-select/manage-users-select.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-select/manage-users-select.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-select/manage-users-select.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-select/manage-users-select.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-select/manage-users-select.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-select/manage-users-select.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-select/manage-users-select.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-select/manage-users-select.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-select/manage-users-select.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-select/manage-users-select.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users.component.spec.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users.component.spec.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/remove-user/remove-user.component.html b/src/frontend/packages/cloud-foundry/src/features/cf/users/remove-user/remove-user.component.html similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/remove-user/remove-user.component.html rename to src/frontend/packages/cloud-foundry/src/features/cf/users/remove-user/remove-user.component.html diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/remove-user/remove-user.component.scss b/src/frontend/packages/cloud-foundry/src/features/cf/users/remove-user/remove-user.component.scss similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/remove-user/remove-user.component.scss rename to src/frontend/packages/cloud-foundry/src/features/cf/users/remove-user/remove-user.component.scss diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/remove-user/remove-user.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/remove-user/remove-user.component.spec.ts similarity index 96% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/remove-user/remove-user.component.spec.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/users/remove-user/remove-user.component.spec.ts index 5879a847df..dc025ef094 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/remove-user/remove-user.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/users/remove-user/remove-user.component.spec.ts @@ -10,7 +10,7 @@ import { TabNavService } from '../../../../../../core/tab-nav.service'; import { generateCfStoreModules } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CfUserServiceTestProvider } from '../../../../../test-framework/user-service-helper'; import { ActiveRouteCfOrgSpace } from '../../cf-page.types'; -import { CfRolesService } from '../manage-users//cf-roles.service'; +import { CfRolesService } from '../manage-users/cf-roles.service'; import { UsersRolesConfirmComponent } from '../manage-users/manage-users-confirm/manage-users-confirm.component'; import { RemoveUserComponent } from './remove-user.component'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/remove-user/remove-user.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/remove-user/remove-user.component.ts similarity index 100% rename from src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/remove-user/remove-user.component.ts rename to src/frontend/packages/cloud-foundry/src/features/cf/users/remove-user/remove-user.component.ts diff --git a/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-catalog-page/service-catalog-page.component.ts b/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-catalog-page/service-catalog-page.component.ts index ba66604b3a..0a2e54631b 100644 --- a/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-catalog-page/service-catalog-page.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-catalog-page/service-catalog-page.component.ts @@ -7,7 +7,7 @@ import { CfServicesListConfigService, } from '../../../shared/components/list/list-types/cf-services/cf-services-list-config.service'; import { CloudFoundryService } from '../../../shared/data-services/cloud-foundry.service'; -import { getActiveRouteCfOrgSpaceProvider } from '../../cloud-foundry/cf.helpers'; +import { getActiveRouteCfOrgSpaceProvider } from '../../cf/cf.helpers'; @Component({ selector: 'app-service-catalog-page', diff --git a/src/frontend/packages/cloud-foundry/src/features/service-catalog/services-helper.ts b/src/frontend/packages/cloud-foundry/src/features/service-catalog/services-helper.ts index d3fce2628e..21db362ed1 100644 --- a/src/frontend/packages/cloud-foundry/src/features/service-catalog/services-helper.ts +++ b/src/frontend/packages/cloud-foundry/src/features/service-catalog/services-helper.ts @@ -22,7 +22,7 @@ import { CFAppState } from '../../cf-app-state'; import { cfEntityCatalog } from '../../cf-entity-catalog'; import { organizationEntityType, servicePlanEntityType, spaceEntityType } from '../../cf-entity-types'; import { QParam, QParamJoiners } from '../../shared/q-param'; -import { fetchTotalResults } from '../cloud-foundry/cf.helpers'; +import { fetchTotalResults } from '../cf/cf.helpers'; import { ServicePlanAccessibility } from './services.service'; export const getSvcAvailability = ( diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.spec.ts index 8cff3346aa..68f6a9d59e 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.spec.ts @@ -12,7 +12,7 @@ import { generateCfBaseTestModulesNoShared, generateTestCfEndpointService, } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { UserInviteService } from '../../../../features/cloud-foundry/user-invites/user-invite.service'; +import { UserInviteService } from '../../../../features/cf/user-invites/user-invite.service'; import { CardCfInfoComponent } from './card-cf-info.component'; describe('CardCfInfoComponent', () => { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.ts index 89afc74d73..8be8e3f5cb 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.ts @@ -7,14 +7,11 @@ import { map, tap } from 'rxjs/operators'; import { EntityServiceFactory } from '../../../../../../store/src/entity-service-factory.service'; import { ICfV2Info } from '../../../../cf-api.types'; -import { CloudFoundryEndpointService } from '../../../../features/cloud-foundry/services/cloud-foundry-endpoint.service'; +import { CloudFoundryEndpointService } from '../../../../features/cf/services/cloud-foundry-endpoint.service'; import { UserInviteConfigurationDialogComponent, -} from '../../../../features/cloud-foundry/user-invites/configuration-dialog/user-invite-configuration-dialog.component'; -import { - UserInviteConfigureService, - UserInviteService, -} from '../../../../features/cloud-foundry/user-invites/user-invite.service'; +} from '../../../../features/cf/user-invites/configuration-dialog/user-invite-configuration-dialog.component'; +import { UserInviteConfigureService, UserInviteService } from '../../../../features/cf/user-invites/user-invite.service'; @Component({ selector: 'app-card-cf-info', diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-org-user-details/card-cf-org-user-details.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-org-user-details/card-cf-org-user-details.component.spec.ts index 7b9e5540dd..45b086bc37 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-org-user-details/card-cf-org-user-details.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-org-user-details/card-cf-org-user-details.component.spec.ts @@ -16,9 +16,7 @@ import { generateTestCfEndpointServiceProvider, } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CloudFoundryOrganizationServiceMock } from '../../../../../test-framework/cloud-foundry-organization.service.mock'; -import { - CloudFoundryOrganizationService, -} from '../../../../features/cloud-foundry/services/cloud-foundry-organization.service'; +import { CloudFoundryOrganizationService } from '../../../../features/cf/services/cloud-foundry-organization.service'; import { CfOrgSpaceDataService } from '../../../data-services/cf-org-space-service.service'; import { CfUserService } from '../../../data-services/cf-user.service'; import { CardCfOrgUserDetailsComponent } from './card-cf-org-user-details.component'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-org-user-details/card-cf-org-user-details.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-org-user-details/card-cf-org-user-details.component.ts index 151fbfb9c4..6fe1eda951 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-org-user-details/card-cf-org-user-details.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-org-user-details/card-cf-org-user-details.component.ts @@ -1,12 +1,8 @@ import { Component } from '@angular/core'; -import { - CloudFoundryEndpointService, -} from '../../../../../../cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service'; -import { - CloudFoundryOrganizationService, -} from '../../../../../../cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service'; import { CfUserService } from '../../../../../../cloud-foundry/src/shared/data-services/cf-user.service'; +import { CloudFoundryEndpointService } from '../../../../features/cf/services/cloud-foundry-endpoint.service'; +import { CloudFoundryOrganizationService } from '../../../../features/cf/services/cloud-foundry-organization.service'; @Component({ selector: 'app-card-cf-org-user-details', diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/card-cf-recent-apps.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/card-cf-recent-apps.component.spec.ts index e05382f2c2..b93ba4295a 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/card-cf-recent-apps.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/card-cf-recent-apps.component.spec.ts @@ -16,7 +16,7 @@ import { generateActiveRouteCfOrgSpaceMock, generateCfBaseTestModulesNoShared, } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { CloudFoundryEndpointService } from '../../../../features/cloud-foundry/services/cloud-foundry-endpoint.service'; +import { CloudFoundryEndpointService } from '../../../../features/cf/services/cloud-foundry-endpoint.service'; import { CfUserService } from '../../../data-services/cf-user.service'; import { CardCfRecentAppsComponent } from './card-cf-recent-apps.component'; import { CompactAppCardComponent } from './compact-app-card/compact-app-card.component'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/card-cf-recent-apps.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/card-cf-recent-apps.component.ts index 6edd6010ab..136fc16ee1 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/card-cf-recent-apps.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/card-cf-recent-apps.component.ts @@ -4,13 +4,10 @@ import { Observable } from 'rxjs'; import { filter, first, map, tap } from 'rxjs/operators'; import { CFAppState } from '../../../../../../cloud-foundry/src/cf-app-state'; -import { - appDataSort, - CloudFoundryEndpointService, -} from '../../../../../../cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service'; import { APIResource } from '../../../../../../store/src/types/api.types'; import { IApp } from '../../../../cf-api.types'; import { cfEntityCatalog } from '../../../../cf-entity-catalog'; +import { appDataSort, CloudFoundryEndpointService } from '../../../../features/cf/services/cloud-foundry-endpoint.service'; const RECENT_ITEMS_COUNT = 10; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/compact-app-card/compact-app-card.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/compact-app-card/compact-app-card.component.spec.ts index 8054860739..d6655a60b1 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/compact-app-card/compact-app-card.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/compact-app-card/compact-app-card.component.spec.ts @@ -7,7 +7,7 @@ import { ApplicationStateIconPipe, } from '../../../../../../../core/src/shared/components/application-state/application-state-icon/application-state-icon.pipe'; import { generateCfBaseTestModulesNoShared } from '../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; import { ApplicationStateService } from '../../../../services/application-state.service'; import { CompactAppCardComponent } from './compact-app-card.component'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/compact-app-card/compact-app-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/compact-app-card/compact-app-card.component.ts index d982f91991..456ba80949 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/compact-app-card/compact-app-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-recent-apps/compact-app-card/compact-app-card.component.ts @@ -5,9 +5,9 @@ import { map, startWith } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { ApplicationService } from '../../../../../../../cloud-foundry/src/features/applications/application.service'; -import { ActiveRouteCfOrgSpace } from '../../../../../../../cloud-foundry/src/features/cloud-foundry/cf-page.types'; import { BREADCRUMB_URL_PARAM } from '../../../../../../../core/src/shared/components/breadcrumbs/breadcrumbs.types'; import { StratosStatus } from '../../../../../../../store/src/types/shared.types'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; import { ApplicationStateData, ApplicationStateService } from '../../../../services/application-state.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-space-details/card-cf-space-details.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-space-details/card-cf-space-details.component.spec.ts index 11aadb41a3..72fc9fc91e 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-space-details/card-cf-space-details.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-space-details/card-cf-space-details.component.spec.ts @@ -10,7 +10,7 @@ import { MetadataItemComponent } from '../../../../../../core/src/shared/compone import { EntityMonitorFactory } from '../../../../../../store/src/monitors/entity-monitor.factory.service'; import { generateCfBaseTestModulesNoShared } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CloudFoundrySpaceServiceMock } from '../../../../../test-framework/cloud-foundry-space.service.mock'; -import { CloudFoundrySpaceService } from '../../../../features/cloud-foundry/services/cloud-foundry-space.service'; +import { CloudFoundrySpaceService } from '../../../../features/cf/services/cloud-foundry-space.service'; import { CardCfSpaceDetailsComponent } from './card-cf-space-details.component'; describe('CardCfSpaceDetailsComponent', () => { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-space-details/card-cf-space-details.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-space-details/card-cf-space-details.component.ts index 555865d96e..ab6ed99ffe 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-space-details/card-cf-space-details.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-space-details/card-cf-space-details.component.ts @@ -4,13 +4,11 @@ import { Store } from '@ngrx/store'; import { Observable, Subscription } from 'rxjs'; import { map } from 'rxjs/operators'; -import { - CloudFoundrySpaceService, -} from '../../../../../../cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service'; import { safeUnsubscribe } from '../../../../../../core/src/core/utils.service'; import { SnackBarService } from '../../../../../../core/src/shared/services/snackbar.service'; import { RouterNav } from '../../../../../../store/src/actions/router.actions'; import { AppState } from '../../../../../../store/src/app-state'; +import { CloudFoundrySpaceService } from '../../../../features/cf/services/cloud-foundry-space.service'; @Component({ selector: 'app-card-cf-space-details', diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-user-info/card-cf-user-info.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-user-info/card-cf-user-info.component.ts index 3c6ac6c1c6..86ed14b167 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-user-info/card-cf-user-info.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-user-info/card-cf-user-info.component.ts @@ -1,8 +1,6 @@ import { Component, OnInit } from '@angular/core'; -import { - CloudFoundryEndpointService, -} from '../../../../../../cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service'; +import { CloudFoundryEndpointService } from '../../../../features/cf/services/cloud-foundry-endpoint.service'; @Component({ selector: 'app-card-cf-user-info', diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cf-role-checkbox/cf-role-checkbox.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cf-role-checkbox/cf-role-checkbox.component.spec.ts index a1620f3c8e..e7ade5e7bd 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cf-role-checkbox/cf-role-checkbox.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cf-role-checkbox/cf-role-checkbox.component.spec.ts @@ -7,8 +7,8 @@ import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-m import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; import { generateCfStoreModules } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CfUserServiceTestProvider } from '../../../../test-framework/user-service-helper'; -import { ActiveRouteCfOrgSpace } from '../../../features/cloud-foundry/cf-page.types'; -import { CfRolesService } from '../../../features/cloud-foundry/users/manage-users/cf-roles.service'; +import { ActiveRouteCfOrgSpace } from '../../../features/cf/cf-page.types'; +import { CfRolesService } from '../../../features/cf/users/manage-users/cf-roles.service'; import { CfRoleCheckboxComponent } from './cf-role-checkbox.component'; describe('CfRoleCheckboxComponent', () => { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cf-role-checkbox/cf-role-checkbox.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cf-role-checkbox/cf-role-checkbox.component.ts index f2337914c2..a7d949e8dd 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cf-role-checkbox/cf-role-checkbox.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cf-role-checkbox/cf-role-checkbox.component.ts @@ -7,8 +7,8 @@ import { UsersRolesSetOrgRole, UsersRolesSetSpaceRole } from '../../../../../clo import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; import { CfUserRolesSelected } from '../../../../../cloud-foundry/src/store/types/users-roles.types'; import { CurrentUserPermissionsService } from '../../../../../core/src/core/permissions/current-user-permissions.service'; -import { canUpdateOrgSpaceRoles } from '../../../features/cloud-foundry/cf.helpers'; -import { CfRolesService } from '../../../features/cloud-foundry/users/manage-users/cf-roles.service'; +import { canUpdateOrgSpaceRoles } from '../../../features/cf/cf.helpers'; +import { CfRolesService } from '../../../features/cf/users/manage-users/cf-roles.service'; import { selectCfUsersIsRemove, selectCfUsersIsSetByUsername, diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cloud-foundry-events-list/cloud-foundry-events-list.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cloud-foundry-events-list/cloud-foundry-events-list.component.spec.ts index 90b3c30f3f..97f86c96a9 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cloud-foundry-events-list/cloud-foundry-events-list.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cloud-foundry-events-list/cloud-foundry-events-list.component.spec.ts @@ -2,8 +2,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; import { CFBaseTestModules } from '../../../../test-framework/cf-test-helper'; -import { ActiveRouteCfOrgSpace } from '../../../features/cloud-foundry/cf-page.types'; -import { CloudFoundryEndpointService } from '../../../features/cloud-foundry/services/cloud-foundry-endpoint.service'; +import { ActiveRouteCfOrgSpace } from '../../../features/cf/cf-page.types'; +import { CloudFoundryEndpointService } from '../../../features/cf/services/cloud-foundry-endpoint.service'; import { CfUserService } from '../../data-services/cf-user.service'; import { CfAllEventsConfigService } from '../list/list-types/cf-events/types/cf-all-events-config.service'; import { CloudFoundryEventsListComponent } from './cloud-foundry-events-list.component'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.ts index 2753ce8909..a06230eeb0 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-instance/cf-app-instances-config.service.ts @@ -28,7 +28,7 @@ import { PaginationMonitorFactory } from '../../../../../../../store/src/monitor import { IMetricMatrixResult, IMetrics } from '../../../../../../../store/src/types/base-metric.types'; import { IMetricApplication, MetricQueryType } from '../../../../../../../store/src/types/metric.types'; import { ApplicationService } from '../../../../../features/applications/application.service'; -import { CfCellHelper } from '../../../../../features/cloud-foundry/cf-cell.helpers'; +import { CfCellHelper } from '../../../../../features/cf/cf-cell.helpers'; import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { ListAppInstance } from './app-instance-types'; import { CfAppInstancesDataSource } from './cf-app-instances-data-source'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.ts index 50fb563f34..23b66083dc 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.ts @@ -22,7 +22,7 @@ import { cfEntityCatalog } from '../../../../../../cf-entity-catalog'; import { cfEntityFactory } from '../../../../../../cf-entity-factory'; import { serviceBindingEntityType } from '../../../../../../cf-entity-types'; import { ApplicationService } from '../../../../../../features/applications/application.service'; -import { isUserProvidedServiceInstance } from '../../../../../../features/cloud-foundry/cf.helpers'; +import { isUserProvidedServiceInstance } from '../../../../../../features/cf/cf.helpers'; import { getServiceBrokerName, getServiceName, diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-list-config.service.ts index 2d39cd38df..5197f7db8b 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-list-config.service.ts @@ -21,7 +21,7 @@ import { RouterNav } from '../../../../../../../store/src/actions/router.actions import { APIResource } from '../../../../../../../store/src/types/api.types'; import { IServiceBinding } from '../../../../../cf-api-svc.types'; import { ApplicationService } from '../../../../../features/applications/application.service'; -import { isServiceInstance, isUserProvidedServiceInstance } from '../../../../../features/cloud-foundry/cf.helpers'; +import { isServiceInstance, isUserProvidedServiceInstance } from '../../../../../features/cf/cf.helpers'; import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { ServiceActionHelperService } from '../../../../data-services/service-action-helper.service'; import { CSI_CANCEL_URL } from '../../../add-service-instance/csi-mode.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/cf-apps-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/cf-apps-data-source.ts index 0e4e9e1269..137f9073dd 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/cf-apps-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/cf-apps-data-source.ts @@ -5,6 +5,7 @@ import { tag } from 'rxjs-spy/operators/tag'; import { debounceTime, delay, distinctUntilChanged, map, withLatestFrom } from 'rxjs/operators'; import { GetAllApplications } from '../../../../../../../cloud-foundry/src/actions/application.actions'; +import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { applicationEntityType, organizationEntityType, @@ -27,10 +28,9 @@ import { APIResource } from '../../../../../../../store/src/types/api.types'; import { PaginationParam } from '../../../../../../../store/src/types/pagination.types'; import { cfEntityCatalog } from '../../../../../cf-entity-catalog'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; -import { cfOrgSpaceFilter } from '../../../../../features/cloud-foundry/cf.helpers'; +import { cfOrgSpaceFilter } from '../../../../../features/cf/cf.helpers'; import { CFListDataSource } from '../../../../cf-list-data-source'; import { createCfOrSpaceMultipleFilterFn } from '../../../../data-services/cf-org-space-service.service'; -import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; export class CfAppsDataSource extends CFListDataSource { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-buildpacks/cf-buildpacks-list-config.service.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-buildpacks/cf-buildpacks-list-config.service.spec.ts index bc239c35ee..b5c46666f8 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-buildpacks/cf-buildpacks-list-config.service.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-buildpacks/cf-buildpacks-list-config.service.spec.ts @@ -1,7 +1,7 @@ import { inject, TestBed } from '@angular/core/testing'; import { generateCfBaseTestModules } from '../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; import { CfBuildpacksListConfigService } from './cf-buildpacks-list-config.service'; describe('CfBuildpacksListConfigService', () => { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-buildpacks/cf-buildpacks-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-buildpacks/cf-buildpacks-list-config.service.ts index 779c9cbcad..911eb5a927 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-buildpacks/cf-buildpacks-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-buildpacks/cf-buildpacks-list-config.service.ts @@ -5,7 +5,7 @@ import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state' import { ITableColumn } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { IBuildpack } from '../../../../../cf-api.types'; -import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; import { BaseCfListConfig } from '../base-cf/base-cf-list-config'; import { CfBuildpackCardComponent } from './cf-buildpack-card/cf-buildpack-card.component'; import { CfBuildpacksDataSource } from './cf-buildpacks-data-source'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-apps/cf-cell-apps-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-apps/cf-cell-apps-list-config.service.ts index 6d4c6d7b02..7cdbeb0e4a 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-apps/cf-cell-apps-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-apps/cf-cell-apps-list-config.service.ts @@ -7,7 +7,7 @@ import { ListViewTypes } from '../../../../../../../core/src/shared/components/l import { ListView } from '../../../../../../../store/src/actions/list.actions'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { IApp, ISpace } from '../../../../../cf-api.types'; -import { ActiveRouteCfCell } from '../../../../../features/cloud-foundry/cf-page.types'; +import { ActiveRouteCfCell } from '../../../../../features/cf/cf-page.types'; import { BaseCfListConfig } from '../base-cf/base-cf-list-config'; import { CfCellApp, CfCellAppsDataSource } from './cf-cell-apps-source'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-health/cf-cell-health-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-health/cf-cell-health-list-config.service.ts index 86bb2ee270..51e4e54f65 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-health/cf-cell-health-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-health/cf-cell-health-list-config.service.ts @@ -17,10 +17,10 @@ import { ListView } from '../../../../../../../store/src/actions/list.actions'; import { PaginationMonitorFactory } from '../../../../../../../store/src/monitors/pagination-monitor.factory'; import { FetchCFCellMetricsPaginatedAction } from '../../../../../actions/cf-metrics.actions'; import { CFAppState } from '../../../../../cf-app-state'; -import { CfCellHelper } from '../../../../../features/cloud-foundry/cf-cell.helpers'; +import { CfCellHelper } from '../../../../../features/cf/cf-cell.helpers'; import { CloudFoundryCellService, -} from '../../../../../features/cloud-foundry/tabs/cloud-foundry-cells/cloud-foundry-cell/cloud-foundry-cell.service'; +} from '../../../../../features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell.service'; import { BaseCfListConfig } from '../base-cf/base-cf-list-config'; import { CfCellHealthDataSource, CfCellHealthEntry, CfCellHealthState } from './cf-cell-health-source'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cells/cf-cells-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cells/cf-cells-list-config.service.ts index 019ab20631..783f2b148f 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cells/cf-cells-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cells/cf-cells-list-config.service.ts @@ -17,8 +17,8 @@ import { ListView } from '../../../../../../../store/src/actions/list.actions'; import { PaginationMonitorFactory } from '../../../../../../../store/src/monitors/pagination-monitor.factory'; import { IMetricVectorResult } from '../../../../../../../store/src/types/base-metric.types'; import { IMetricCell } from '../../../../../../../store/src/types/metric.types'; -import { CfCellHelper } from '../../../../../features/cloud-foundry/cf-cell.helpers'; -import { ActiveRouteCfCell } from '../../../../../features/cloud-foundry/cf-page.types'; +import { CfCellHelper } from '../../../../../features/cf/cf-cell.helpers'; +import { ActiveRouteCfCell } from '../../../../../features/cf/cf-page.types'; import { BaseCfListConfig } from '../base-cf/base-cf-list-config'; import { CfCellsDataSource } from './cf-cells-data-source'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-events/types/cf-all-events-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-events/types/cf-all-events-config.service.ts index c1a013b7a4..5e5667be57 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-events/types/cf-all-events-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-events/types/cf-all-events-config.service.ts @@ -4,9 +4,7 @@ import { Store } from '@ngrx/store'; import { IListConfig } from '../../../../../../../../core/src/shared/components/list/list.component.types'; import { APIResource } from '../../../../../../../../store/src/types/api.types'; import { CFAppState } from '../../../../../../cf-app-state'; -import { - CloudFoundryEndpointService, -} from '../../../../../../features/cloud-foundry/services/cloud-foundry-endpoint.service'; +import { CloudFoundryEndpointService } from '../../../../../../features/cf/services/cloud-foundry-endpoint.service'; import { CfEventsConfigService } from '../cf-events-config.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-events/types/cf-org-events-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-events/types/cf-org-events-config.service.ts index 0897130e93..087cc0e4bc 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-events/types/cf-org-events-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-events/types/cf-org-events-config.service.ts @@ -4,9 +4,7 @@ import { Store } from '@ngrx/store'; import { IListConfig } from '../../../../../../../../core/src/shared/components/list/list.component.types'; import { APIResource } from '../../../../../../../../store/src/types/api.types'; import { CFAppState } from '../../../../../../cf-app-state'; -import { - CloudFoundryOrganizationService, -} from '../../../../../../features/cloud-foundry/services/cloud-foundry-organization.service'; +import { CloudFoundryOrganizationService } from '../../../../../../features/cf/services/cloud-foundry-organization.service'; import { CfEventsConfigService } from '../cf-events-config.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-events/types/cf-space-events-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-events/types/cf-space-events-config.service.ts index e792419597..c665577e32 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-events/types/cf-space-events-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-events/types/cf-space-events-config.service.ts @@ -4,7 +4,7 @@ import { Store } from '@ngrx/store'; import { IListConfig } from '../../../../../../../../core/src/shared/components/list/list.component.types'; import { APIResource } from '../../../../../../../../store/src/types/api.types'; import { CFAppState } from '../../../../../../cf-app-state'; -import { CloudFoundrySpaceService } from '../../../../../../features/cloud-foundry/services/cloud-foundry-space.service'; +import { CloudFoundrySpaceService } from '../../../../../../features/cf/services/cloud-foundry-space.service'; import { CfEventsConfigService } from '../cf-events-config.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-feature-flags/cf-feature-flags-list-config.service.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-feature-flags/cf-feature-flags-list-config.service.spec.ts index be0423ce3a..8349afd1ca 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-feature-flags/cf-feature-flags-list-config.service.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-feature-flags/cf-feature-flags-list-config.service.spec.ts @@ -1,7 +1,7 @@ import { inject, TestBed } from '@angular/core/testing'; import { generateCfBaseTestModules } from '../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; import { CfFeatureFlagsListConfigService } from './cf-feature-flags-list-config.service'; describe('CfFeatureFlagsListConfigService', () => { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-feature-flags/cf-feature-flags-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-feature-flags/cf-feature-flags-list-config.service.ts index f943e373ad..e332ca154d 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-feature-flags/cf-feature-flags-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-feature-flags/cf-feature-flags-list-config.service.ts @@ -6,7 +6,7 @@ import { ITableColumn } from '../../../../../../../core/src/shared/components/li import { IListFilter, ListViewTypes } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { ListView } from '../../../../../../../store/src/actions/list.actions'; import { IFeatureFlag } from '../../../../../cf-api.types'; -import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; import { BaseCfListConfig } from '../base-cf/base-cf-list-config'; import { CfFeatureFlagsDataSource } from './cf-feature-flags-data-source'; import { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-org-users/cf-org-users-list-config.service.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-org-users/cf-org-users-list-config.service.spec.ts index 10944e2dd7..361466c74f 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-org-users/cf-org-users-list-config.service.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-org-users/cf-org-users-list-config.service.spec.ts @@ -11,12 +11,10 @@ import { import { CloudFoundryOrganizationServiceMock, } from '../../../../../../test-framework/cloud-foundry-organization.service.mock'; -import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; -import { CloudFoundryEndpointService } from '../../../../../features/cloud-foundry/services/cloud-foundry-endpoint.service'; -import { - CloudFoundryOrganizationService, -} from '../../../../../features/cloud-foundry/services/cloud-foundry-organization.service'; -import { UserInviteService } from '../../../../../features/cloud-foundry/user-invites/user-invite.service'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; +import { CloudFoundryEndpointService } from '../../../../../features/cf/services/cloud-foundry-endpoint.service'; +import { CloudFoundryOrganizationService } from '../../../../../features/cf/services/cloud-foundry-organization.service'; +import { UserInviteService } from '../../../../../features/cf/user-invites/user-invite.service'; import { CfOrgUsersListConfigService } from './cf-org-users-list-config.service'; describe('CfOrgUsersListConfigService', () => { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-org-users/cf-org-users-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-org-users/cf-org-users-list-config.service.ts index 4d125e2cca..5a49886ad2 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-org-users/cf-org-users-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-org-users/cf-org-users-list-config.service.ts @@ -6,10 +6,8 @@ import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state' import { CurrentUserPermissionsService, } from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; -import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; -import { - CloudFoundryOrganizationService, -} from '../../../../../features/cloud-foundry/services/cloud-foundry-organization.service'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; +import { CloudFoundryOrganizationService } from '../../../../../features/cf/services/cloud-foundry-organization.service'; import { CfUser } from '../../../../../store/types/cf-user.types'; import { CfUserService } from '../../../../data-services/cf-user.service'; import { CfUserListConfigService } from '../cf-users/cf-user-list-config.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component.ts index f7e4ff459b..fee067f62f 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component.ts @@ -25,14 +25,10 @@ import { getFavoriteFromEntity } from '../../../../../../../../store/src/user-fa import { IApp, IOrganization } from '../../../../../../cf-api.types'; import { cfEntityFactory } from '../../../../../../cf-entity-factory'; import { getStartedAppInstanceCount } from '../../../../../../cf.helpers'; -import { getOrgRolesString } from '../../../../../../features/cloud-foundry/cf.helpers'; -import { - CloudFoundryEndpointService, -} from '../../../../../../features/cloud-foundry/services/cloud-foundry-endpoint.service'; -import { OrgQuotaHelper } from '../../../../../../features/cloud-foundry/services/cloud-foundry-organization-quota'; -import { - createOrgQuotaDefinition, -} from '../../../../../../features/cloud-foundry/services/cloud-foundry-organization.service'; +import { getOrgRolesString } from '../../../../../../features/cf/cf.helpers'; +import { CloudFoundryEndpointService } from '../../../../../../features/cf/services/cloud-foundry-endpoint.service'; +import { OrgQuotaHelper } from '../../../../../../features/cf/services/cloud-foundry-organization-quota'; +import { createOrgQuotaDefinition } from '../../../../../../features/cf/services/cloud-foundry-organization.service'; import { createUserRoleInOrg } from '../../../../../../store/types/cf-user.types'; import { CfCurrentUserPermissions } from '../../../../../../user-permissions/cf-user-permissions-checkers'; import { CfUserService } from '../../../../../data-services/cf-user.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-orgs-data-source.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-orgs-data-source.service.ts index f0c23e0d95..86c11ed8c3 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-orgs-data-source.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-orgs-data-source.service.ts @@ -9,7 +9,7 @@ import { import { IListConfig } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; -import { CloudFoundryEndpointService } from '../../../../../features/cloud-foundry/services/cloud-foundry-endpoint.service'; +import { CloudFoundryEndpointService } from '../../../../../features/cf/services/cloud-foundry-endpoint.service'; export class CfOrgsDataSourceService extends ListDataSource { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-orgs-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-orgs-list-config.service.ts index 4ab0ef44aa..3986f60290 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-orgs-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-orgs-list-config.service.ts @@ -5,7 +5,7 @@ import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state' import { ITableColumn } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { IOrganization } from '../../../../../cf-api.types'; -import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; import { BaseCfListConfig } from '../base-cf/base-cf-list-config'; import { CfOrgCardComponent } from './cf-org-card/cf-org-card.component'; import { CfOrgsDataSourceService } from './cf-orgs-data-source.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-quotas/cf-quotas-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-quotas/cf-quotas-list-config.service.ts index d4e001d8aa..448a927339 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-quotas/cf-quotas-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-quotas/cf-quotas-list-config.service.ts @@ -5,7 +5,6 @@ import { Observable, Subscription } from 'rxjs'; import { DeleteQuotaDefinition } from '../../../../../../../cloud-foundry/src/actions/quota-definitions.actions'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; -import { ActiveRouteCfOrgSpace } from '../../../../../../../cloud-foundry/src/features/cloud-foundry/cf-page.types'; import { BaseCfListConfig, } from '../../../../../../../cloud-foundry/src/shared/components/list/list-types/base-cf/base-cf-list-config'; @@ -19,6 +18,7 @@ import { IListAction, ListViewTypes } from '../../../../../../../core/src/shared import { RouterNav } from '../../../../../../../store/src/actions/router.actions'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { IQuotaDefinition } from '../../../../../cf-api.types'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { CfQuotasDataSourceService } from './cf-quotas-data-source.service'; import { TableCellQuotaComponent } from './table-cell-quota/table-cell-quota.component'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-routes/cf-routes-data-source-base.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-routes/cf-routes-data-source-base.ts index f176f59556..ae52336a7e 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-routes/cf-routes-data-source-base.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-routes/cf-routes-data-source-base.ts @@ -23,7 +23,7 @@ import { IRoute } from '../../../../../cf-api.types'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; import { CF_ENDPOINT_TYPE } from '../../../../../cf-types'; import { getRoute, isTCPRoute } from '../../../../../features/applications/routes/routes.helper'; -import { cfOrgSpaceFilter } from '../../../../../features/cloud-foundry/cf.helpers'; +import { cfOrgSpaceFilter } from '../../../../../features/cf/cf.helpers'; import { CFListDataSource } from '../../../../cf-list-data-source'; import { createCfOrSpaceMultipleFilterFn } from '../../../../data-services/cf-org-space-service.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-routes/cf-routes-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-routes/cf-routes-list-config.service.ts index 9767322990..f800809ecf 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-routes/cf-routes-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-routes/cf-routes-list-config.service.ts @@ -14,7 +14,7 @@ import { IListMultiFilterConfig, } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { APIResource } from '../../../../../../../store/src/types/api.types'; -import { CloudFoundryEndpointService } from '../../../../../features/cloud-foundry/services/cloud-foundry-endpoint.service'; +import { CloudFoundryEndpointService } from '../../../../../features/cf/services/cloud-foundry-endpoint.service'; import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { CfOrgSpaceDataService, diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-routes/table-cell-route-apps-attached/table-cell-route-apps-attached.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-routes/table-cell-route-apps-attached/table-cell-route-apps-attached.component.spec.ts index 9ac357c8b7..a63b3659e7 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-routes/table-cell-route-apps-attached/table-cell-route-apps-attached.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-routes/table-cell-route-apps-attached/table-cell-route-apps-attached.component.spec.ts @@ -5,7 +5,7 @@ import { generateCfBaseTestModulesNoShared, } from '../../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CloudFoundrySpaceServiceMock } from '../../../../../../../test-framework/cloud-foundry-space.service.mock'; -import { CloudFoundrySpaceService } from '../../../../../../features/cloud-foundry/services/cloud-foundry-space.service'; +import { CloudFoundrySpaceService } from '../../../../../../features/cf/services/cloud-foundry-space.service'; import { TableCellRouteAppsAttachedComponent } from './table-cell-route-apps-attached.component'; describe('TableCellRouteAppsAttachedComponent', () => { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-card/cf-security-groups-card.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-card/cf-security-groups-card.component.spec.ts index 434dd70394..78c76495df 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-card/cf-security-groups-card.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-card/cf-security-groups-card.component.spec.ts @@ -9,7 +9,7 @@ import { generateCfBaseTestModulesNoShared, generateTestCfEndpointService, } from '../../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { ActiveRouteCfOrgSpace } from '../../../../../../features/cloud-foundry/cf-page.types'; +import { ActiveRouteCfOrgSpace } from '../../../../../../features/cf/cf-page.types'; import { CfSecurityGroupsCardComponent } from './cf-security-groups-card.component'; describe('CfSecurityGroupsCardComponent', () => { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-card/cf-security-groups-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-card/cf-security-groups-card.component.ts index a93e2e1137..3b3853c13f 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-card/cf-security-groups-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-card/cf-security-groups-card.component.ts @@ -4,9 +4,7 @@ import { AppChip } from '../../../../../../../../core/src/shared/components/chip import { CardCell } from '../../../../../../../../core/src/shared/components/list/list.types'; import { APIResource } from '../../../../../../../../store/src/types/api.types'; import { IRule, IRuleType, ISpace } from '../../../../../../cf-api.types'; -import { - CloudFoundryEndpointService, -} from '../../../../../../features/cloud-foundry/services/cloud-foundry-endpoint.service'; +import { CloudFoundryEndpointService } from '../../../../../../features/cf/services/cloud-foundry-endpoint.service'; @Component({ selector: 'app-cf-security-groups-card', diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-list-config.service.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-list-config.service.spec.ts index a4f651e5f1..0e54db9f11 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-list-config.service.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-list-config.service.spec.ts @@ -1,7 +1,7 @@ import { inject, TestBed } from '@angular/core/testing'; import { generateCfBaseTestModules } from '../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; import { CfSecurityGroupsListConfigService } from './cf-security-groups-list-config.service'; describe('CfSecurityGroupsListConfigService', () => { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-list-config.service.ts index 6dc824717b..8a95b10189 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-security-groups/cf-security-groups-list-config.service.ts @@ -4,7 +4,7 @@ import { Store } from '@ngrx/store'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { ITableColumn } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; import { APIResource } from '../../../../../../../store/src/types/api.types'; -import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; import { BaseCfListConfig } from '../base-cf/base-cf-list-config'; import { CfSecurityGroupsCardComponent } from './cf-security-groups-card/cf-security-groups-card.component'; import { CfSecurityGroupsDataSource } from './cf-security-groups-data-source'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-select-users/cf-select-users-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-select-users/cf-select-users-list-config.service.ts index 10ee7b1abb..9e074d09d6 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-select-users/cf-select-users-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-select-users/cf-select-users-list-config.service.ts @@ -22,8 +22,8 @@ import { PaginationMonitor } from '../../../../../../../store/src/monitors/pagin import { PaginationMonitorFactory } from '../../../../../../../store/src/monitors/pagination-monitor.factory'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { PaginatedAction } from '../../../../../../../store/src/types/pagination.types'; -import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; -import { waitForCFPermissions } from '../../../../../features/cloud-foundry/cf.helpers'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; +import { waitForCFPermissions } from '../../../../../features/cf/cf.helpers'; import { CfUser, CfUserMissingRoles } from '../../../../../store/types/cf-user.types'; import { CfUserService } from '../../../../data-services/cf-user.service'; import { CfSelectUsersDataSourceService } from './cf-select-users-data-source.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts index b1ecf9ef80..2ed4424106 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts @@ -21,7 +21,7 @@ import { import { ListView } from '../../../../../../../store/src/actions/list.actions'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { IServiceInstance } from '../../../../../cf-api-svc.types'; -import { isUserProvidedServiceInstance } from '../../../../../features/cloud-foundry/cf.helpers'; +import { isUserProvidedServiceInstance } from '../../../../../features/cf/cf.helpers'; import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { ServiceActionHelperService } from '../../../../data-services/service-action-helper.service'; import { CANCEL_ORG_ID_PARAM, CANCEL_SPACE_ID_PARAM, CSI_CANCEL_URL } from '../../../add-service-instance/csi-mode.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.spec.ts index 78df4dfe9b..32f9fd4c6b 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.spec.ts @@ -1,7 +1,7 @@ import { inject, TestBed } from '@angular/core/testing'; import { generateCfBaseTestModules } from '../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; import { CfServicesListConfigService } from './cf-services-list-config.service'; describe('CfServicesListConfigService', () => { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.ts index 90fdcad893..13e12ea854 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.ts @@ -14,8 +14,8 @@ import { ListView } from '../../../../../../../store/src/actions/list.actions'; import { connectedEndpointsOfTypesSelector } from '../../../../../../../store/src/selectors/endpoint.selectors'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { CF_ENDPOINT_TYPE } from '../../../../../cf-types'; -import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; -import { haveMultiConnectedCfs } from '../../../../../features/cloud-foundry/cf.helpers'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; +import { haveMultiConnectedCfs } from '../../../../../features/cf/cf.helpers'; import { CfOrgSpaceItem, createCfOrgSpaceFilterConfig } from '../../../../data-services/cf-org-space-service.service'; import { CfServiceCardComponent } from './cf-service-card/cf-service-card.component'; import { CfServicesDataSource } from './cf-services-data-source'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-user-service-instances-list-config.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-user-service-instances-list-config.ts index 4a9ee537ec..f66bdff76a 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-user-service-instances-list-config.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-user-service-instances-list-config.ts @@ -21,7 +21,7 @@ import { import { ListView } from '../../../../../../../store/src/actions/list.actions'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { IUserProvidedServiceInstance } from '../../../../../cf-api-svc.types'; -import { CloudFoundrySpaceService } from '../../../../../features/cloud-foundry/services/cloud-foundry-space.service'; +import { CloudFoundrySpaceService } from '../../../../../features/cf/services/cloud-foundry-space.service'; import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { ServiceActionHelperService } from '../../../../data-services/service-action-helper.service'; import { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-data-source.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-data-source.service.ts index b858a197ed..de8d5b25d0 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-data-source.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-data-source.service.ts @@ -13,7 +13,7 @@ import { IListConfig } from '../../../../../../../core/src/shared/components/lis import { APIResource } from '../../../../../../../store/src/types/api.types'; import { cfEntityCatalog } from '../../../../../cf-entity-catalog'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; -import { CloudFoundrySpaceService } from '../../../../../features/cloud-foundry/services/cloud-foundry-space.service'; +import { CloudFoundrySpaceService } from '../../../../../features/cf/services/cloud-foundry-space.service'; export class CfSpaceAppsDataSource extends ListDataSource { constructor(store: Store, cfSpaceService: CloudFoundrySpaceService, listConfig?: IListConfig) { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-list-config.service.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-list-config.service.spec.ts index 81ce704340..07ea411246 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-list-config.service.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-list-config.service.spec.ts @@ -3,7 +3,7 @@ import { inject, TestBed } from '@angular/core/testing'; import { generateCfBaseTestModules } from '../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CloudFoundrySpaceServiceMock } from '../../../../../../test-framework/cloud-foundry-space.service.mock'; -import { CloudFoundrySpaceService } from '../../../../../features/cloud-foundry/services/cloud-foundry-space.service'; +import { CloudFoundrySpaceService } from '../../../../../features/cf/services/cloud-foundry-space.service'; import { CfSpaceAppsListConfigService } from './cf-space-apps-list-config.service'; describe('CfSpaceAppsListConfigService', () => { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-list-config.service.ts index 60065a1ebb..0140384461 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-apps/cf-space-apps-list-config.service.ts @@ -18,7 +18,7 @@ import { ListView } from '../../../../../../../store/src/actions/list.actions'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { UserFavorite } from '../../../../../../../store/src/types/user-favorites.types'; import { IApp } from '../../../../../cf-api.types'; -import { CloudFoundrySpaceService } from '../../../../../features/cloud-foundry/services/cloud-foundry-space.service'; +import { CloudFoundrySpaceService } from '../../../../../features/cf/services/cloud-foundry-space.service'; import { TableCellAppNameComponent } from '../app/table-cell-app-name/table-cell-app-name.component'; import { TableCellAppStatusComponent } from '../app/table-cell-app-status/table-cell-app-status.component'; import { CfSpaceAppsDataSource } from './cf-space-apps-data-source.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-quotas/cf-space-quotas-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-quotas/cf-space-quotas-list-config.service.ts index fd78c9a3fd..69350aed0c 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-quotas/cf-space-quotas-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-quotas/cf-space-quotas-list-config.service.ts @@ -15,7 +15,7 @@ import { APIResource } from '../../../../../../../store/src/types/api.types'; import { DeleteSpaceQuotaDefinition } from '../../../../../actions/quota-definitions.actions'; import { IQuotaDefinition } from '../../../../../cf-api.types'; import { CFAppState } from '../../../../../cf-app-state'; -import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { BaseCfListConfig } from '../base-cf/base-cf-list-config'; import { QUOTA_FROM_LIST } from '../cf-quotas/cf-quotas-list-config.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-routes/cf-space-routes-list-config.service.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-routes/cf-space-routes-list-config.service.spec.ts index 7f8531ebd3..1819bf3f93 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-routes/cf-space-routes-list-config.service.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-routes/cf-space-routes-list-config.service.spec.ts @@ -3,7 +3,7 @@ import { inject, TestBed } from '@angular/core/testing'; import { generateCfBaseTestModules } from '../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CloudFoundrySpaceServiceMock } from '../../../../../../test-framework/cloud-foundry-space.service.mock'; -import { CloudFoundrySpaceService } from '../../../../../features/cloud-foundry/services/cloud-foundry-space.service'; +import { CloudFoundrySpaceService } from '../../../../../features/cf/services/cloud-foundry-space.service'; import { CfSpaceRoutesListConfigService } from './cf-space-routes-list-config.service'; describe('CfSpaceRoutesListConfigService', () => { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-routes/cf-space-routes-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-routes/cf-space-routes-list-config.service.ts index 0090a576fb..9a54d91cdc 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-routes/cf-space-routes-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-routes/cf-space-routes-list-config.service.ts @@ -10,7 +10,7 @@ import { import { ConfirmationDialogService } from '../../../../../../../core/src/shared/components/confirmation-dialog.service'; import { IListConfig } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { APIResource } from '../../../../../../../store/src/types/api.types'; -import { CloudFoundrySpaceService } from '../../../../../features/cloud-foundry/services/cloud-foundry-space.service'; +import { CloudFoundrySpaceService } from '../../../../../features/cf/services/cloud-foundry-space.service'; import { CfCurrentUserPermissions } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { CfRoutesListConfigBase } from '../cf-routes/cf-routes-list-config-base'; import { CfSpaceRoutesDataSource } from './cf-space-routes-data-source'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-users/cf-space-users-list-config.service.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-users/cf-space-users-list-config.service.spec.ts index 17b9e153bf..ab5cf5744e 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-users/cf-space-users-list-config.service.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-users/cf-space-users-list-config.service.spec.ts @@ -6,13 +6,11 @@ import { CloudFoundryOrganizationServiceMock, } from '../../../../../../test-framework/cloud-foundry-organization.service.mock'; import { CloudFoundrySpaceServiceMock } from '../../../../../../test-framework/cloud-foundry-space.service.mock'; -import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; -import { CloudFoundryEndpointService } from '../../../../../features/cloud-foundry/services/cloud-foundry-endpoint.service'; -import { - CloudFoundryOrganizationService, -} from '../../../../../features/cloud-foundry/services/cloud-foundry-organization.service'; -import { CloudFoundrySpaceService } from '../../../../../features/cloud-foundry/services/cloud-foundry-space.service'; -import { UserInviteService } from '../../../../../features/cloud-foundry/user-invites/user-invite.service'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; +import { CloudFoundryEndpointService } from '../../../../../features/cf/services/cloud-foundry-endpoint.service'; +import { CloudFoundryOrganizationService } from '../../../../../features/cf/services/cloud-foundry-organization.service'; +import { CloudFoundrySpaceService } from '../../../../../features/cf/services/cloud-foundry-space.service'; +import { UserInviteService } from '../../../../../features/cf/user-invites/user-invite.service'; import { CfUserService } from '../../../../data-services/cf-user.service'; import { CfSpaceUsersListConfigService } from './cf-space-users-list-config.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-users/cf-space-users-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-users/cf-space-users-list-config.service.ts index 0484f50f82..84ce162b93 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-users/cf-space-users-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-users/cf-space-users-list-config.service.ts @@ -6,11 +6,9 @@ import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state' import { CurrentUserPermissionsService, } from '../../../../../../../core/src/core/permissions/current-user-permissions.service'; -import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; -import { - CloudFoundryOrganizationService, -} from '../../../../../features/cloud-foundry/services/cloud-foundry-organization.service'; -import { CloudFoundrySpaceService } from '../../../../../features/cloud-foundry/services/cloud-foundry-space.service'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; +import { CloudFoundryOrganizationService } from '../../../../../features/cf/services/cloud-foundry-organization.service'; +import { CloudFoundrySpaceService } from '../../../../../features/cf/services/cloud-foundry-space.service'; import { CfUser } from '../../../../../store/types/cf-user.types'; import { CfUserService } from '../../../../data-services/cf-user.service'; import { CfUserListConfigService } from '../cf-users/cf-user-list-config.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-service-instances-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-service-instances-list-config.service.ts index f747341abe..61361c6697 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-service-instances-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-service-instances-list-config.service.ts @@ -9,7 +9,7 @@ import { import { IListConfig } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { IServiceInstance } from '../../../../../cf-api-svc.types'; -import { CloudFoundrySpaceService } from '../../../../../features/cloud-foundry/services/cloud-foundry-space.service'; +import { CloudFoundrySpaceService } from '../../../../../features/cf/services/cloud-foundry-space.service'; import { ServiceActionHelperService } from '../../../../data-services/service-action-helper.service'; import { CfServiceInstancesListConfigBase } from '../cf-services/cf-service-instances-list-config.base'; import { CfSpacesServiceInstancesDataSource } from './cf-spaces-service-instances-data-source'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces/cf-space-card/cf-space-card.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces/cf-space-card/cf-space-card.component.spec.ts index f780e7ce4d..6f137e7428 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces/cf-space-card/cf-space-card.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces/cf-space-card/cf-space-card.component.spec.ts @@ -13,9 +13,7 @@ import { generateTestCfEndpointServiceProvider, generateTestCfUserServiceProvider, } from '../../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { - CloudFoundryOrganizationService, -} from '../../../../../../features/cloud-foundry/services/cloud-foundry-organization.service'; +import { CloudFoundryOrganizationService } from '../../../../../../features/cf/services/cloud-foundry-organization.service'; import { CfOrgSpaceDataService } from '../../../../../data-services/cf-org-space-service.service'; import { CloudFoundryUserProvidedServicesService, diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces/cf-space-card/cf-space-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces/cf-space-card/cf-space-card.component.ts index 322d4bc454..784692eab6 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces/cf-space-card/cf-space-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces/cf-space-card/cf-space-card.component.ts @@ -27,15 +27,13 @@ import { IApp, ISpace } from '../../../../../../cf-api.types'; import { cfEntityFactory } from '../../../../../../cf-entity-factory'; import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; import { getStartedAppInstanceCount } from '../../../../../../cf.helpers'; -import { getSpaceRolesString } from '../../../../../../features/cloud-foundry/cf.helpers'; -import { - CloudFoundryEndpointService, -} from '../../../../../../features/cloud-foundry/services/cloud-foundry-endpoint.service'; +import { getSpaceRolesString } from '../../../../../../features/cf/cf.helpers'; +import { CloudFoundryEndpointService } from '../../../../../../features/cf/services/cloud-foundry-endpoint.service'; import { CloudFoundryOrganizationService, createSpaceQuotaDefinition, -} from '../../../../../../features/cloud-foundry/services/cloud-foundry-organization.service'; -import { SpaceQuotaHelper } from '../../../../../../features/cloud-foundry/services/cloud-foundry-space-quota'; +} from '../../../../../../features/cf/services/cloud-foundry-organization.service'; +import { SpaceQuotaHelper } from '../../../../../../features/cf/services/cloud-foundry-space-quota'; import { CfCurrentUserPermissions } from '../../../../../../user-permissions/cf-user-permissions-checkers'; import { CfUserService } from '../../../../../data-services/cf-user.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces/cf-spaces-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces/cf-spaces-list-config.service.ts index 4571a58385..5af34c8602 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces/cf-spaces-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces/cf-spaces-list-config.service.ts @@ -7,9 +7,7 @@ import { IListConfig, ListViewTypes } from '../../../../../../../core/src/shared import { ListView } from '../../../../../../../store/src/actions/list.actions'; import { APIResource } from '../../../../../../../store/src/types/api.types'; import { ISpace } from '../../../../../cf-api.types'; -import { - CloudFoundryOrganizationService, -} from '../../../../../features/cloud-foundry/services/cloud-foundry-organization.service'; +import { CloudFoundryOrganizationService } from '../../../../../features/cf/services/cloud-foundry-organization.service'; import { CfSpaceCardComponent } from './cf-space-card/cf-space-card.component'; import { CfSpacesDataSourceService } from './cf-spaces-data-source.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-stacks/cf-stacks-list-config.service.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-stacks/cf-stacks-list-config.service.spec.ts index f037cfcc5f..3d9c00c8d7 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-stacks/cf-stacks-list-config.service.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-stacks/cf-stacks-list-config.service.spec.ts @@ -1,7 +1,7 @@ import { inject, TestBed } from '@angular/core/testing'; import { generateCfBaseTestModules } from '../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; import { CfStacksListConfigService } from './cf-stacks-list-config.service'; describe('CfStacksListConfigService', () => { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-stacks/cf-stacks-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-stacks/cf-stacks-list-config.service.ts index 4e89132e0f..1601fd041c 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-stacks/cf-stacks-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-stacks/cf-stacks-list-config.service.ts @@ -4,7 +4,7 @@ import { Store } from '@ngrx/store'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { ITableColumn } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; import { APIResource } from '../../../../../../../store/src/types/api.types'; -import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; import { BaseCfListConfig } from '../base-cf/base-cf-list-config'; import { CfStacksCardComponent } from './cf-stacks-card/cf-stacks-card.component'; import { CfStacksDataSource } from './cf-stacks-data-source'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/cf-users-space-roles-data-source.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/cf-users-space-roles-data-source.service.ts index 83128b8b1c..b7e9a14566 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/cf-users-space-roles-data-source.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/cf-users-space-roles-data-source.service.ts @@ -19,7 +19,7 @@ import { IListConfig } from '../../../../../../../core/src/shared/components/lis import { APIResource } from '../../../../../../../store/src/types/api.types'; import { PaginationEntityState } from '../../../../../../../store/src/types/pagination.types'; import { ISpace } from '../../../../../cf-api.types'; -import { CfRolesService } from '../../../../../features/cloud-foundry/users/manage-users/cf-roles.service'; +import { CfRolesService } from '../../../../../features/cf/users/manage-users/cf-roles.service'; export class CfUsersSpaceRolesDataSourceService extends ListDataSource> { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/table-cell-org-space-role/table-cell-org-space-role.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/table-cell-org-space-role/table-cell-org-space-role.component.spec.ts index 7fe7843b9a..b6130d172c 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/table-cell-org-space-role/table-cell-org-space-role.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/table-cell-org-space-role/table-cell-org-space-role.component.spec.ts @@ -9,8 +9,8 @@ import { APIResource } from '../../../../../../../../store/src/types/api.types'; import { generateCfStoreModules } from '../../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CfUserServiceTestProvider } from '../../../../../../../test-framework/user-service-helper'; import { ISpace } from '../../../../../../cf-api.types'; -import { ActiveRouteCfOrgSpace } from '../../../../../../features/cloud-foundry/cf-page.types'; -import { CfRolesService } from '../../../../../../features/cloud-foundry/users/manage-users/cf-roles.service'; +import { ActiveRouteCfOrgSpace } from '../../../../../../features/cf/cf-page.types'; +import { CfRolesService } from '../../../../../../features/cf/users/manage-users/cf-roles.service'; import { CfRoleCheckboxComponent } from '../../../../cf-role-checkbox/cf-role-checkbox.component'; import { TableCellRoleOrgSpaceComponent } from './table-cell-org-space-role.component'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/table-cell-select-org/table-cell-select-org.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/table-cell-select-org/table-cell-select-org.component.spec.ts index cd24bca07c..6990244c14 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/table-cell-select-org/table-cell-select-org.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/table-cell-select-org/table-cell-select-org.component.spec.ts @@ -7,8 +7,8 @@ import { EntityMonitorFactory } from '../../../../../../../../store/src/monitors import { PaginationMonitorFactory } from '../../../../../../../../store/src/monitors/pagination-monitor.factory'; import { generateCfStoreModules } from '../../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CfUserServiceTestProvider } from '../../../../../../../test-framework/user-service-helper'; -import { ActiveRouteCfOrgSpace } from '../../../../../../features/cloud-foundry/cf-page.types'; -import { CfRolesService } from '../../../../../../features/cloud-foundry/users/manage-users/cf-roles.service'; +import { ActiveRouteCfOrgSpace } from '../../../../../../features/cf/cf-page.types'; +import { CfRolesService } from '../../../../../../features/cf/users/manage-users/cf-roles.service'; import { CfUserService } from '../../../../../data-services/cf-user.service'; import { TableCellSelectOrgComponent } from './table-cell-select-org.component'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/table-cell-select-org/table-cell-select-org.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/table-cell-select-org/table-cell-select-org.component.ts index 4d11410056..1af855c8b6 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/table-cell-select-org/table-cell-select-org.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users-org-space-roles/table-cell-select-org/table-cell-select-org.component.ts @@ -8,8 +8,8 @@ import { CFAppState } from '../../../../../../../../cloud-foundry/src/cf-app-sta import { TableCellCustom } from '../../../../../../../../core/src/shared/components/list/list.types'; import { APIResource } from '../../../../../../../../store/src/types/api.types'; import { IOrganization } from '../../../../../../cf-api.types'; -import { ActiveRouteCfOrgSpace } from '../../../../../../features/cloud-foundry/cf-page.types'; -import { CfRolesService } from '../../../../../../features/cloud-foundry/users/manage-users/cf-roles.service'; +import { ActiveRouteCfOrgSpace } from '../../../../../../features/cf/cf-page.types'; +import { CfRolesService } from '../../../../../../features/cf/users/manage-users/cf-roles.service'; import { selectCfUsersRolesOrgGuid } from '../../../../../../store/selectors/cf-users-roles.selector'; @Component({ diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-org-permission-cell/cf-org-permission-cell.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-org-permission-cell/cf-org-permission-cell.component.ts index 76825b3388..05653c251e 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-org-permission-cell/cf-org-permission-cell.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-org-permission-cell/cf-org-permission-cell.component.ts @@ -16,7 +16,7 @@ import { entityCatalog } from '../../../../../../../../store/src/entity-catalog/ import { APIResource } from '../../../../../../../../store/src/types/api.types'; import { IOrganization } from '../../../../../../cf-api.types'; import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; -import { getOrgRoles } from '../../../../../../features/cloud-foundry/cf.helpers'; +import { getOrgRoles } from '../../../../../../features/cf/cf.helpers'; import { CfUser, IUserPermissionInOrg, OrgUserRoleNames } from '../../../../../../store/types/cf-user.types'; import { CfCurrentUserPermissions } from '../../../../../../user-permissions/cf-user-permissions-checkers'; import { CfUserService } from '../../../../../data-services/cf-user.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-permission-cell.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-permission-cell.ts index 7c325a8325..458162a84b 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-permission-cell.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-permission-cell.ts @@ -1,4 +1,4 @@ -import { Input, Directive } from '@angular/core'; +import { Directive, Input } from '@angular/core'; import { Store } from '@ngrx/store'; import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; import { filter, first, map, switchMap } from 'rxjs/operators'; @@ -11,7 +11,7 @@ import { ConfirmationDialogService } from '../../../../../../../core/src/shared/ import { TableCellCustom } from '../../../../../../../core/src/shared/components/list/list.types'; import { selectSessionData } from '../../../../../../../store/src/reducers/auth.reducer'; import { APIResource } from '../../../../../../../store/src/types/api.types'; -import { IUserRole } from '../../../../../features/cloud-foundry/cf.helpers'; +import { IUserRole } from '../../../../../features/cf/cf.helpers'; import { CfUser } from '../../../../../store/types/cf-user.types'; import { CfUserService } from '../../../../data-services/cf-user.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-space-permission-cell/cf-space-permission-cell.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-space-permission-cell/cf-space-permission-cell.component.ts index 6aa867410f..ba82dc8bb6 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-space-permission-cell/cf-space-permission-cell.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-space-permission-cell/cf-space-permission-cell.component.ts @@ -16,7 +16,7 @@ import { entityCatalog } from '../../../../../../../../store/src/entity-catalog/ import { APIResource } from '../../../../../../../../store/src/types/api.types'; import { IOrganization, ISpace } from '../../../../../../cf-api.types'; import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; -import { getSpaceRoles } from '../../../../../../features/cloud-foundry/cf.helpers'; +import { getSpaceRoles } from '../../../../../../features/cf/cf.helpers'; import { CfUser, IUserPermissionInSpace, SpaceUserRoleNames } from '../../../../../../store/types/cf-user.types'; import { CfCurrentUserPermissions } from '../../../../../../user-permissions/cf-user-permissions-checkers'; import { CfUserService } from '../../../../../data-services/cf-user.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-config.service.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-config.service.spec.ts index 19f56eb01a..9e35b16f7b 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-config.service.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-config.service.spec.ts @@ -14,11 +14,9 @@ import { CloudFoundryOrganizationServiceMock, } from '../../../../../../test-framework/cloud-foundry-organization.service.mock'; import { CloudFoundrySpaceServiceMock } from '../../../../../../test-framework/cloud-foundry-space.service.mock'; -import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; -import { - CloudFoundryOrganizationService, -} from '../../../../../features/cloud-foundry/services/cloud-foundry-organization.service'; -import { CloudFoundrySpaceService } from '../../../../../features/cloud-foundry/services/cloud-foundry-space.service'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; +import { CloudFoundryOrganizationService } from '../../../../../features/cf/services/cloud-foundry-organization.service'; +import { CloudFoundrySpaceService } from '../../../../../features/cf/services/cloud-foundry-space.service'; import { CfUserService } from '../../../../data-services/cf-user.service'; import { CfUserListConfigService } from './cf-user-list-config.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-config.service.ts index 23f7f500b9..143a4c32e6 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-config.service.ts @@ -23,7 +23,7 @@ import { selectPaginationState } from '../../../../../../../store/src/selectors/ import { APIResource, EntityInfo } from '../../../../../../../store/src/types/api.types'; import { PaginatedAction } from '../../../../../../../store/src/types/pagination.types'; import { IOrganization, ISpace } from '../../../../../cf-api.types'; -import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types'; +import { ActiveRouteCfOrgSpace } from '../../../../../features/cf/cf-page.types'; import { canUpdateOrgSpaceRoles, createCfOrgSpaceSteppersUrl, @@ -32,7 +32,7 @@ import { hasRoleWithinSpace, hasSpaceRoleWithinOrg, waitForCFPermissions, -} from '../../../../../features/cloud-foundry/cf.helpers'; +} from '../../../../../features/cf/cf.helpers'; import { CfUser } from '../../../../../store/types/cf-user.types'; import { CfUserPermissionsChecker } from '../../../../../user-permissions/cf-user-permissions-checkers'; import { CfUserService } from './../../../../data-services/cf-user.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instances-wall-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instances-wall-list-config.service.ts index 0f597ef543..2c76635469 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instances-wall-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instances-wall-list-config.service.ts @@ -22,7 +22,7 @@ import { ListViewTypes, } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { ListView } from '../../../../../../../store/src/actions/list.actions'; -import { cfOrgSpaceFilter } from '../../../../../features/cloud-foundry/cf.helpers'; +import { cfOrgSpaceFilter } from '../../../../../features/cf/cf.helpers'; import { CfOrgSpaceDataService, createCfOrgSpaceFilterConfig } from '../../../../data-services/cf-org-space-service.service'; import { ServiceActionHelperService } from '../../../../data-services/service-action-helper.service'; import { CfServiceInstancesListConfigBase } from '../cf-services/cf-service-instances-list-config.base'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-user.service.ts b/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-user.service.ts index e797eab3c8..01e167c8e2 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-user.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-user.service.ts @@ -23,7 +23,7 @@ import { PaginatedAction } from '../../../../store/src/types/pagination.types'; import { IOrganization, ISpace } from '../../cf-api.types'; import { cfEntityCatalog } from '../../cf-entity-catalog'; import { cfEntityFactory } from '../../cf-entity-factory'; -import { ActiveRouteCfOrgSpace } from '../../features/cloud-foundry/cf-page.types'; +import { ActiveRouteCfOrgSpace } from '../../features/cf/cf-page.types'; import { fetchTotalResults, filterEntitiesByGuid, @@ -35,7 +35,7 @@ import { isSpaceDeveloper, isSpaceManager, waitForCFPermissions, -} from '../../features/cloud-foundry/cf.helpers'; +} from '../../features/cf/cf.helpers'; import { selectCfPaginationState } from '../../store/selectors/pagination.selectors'; import { CfUser, diff --git a/src/frontend/packages/cloud-foundry/src/shared/directives/app-name-unique.directive/app-name-unique.directive.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/directives/app-name-unique.directive/app-name-unique.directive.spec.ts index 51a8e0ee13..c8ec0fdd99 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/directives/app-name-unique.directive/app-name-unique.directive.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/directives/app-name-unique.directive/app-name-unique.directive.spec.ts @@ -14,7 +14,7 @@ import { SharedModule } from '../../../../../core/src/shared/shared.module'; import { CoreTestingModule } from '../../../../../core/test-framework/core-test.modules'; import { AppStoreModule } from '../../../../../store/src/store.module'; import { CFAppState } from '../../../cf-app-state'; -import { ActiveRouteCfOrgSpace } from '../../../features/cloud-foundry/cf-page.types'; +import { ActiveRouteCfOrgSpace } from '../../../features/cf/cf-page.types'; import { CfUserService } from '../../data-services/cf-user.service'; import { AppNameUniqueDirective } from './app-name-unique.directive'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/directives/cf-user-permission/cf-user-permission.directive.ts b/src/frontend/packages/cloud-foundry/src/shared/directives/cf-user-permission/cf-user-permission.directive.ts index 283767d5f4..8e6406738f 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/directives/cf-user-permission/cf-user-permission.directive.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/directives/cf-user-permission/cf-user-permission.directive.ts @@ -5,7 +5,7 @@ import { switchMap } from 'rxjs/operators'; import { CurrentUserPermissionsService } from '../../../../../core/src/core/permissions/current-user-permissions.service'; import { AppState } from '../../../../../store/src/app-state'; -import { waitForCFPermissions } from '../../../features/cloud-foundry/cf.helpers'; +import { waitForCFPermissions } from '../../../features/cf/cf.helpers'; import { CfCurrentUserPermissions } from '../../../user-permissions/cf-user-permissions-checkers'; @Directive({ diff --git a/src/frontend/packages/cloud-foundry/src/shared/services/cf-org-space-label.service.ts b/src/frontend/packages/cloud-foundry/src/shared/services/cf-org-space-label.service.ts index 36c9cb2e38..7fb63eafc2 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/services/cf-org-space-label.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/services/cf-org-space-label.service.ts @@ -10,7 +10,7 @@ import { EndpointModel } from '../../../../store/src/types/endpoint.types'; import { IOrganization, ISpace } from '../../cf-api.types'; import { CFAppState } from '../../cf-app-state'; import { organizationEntityType, spaceEntityType } from '../../cf-entity-types'; -import { haveMultiConnectedCfs } from '../../features/cloud-foundry/cf.helpers'; +import { haveMultiConnectedCfs } from '../../features/cf/cf.helpers'; import { selectCfEntity } from '../../store/selectors/api.selectors'; export class CfOrgSpaceLabelService { diff --git a/src/frontend/packages/cloud-foundry/src/shared/services/cloud-foundry-user-provided-services.service.ts b/src/frontend/packages/cloud-foundry/src/shared/services/cloud-foundry-user-provided-services.service.ts index a90fbb0a94..3ebc0e70b6 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/services/cloud-foundry-user-provided-services.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/services/cloud-foundry-user-provided-services.service.ts @@ -17,7 +17,7 @@ import { CFAppState } from '../../cf-app-state'; import { cfEntityCatalog } from '../../cf-entity-catalog'; import { organizationEntityType, spaceEntityType } from '../../cf-entity-types'; import { createEntityRelationPaginationKey } from '../../entity-relations/entity-relations.types'; -import { fetchTotalResults } from '../../features/cloud-foundry/cf.helpers'; +import { fetchTotalResults } from '../../features/cf/cf.helpers'; import { QParam, QParamJoiners } from '../q-param'; diff --git a/src/frontend/packages/cloud-foundry/src/store/cloud-foundry.store.module.ts b/src/frontend/packages/cloud-foundry/src/store/cloud-foundry.store.module.ts index 700bf7c0a7..0d49b902db 100644 --- a/src/frontend/packages/cloud-foundry/src/store/cloud-foundry.store.module.ts +++ b/src/frontend/packages/cloud-foundry/src/store/cloud-foundry.store.module.ts @@ -1,7 +1,7 @@ import { NgModule } from '@angular/core'; import { EffectsModule } from '@ngrx/effects'; -import { ActiveRouteCfOrgSpace } from '../features/cloud-foundry/cf-page.types'; +import { ActiveRouteCfOrgSpace } from '../features/cf/cf-page.types'; import { CloudFoundryReducersModule } from './cloud-foundry.reducers.module'; import { AppVariablesEffect } from './effects/app-variables.effects'; import { AppEffects } from './effects/app.effects'; diff --git a/src/frontend/packages/cloud-foundry/src/store/effects/users-roles.effects.ts b/src/frontend/packages/cloud-foundry/src/store/effects/users-roles.effects.ts index 7ed0d48c09..c75fb997b0 100644 --- a/src/frontend/packages/cloud-foundry/src/store/effects/users-roles.effects.ts +++ b/src/frontend/packages/cloud-foundry/src/store/effects/users-roles.effects.ts @@ -4,7 +4,7 @@ import { Actions, Effect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; import { ManageUsersSetUsernamesHelper, -} from 'frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component'; +} from 'frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component'; import { combineLatest as observableCombineLatest, combineLatest, Observable, of as observableOf, of } from 'rxjs'; import { catchError, filter, first, map, mergeMap, pairwise, switchMap, tap, withLatestFrom } from 'rxjs/operators'; diff --git a/src/frontend/packages/cloud-foundry/test-framework/cloud-foundry-endpoint-service.helper.ts b/src/frontend/packages/cloud-foundry/test-framework/cloud-foundry-endpoint-service.helper.ts index 3fc7ed40e9..7a99f5dd9b 100644 --- a/src/frontend/packages/cloud-foundry/test-framework/cloud-foundry-endpoint-service.helper.ts +++ b/src/frontend/packages/cloud-foundry/test-framework/cloud-foundry-endpoint-service.helper.ts @@ -14,12 +14,9 @@ import { PaginationMonitorFactory } from '../../store/src/monitors/pagination-mo import { appReducers } from '../../store/src/reducers.module'; import { CFAppState } from '../src/cf-app-state'; import { CloudFoundryTestingModule } from '../src/cloud-foundry-test.module'; -import { ActiveRouteCfOrgSpace } from '../src/features/cloud-foundry/cf-page.types'; -import { CloudFoundryEndpointService } from '../src/features/cloud-foundry/services/cloud-foundry-endpoint.service'; -import { - UserInviteConfigureService, - UserInviteService, -} from '../src/features/cloud-foundry/user-invites/user-invite.service'; +import { ActiveRouteCfOrgSpace } from '../src/features/cf/cf-page.types'; +import { CloudFoundryEndpointService } from '../src/features/cf/services/cloud-foundry-endpoint.service'; +import { UserInviteConfigureService, UserInviteService } from '../src/features/cf/user-invites/user-invite.service'; import { CfOrgSpaceDataService } from '../src/shared/data-services/cf-org-space-service.service'; import { CfUserService } from '../src/shared/data-services/cf-user.service'; import { CloudFoundryService } from '../src/shared/data-services/cloud-foundry.service'; diff --git a/src/frontend/packages/cloud-foundry/test-framework/cloud-foundry-space.service.mock.ts b/src/frontend/packages/cloud-foundry/test-framework/cloud-foundry-space.service.mock.ts index 385bf3b793..37e33ce7bd 100644 --- a/src/frontend/packages/cloud-foundry/test-framework/cloud-foundry-space.service.mock.ts +++ b/src/frontend/packages/cloud-foundry/test-framework/cloud-foundry-space.service.mock.ts @@ -1,6 +1,6 @@ import { Observable, of as observableOf } from 'rxjs'; -import { CloudFoundrySpaceService } from '../src/features/cloud-foundry/services/cloud-foundry-space.service'; +import { CloudFoundrySpaceService } from '../src/features/cf/services/cloud-foundry-space.service'; export class CloudFoundrySpaceServiceMock { diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-cf-step-1/create-endpoint-cf-step-1.component.html b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-cf-step-1/create-endpoint-cf-step-1.component.html index fe95b7a420..234764c826 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-cf-step-1/create-endpoint-cf-step-1.component.html +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-cf-step-1/create-endpoint-cf-step-1.component.html @@ -22,7 +22,11 @@

Advanced Information (Optional)

+ placeholder="Client Secret" [type]="!show ? 'password' : 'text'"> +
diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-cf-step-1/create-endpoint-cf-step-1.component.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-cf-step-1/create-endpoint-cf-step-1.component.ts index 96bca56f21..a1aaf71d57 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-cf-step-1/create-endpoint-cf-step-1.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-cf-step-1/create-endpoint-cf-step-1.component.ts @@ -51,6 +51,7 @@ export class CreateEndpointCfStep1Component implements IStepperStep, AfterConten endpointTypeSupportsSSO = false; endpoint: StratosCatalogEndpointEntity; + show = false; constructor( activatedRoute: ActivatedRoute, diff --git a/src/frontend/packages/core/src/features/endpoints/edit-endpoint/edit-endpoint-step/edit-endpoint-step.component.html b/src/frontend/packages/core/src/features/endpoints/edit-endpoint/edit-endpoint-step/edit-endpoint-step.component.html index fd059d3a3d..f58d96c145 100644 --- a/src/frontend/packages/core/src/features/endpoints/edit-endpoint/edit-endpoint-step/edit-endpoint-step.component.html +++ b/src/frontend/packages/core/src/features/endpoints/edit-endpoint/edit-endpoint-step/edit-endpoint-step.component.html @@ -15,14 +15,20 @@

{{definition.label}} Information

Advanced Information (Optional)

- Update Client ID and Client Secret - + Update Client ID and Client Secret + + Client ID is required - + +
@@ -36,4 +42,4 @@

Advanced Information (Optional)

- + \ No newline at end of file diff --git a/src/frontend/packages/core/src/features/endpoints/edit-endpoint/edit-endpoint-step/edit-endpoint-step.component.ts b/src/frontend/packages/core/src/features/endpoints/edit-endpoint/edit-endpoint-step/edit-endpoint-step.component.ts index cb6120b12e..e447f535f7 100644 --- a/src/frontend/packages/core/src/features/endpoints/edit-endpoint/edit-endpoint-step/edit-endpoint-step.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/edit-endpoint/edit-endpoint-step/edit-endpoint-step.component.ts @@ -42,6 +42,7 @@ export class EditEndpointStepComponent implements OnDestroy, IStepperStep { existingEndpointNames$: Observable; formChangeSub: Subscription; setClientInfo = false; + show = false; constructor( activatedRoute: ActivatedRoute, diff --git a/src/frontend/packages/core/src/features/setup/uaa-wizard/console-uaa-wizard.component.html b/src/frontend/packages/core/src/features/setup/uaa-wizard/console-uaa-wizard.component.html index 8eab0cb6ab..21b3273bdf 100644 --- a/src/frontend/packages/core/src/features/setup/uaa-wizard/console-uaa-wizard.component.html +++ b/src/frontend/packages/core/src/features/setup/uaa-wizard/console-uaa-wizard.component.html @@ -20,7 +20,12 @@

{{title}} Setup with Cloud Foundry UAA

- + +

Enter the username and password of an admin user (this is used to @@ -31,7 +36,12 @@

{{title}} Setup with Cloud Foundry UAA

- + + diff --git a/src/frontend/packages/core/src/features/setup/uaa-wizard/console-uaa-wizard.component.ts b/src/frontend/packages/core/src/features/setup/uaa-wizard/console-uaa-wizard.component.ts index d1e360bfa5..677d623ea1 100644 --- a/src/frontend/packages/core/src/features/setup/uaa-wizard/console-uaa-wizard.component.ts +++ b/src/frontend/packages/core/src/features/setup/uaa-wizard/console-uaa-wizard.component.ts @@ -1,16 +1,16 @@ -import { Component, OnInit, ViewEncapsulation, Inject } from '@angular/core'; +import { Component, Inject, OnInit, ViewEncapsulation } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import { BehaviorSubject, Observable } from 'rxjs'; import { delay, filter, map, skipWhile, take } from 'rxjs/operators'; import { VerifySession } from '../../../../../store/src/actions/auth.actions'; +import { SetupConsoleGetScopes, SetupSaveConfig } from '../../../../../store/src/actions/setup.actions'; import { InternalAppState } from '../../../../../store/src/app-state'; import { AuthState } from '../../../../../store/src/reducers/auth.reducer'; import { UAASetupState } from '../../../../../store/src/types/uaa-setup.types'; -import { StepOnNextFunction } from '../../../shared/components/stepper/step/step.component'; -import { SetupConsoleGetScopes, SetupSaveConfig } from '../../../../../store/src/actions/setup.actions'; import { APP_TITLE } from '../../../core/core.types'; +import { StepOnNextFunction } from '../../../shared/components/stepper/step/step.component'; import { getSSOClientRedirectURI } from '../../endpoints/endpoint-helpers'; @Component({ @@ -35,6 +35,9 @@ export class ConsoleUaaWizardComponent implements OnInit { selectedScope = ''; applyingSetup$ = new BehaviorSubject(false); + public show = false; + public showPassword = false; + uaaFormNext: StepOnNextFunction = () => { this.store.dispatch(new SetupConsoleGetScopes({ uaa_endpoint: this.uaaForm.get('apiUrl').value, diff --git a/src/frontend/packages/devkit/build.js b/src/frontend/packages/devkit/build.js new file mode 100644 index 0000000000..7bbcf1dba5 --- /dev/null +++ b/src/frontend/packages/devkit/build.js @@ -0,0 +1,27 @@ +// Copy extra files needed as part of the devkit build +// Implemented as a single script here so that it works on Windows, Linux and Mac + +const path = require('path'); +const fs = require('fs-extra'); + +console.log('Copying devkit files'); + +// __dirname is the folder where build.js is located +const STRATOS_DIR= path.resolve(__dirname, '..', '..', '..', '..'); +const DEVKIT_DIST_DIR= path.join(STRATOS_DIR, 'dist-devkit'); + +let err = fs.copySync(path.join(__dirname, 'package.json'), path.join(DEVKIT_DIST_DIR, 'package.json')); +if (err) { + console.log(err); +} +err =fs.copySync(path.join(__dirname, 'src'), path.join(DEVKIT_DIST_DIR), { + overwrite: true, + dereference: true, + preserveTimestamps: true, + filter: function(file) { + return !file.endsWith('.ts'); + } +}); +if (err) { + console.log(err); +} diff --git a/src/frontend/packages/devkit/build.sh b/src/frontend/packages/devkit/build.sh deleted file mode 100755 index 8aaa9f8b3b..0000000000 --- a/src/frontend/packages/devkit/build.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -# Build script for devkit -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -ROOT="$( cd "$( dirname "${DIR}" )" && cd ../../.. && pwd )" -DIST="$ROOT/dist-devkit" - -echo $DIST -echo $DIR -tsc -if [ $? -eq 0 ]; then - echo "Copying supporting files" - cp "$DIR/package.json" "$DIST" - rsync -r --exclude=*.ts "${DIR}/src/" "${DIST}/" -fi - diff --git a/src/frontend/packages/devkit/package.json b/src/frontend/packages/devkit/package.json index e16bd4f8fe..e27689fbd3 100644 --- a/src/frontend/packages/devkit/package.json +++ b/src/frontend/packages/devkit/package.json @@ -2,7 +2,7 @@ "name": "@stratosui/devkit", "version": "0.0.1", "scripts": { - "build": "./build.sh", + "build": "tsc && node build.js", "tsc": "tsc" }, "dependencies": { diff --git a/src/frontend/packages/devkit/src/build/extensions.ts b/src/frontend/packages/devkit/src/build/extensions.ts index cbd7d43090..26f7e8af26 100644 --- a/src/frontend/packages/devkit/src/build/extensions.ts +++ b/src/frontend/packages/devkit/src/build/extensions.ts @@ -1,11 +1,13 @@ import * as fs from 'fs'; import * as path from 'path'; -import { NormalModuleReplacementPlugin } from 'webpack'; +import { NormalModuleReplacementPlugin } from 'webpack'; import { StratosConfig } from '../lib/stratos.config'; const importModuleRegex = /src\/frontend\/packages\/core\/src\/custom-import.module.ts/; +const isWindows = process.platform === 'win32'; + /** * Generates the file _custom-import.module.ts containing the code to import * the extensions modules discovered from the packages being included. @@ -51,8 +53,18 @@ export class ExtensionsHandler { this.writeModule(overrideFile, 'CustomImportModule', moduleImports); this.writeModule(overrideFile, 'CustomRoutingImportModule', routingMmoduleImports); + let regex; + // On windows, use an absolute path with backslashes escaped + if (isWindows) { + let p = path.resolve(path.join(dir, 'custom-import.module.ts')); + p = p.replace(/\\/g, '\\\\'); + regex = new RegExp(p); + } else { + regex = importModuleRegex + } + webpackConfig.plugins.push(new NormalModuleReplacementPlugin( - importModuleRegex, + regex, overrideFile )); } diff --git a/src/frontend/packages/devkit/src/build/sass.ts b/src/frontend/packages/devkit/src/build/sass.ts index 745a378e36..5a1dcf8aa8 100644 --- a/src/frontend/packages/devkit/src/build/sass.ts +++ b/src/frontend/packages/devkit/src/build/sass.ts @@ -1,3 +1,4 @@ +import * as path from 'path'; import { StratosConfig } from '../lib/stratos.config'; /** @@ -52,12 +53,12 @@ export class SassHandler { } else { pkgName = pkgParts.shift(); } - const pkgPath = pkgParts.join('/'); + const pkgPath = pkgParts.join(path.sep); // See if we can resolve the package name const knownPath = config.resolveKnownPackage(pkgName); if (knownPath) { return { - file: knownPath + '/_' + pkgPath + '.scss' + file: path.join(knownPath, pkgPath + '.scss') }; } } diff --git a/src/frontend/packages/devkit/src/lib/packages.ts b/src/frontend/packages/devkit/src/lib/packages.ts index 6e0f3a8c02..eb16284148 100644 --- a/src/frontend/packages/devkit/src/lib/packages.ts +++ b/src/frontend/packages/devkit/src/lib/packages.ts @@ -267,7 +267,7 @@ export class Packages { package: pkg.name, scss: refParts[0], mixin: refParts[1], - importPath: path.join(packagePath, refParts[0]) + importPath: '~' + pkg.name + '/' + refParts[0] }; this.log('Found themed package: ' + pkg.name + ' (' + pkg.stratos.theming + ')'); return themingConfig; diff --git a/src/jetstream/config.dev b/src/jetstream/config.dev new file mode 100644 index 0000000000..a98e5c3ec3 --- /dev/null +++ b/src/jetstream/config.dev @@ -0,0 +1,55 @@ +# =============================================== +# Stratos Default Backend Developer Configuration +# =============================================== + +# Database connectivity environment variables +DATABASE_PROVIDER=sqlite +HTTP_CONNECTION_TIMEOUT_IN_SECS=10 +HTTP_CLIENT_TIMEOUT_IN_SECS=30 +HTTP_CLIENT_TIMEOUT_MUTATING_IN_SECS=120 +HTTP_CLIENT_TIMEOUT_LONGRUNNING_IN_SECS=600 +SKIP_SSL_VALIDATION=true +CONSOLE_PROXY_TLS_ADDRESS=:5443 +CONSOLE_CLIENT=console +CF_CLIENT=cf +UAA_ENDPOINT= +CONSOLE_ADMIN_SCOPE=stratos.admin +CF_ADMIN_ROLE=cloud_controller.admin +ALLOWED_ORIGINS=http://nginx +SESSION_STORE_SECRET=stratos_development +CONSOLE_PROXY_CERT_PATH=../../dev-ssl/server.crt +CONSOLE_PROXY_CERT_KEY_PATH=../../dev-ssl/server.key +ENCRYPTION_KEY=B374A26A71490437AA024E4FADD5B497FDFF1A8EA6FF12F6FB65AF2720B59CCF +SQLITE_KEEP_DB=true +UI_PATH=../../dist + +LOG_TO_JSON=false +LOG_API_REQUESTS=true + +SSO_LOGIN=false +# Whitelist for the SSO redirect url. Paths can contain wildcard `*` +SSO_WHITELIST= + +# Enable feature in tech preview +ENABLE_TECH_PREVIEW=true + +# Override the default max list size. When hit we won't fetch all results for the given list +#UI_LIST_MAX_SIZE=600 +# If the max list size is hit allow the user to load all results anyway. Defaults to false +#UI_LIST_ALLOW_LOAD_MAXED=false + +# User Invites +SMTP_FROM_ADDRESS=Stratos +SMTP_HOST=127.0.0.1 +SMTP_PASSWORD= +SMTP_PORT=1025 +SMTP_USER= +TEMPLATE_DIR=./templates +INVITE_USER_CLIENT_ID= +INVITE_USER_CLIENT_SECRET= + +# Use local admin user rather than UAA users +AUTH_ENDPOINT_TYPE=local +LOCAL_USER=admin +LOCAL_USER_PASSWORD=admin +LOCAL_USER_SCOPE=stratos.admin diff --git a/src/jetstream/default.config.properties b/src/jetstream/config.example similarity index 98% rename from src/jetstream/default.config.properties rename to src/jetstream/config.example index e6466f8004..69d9110c8b 100644 --- a/src/jetstream/default.config.properties +++ b/src/jetstream/config.example @@ -8,7 +8,7 @@ SKIP_SSL_VALIDATION=true CONSOLE_PROXY_TLS_ADDRESS=:5443 CONSOLE_CLIENT=console CF_CLIENT=cf -UAA_ENDPOINT=http://localhost:8080 +UAA_ENDPOINT= CONSOLE_ADMIN_SCOPE=stratos.admin CF_ADMIN_ROLE=cloud_controller.admin ALLOWED_ORIGINS=http://nginx diff --git a/src/jetstream/main.go b/src/jetstream/main.go index f6f90c60c6..955f6f0000 100644 --- a/src/jetstream/main.go +++ b/src/jetstream/main.go @@ -10,7 +10,7 @@ import ( "fmt" "io" "io/ioutil" - "math/rand" + "math/rand" "net" "net/http" "os" @@ -90,9 +90,6 @@ func getEnvironmentLookup() *env.VarSet { // Fallback to a "config.properties" files in our directory envLookup.AppendSource(config.NewConfigFileLookup("./config.properties")) - // Fall back to "default.config.properties" in our directory - envLookup.AppendSource(config.NewConfigFileLookup("./default.config.properties")) - // Fallback to individual files in the "/etc/secrets" directory envLookup.AppendSource(config.NewSecretsDirLookup("/etc/secrets")) diff --git a/src/test-e2e/application/application-autoscaler-e2e.spec.ts b/src/test-e2e/application/application-autoscaler-e2e.spec.ts index c2656ac9a4..8eb872fd73 100644 --- a/src/test-e2e/application/application-autoscaler-e2e.spec.ts +++ b/src/test-e2e/application/application-autoscaler-e2e.spec.ts @@ -9,6 +9,7 @@ import { extendE2ETestTime } from '../helpers/extend-test-helpers'; import { LocaleHelper } from '../locale.helper'; import { CFPage } from '../po/cf-page.po'; import { ConfirmDialogComponent } from '../po/confirm-dialog'; +import { TableData } from '../po/list.po'; import { CREATE_APP_DEPLOY_TEST_TYPE, createApplicationDeployTests } from './application-deploy-helper'; import { ApplicationE2eHelper } from './application-e2e-helpers'; import { ApplicationPageAutoscalerTab } from './po/application-page-autoscaler.po'; @@ -39,6 +40,19 @@ describe('Autoscaler -', () => { const { testAppName, appDetails } = createApplicationDeployTests(CREATE_APP_DEPLOY_TEST_TYPE.GIT_URL); + // Scaling rules for the policy. + // Note - these should not result in scaling events during the test (we only expect one scaling event due to a schedule) + const memoryUtilThreshold = '90'; + const memoryUtilOperator = '>='; + const memoryUtilBreach = '160'; + + const throughputOperator = '>='; + const throughputThreshold = '100' + const throughputAdjustment = '10'; + const throughputAdjustmentType = '% instances'; + + const memoryUsedThreshold = '500'; + describe('Tab Tests -', () => { beforeAll(() => { // Should be deployed, no web-socket open, so we can wait for angular again @@ -123,9 +137,9 @@ describe('Autoscaler -', () => { expect(createPolicy.stepper.canNext()).toBeFalsy(); // Fill in form -- valid inputs createPolicy.stepper.getStepperForm().fill({ metric_type: 'memoryutil' }); - createPolicy.stepper.getStepperForm().fill({ operator: '>=' }); - createPolicy.stepper.getStepperForm().fill({ threshold: '60' }); - createPolicy.stepper.getStepperForm().fill({ breach_duration_secs: '60' }); + createPolicy.stepper.getStepperForm().fill({ operator: memoryUtilOperator }); + createPolicy.stepper.getStepperForm().fill({ threshold: memoryUtilThreshold }); + createPolicy.stepper.getStepperForm().fill({ breach_duration_secs: memoryUtilBreach }); expect(createPolicy.stepper.getMatErrorsCount()).toBe(0); expect(createPolicy.stepper.getDoneButtonDisabledStatus()).toBe(null); // Fill in form -- invalid inputs @@ -140,20 +154,22 @@ describe('Autoscaler -', () => { expect(createPolicy.stepper.getDoneButtonDisabledStatus()).toBe(null); createPolicy.stepper.clickDoneButton(); + // Click [Add] button createPolicy.stepper.clickAddButton(); expect(createPolicy.stepper.getRuleTilesCount()).toBe(2); expect(createPolicy.stepper.canNext()).toBeFalsy(); // Fill in form -- valid inputs createPolicy.stepper.getStepperForm().fill({ metric_type: 'throughput' }); + createPolicy.stepper.getStepperForm().fill({ operator: throughputOperator, threshold: throughputThreshold }); expect(createPolicy.stepper.getMatErrorsCount()).toBe(0); expect(createPolicy.stepper.getDoneButtonDisabledStatus()).toBe(null); // Fill in form -- invalid inputs - createPolicy.stepper.getStepperForm().fill({ adjustment: '10' }); + createPolicy.stepper.getStepperForm().fill({ adjustment: throughputAdjustment }); expect(createPolicy.stepper.getMatErrorsCount()).toBe(1); expect(createPolicy.stepper.getDoneButtonDisabledStatus()).toBe('true'); // Fill in form -- fix invalid inputs - createPolicy.stepper.getStepperForm().fill({ adjustment_type: '% instances' }); + createPolicy.stepper.getStepperForm().fill({ adjustment_type: throughputAdjustmentType }); expect(createPolicy.stepper.getMatErrorsCount()).toBe(0); expect(createPolicy.stepper.getDoneButtonDisabledStatus()).toBe(null); createPolicy.stepper.clickDoneButton(); @@ -281,11 +297,11 @@ describe('Autoscaler -', () => { expect(appAutoscaler.tableTriggers.getTableRowsCount()).toBe(2); expect(appAutoscaler.tableTriggers.getTableRowCellContent(0, 0)).toBe('memoryutil'); - expect(appAutoscaler.tableTriggers.getTableRowCellContent(0, 1)).toBe('>=60 % for 60 secs.'); + expect(appAutoscaler.tableTriggers.getTableRowCellContent(0, 1)).toBe(`${memoryUtilOperator}${memoryUtilThreshold} % for ${memoryUtilBreach} secs.`); expect(appAutoscaler.tableTriggers.getTableRowCellContent(0, 2)).toBe('+2 instances'); expect(appAutoscaler.tableTriggers.getTableRowCellContent(1, 0)).toBe('throughput'); - expect(appAutoscaler.tableTriggers.getTableRowCellContent(1, 1)).toBe('<=10rps for 120 secs.'); - expect(appAutoscaler.tableTriggers.getTableRowCellContent(1, 2)).toBe('-10% instances'); + expect(appAutoscaler.tableTriggers.getTableRowCellContent(1, 1)).toBe(`${throughputOperator}${throughputThreshold}rps for 120 secs.`); + expect(appAutoscaler.tableTriggers.getTableRowCellContent(1, 2)).toBe(`+${throughputAdjustment}${throughputAdjustmentType}`); expect(appAutoscaler.tableSchedules.getScheduleTableTitleText()).toBe('Scheduled Limit Rules in UTC'); expect(appAutoscaler.tableSchedules.getRecurringTableRowsCount()).toBe(1); @@ -349,7 +365,14 @@ describe('Autoscaler -', () => { it('Should pass ScalingRules Step', () => { createPolicy.stepper.clickAddButton(); + createPolicy.stepper.getStepperForm().getControlsMap().then(map => { + expect(map['metric_type'].value).toBe('memoryused') + }); + createPolicy.stepper.getStepperForm().fill({ threshold: memoryUsedThreshold }); + expect(createPolicy.stepper.getMatErrorsCount()).toBe(0); + expect(createPolicy.stepper.getDoneButtonDisabledStatus()).toBe(null); createPolicy.stepper.clickDoneButton(); + expect(createPolicy.stepper.canNext()).toBeTruthy(); createPolicy.stepper.next(); }); @@ -396,7 +419,7 @@ describe('Autoscaler -', () => { expect(appAutoscaler.cardMetric.getMetricChartTitleText(2)).toContain('memoryused'); expect(appAutoscaler.tableTriggers.getTableRowsCount()).toBe(3); expect(appAutoscaler.tableTriggers.getTableRowCellContent(2, 0)).toBe('memoryused'); - expect(appAutoscaler.tableTriggers.getTableRowCellContent(2, 1)).toBe('<=10MB for 120 secs.'); + expect(appAutoscaler.tableTriggers.getTableRowCellContent(2, 1)).toBe(`<=${memoryUsedThreshold}MB for 120 secs.`); expect(appAutoscaler.tableTriggers.getTableRowCellContent(2, 2)).toBe('-1 instances'); expect(appAutoscaler.tableSchedules.getRecurringTableRowsCount()).toBe(0); @@ -447,7 +470,7 @@ describe('Autoscaler -', () => { describe('Autoscaler Event Page - ', () => { const loggingPrefix = 'AutoScaler Event Table:'; let eventPageBase: PageAutoscalerEventBase; - describe('From autoscaler event card', () => { + describe('From autoscaler event card - ', () => { beforeAll(() => { const appAutoscaler = new ApplicationPageAutoscalerTab(appDetails.cfGuid, appDetails.appGuid); appAutoscaler.goToAutoscalerTab(); @@ -482,27 +505,38 @@ describe('Autoscaler -', () => { // Timeout after 32 attempts (each 5 seconds, which is just under 3 minutes) let retries = 32; const sub = timer(5000, 5000).pipe( - switchMap(() => promise.all([ + switchMap(() => promise.all([ findRow(), - eventPageBase.list.header.isRefreshing() + eventPageBase.list.header.isRefreshing(), + eventPageBase.list.table.getTableData() ])) - ).subscribe(([foundRow, isRefreshing]) => { + ).subscribe(([foundRow, isRefreshing, tableData]: [boolean, number, TableData[]]) => { // These console.logs help by // .. Showing the actual time we're checking, which can be compared with schedule start/end times // .. Showing when successful runs complete, over time this should show on average events take to show + const time = moment().toString() + console.log(`${time}: Table Data: `, tableData); + if (isRefreshing) { - console.log(`${moment().toString()}: Waiting for event row: Skip actions... list is refreshing`); + console.log(`${time}: Waiting for event row: Skip actions... list is refreshing`); return; } retries--; if (foundRow) { - console.log(`${moment().toString()}: Waiting for event row: Found row!`); + console.log(`${time}: Waiting for event row: Found row!`); sub.unsubscribe(); } else { - console.log(`${moment().toString()}: Waiting for event row: manually refreshing list`); - eventPageBase.list.header.refresh(); if (retries === 0) { + // Fail the test if the retry count made it down to 0 + e2e.debugLog('Timed out waiting for event row'); + fail('Timed out waiting for event row'); sub.unsubscribe(); + } else { + console.log(`${time}: Waiting for event row: manually refreshing list`); + eventPageBase.list.header.refresh(); + if (retries === 0) { + sub.unsubscribe(); + } } } }); @@ -534,7 +568,7 @@ describe('Autoscaler -', () => { browser.wait(ApplicationPageAutoscalerTab.detect() .then(appAutoscaler => { appAutoscaler.tableEvents.clickRefreshButton(); - expect(appAutoscaler.tableEvents.getTableRowsCount()).toBe(1); + expect(appAutoscaler.tableEvents.getTableRowsCount()).toBe(1, 'Expected rows to be one, could be extremely late event reporting'); expect(appAutoscaler.tableEvents.getTableRowCellContent(0, 0)).toBe('Instances scaled up from 1 to 2'); expect(appAutoscaler.tableEvents.getTableRowCellContent(0, 1)) .toBe('schedule starts with instance min 2, instance max 10 and instance min initial 2 limited by min instances 2'); diff --git a/src/test-e2e/po/form.po.ts b/src/test-e2e/po/form.po.ts index 4ba0f71622..1e676db7ac 100644 --- a/src/test-e2e/po/form.po.ts +++ b/src/test-e2e/po/form.po.ts @@ -1,10 +1,8 @@ -import { e2e } from './../e2e'; import { browser, by, element, promise } from 'protractor'; import { ElementArrayFinder, ElementFinder, protractor } from 'protractor/built'; import { Key } from 'selenium-webdriver'; import { Component } from './component.po'; -import { P } from '@angular/cdk/keycodes'; const until = protractor.ExpectedConditions; diff --git a/src/test-e2e/po/list.po.ts b/src/test-e2e/po/list.po.ts index 4b1d2a9e43..b6f163ad0a 100644 --- a/src/test-e2e/po/list.po.ts +++ b/src/test-e2e/po/list.po.ts @@ -14,6 +14,10 @@ export interface CardMetadata { click: () => void; } +export interface TableData { + [columnHeader: string]: string +} + // Page Object for the List Table View export class ListTableComponent extends Component { @@ -59,7 +63,7 @@ export class ListTableComponent extends Component { }); } - getTableData(): promise.Promise<{ [columnHeader: string]: string }[]> { + getTableData(): promise.Promise { return this.getTableDataRaw().then(tableData => { const table = []; tableData.rows.forEach((row: string[]) => { From 4717ba6bdac569794a9640f6306a097db1146f95 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 24 Jul 2020 15:23:47 +0100 Subject: [PATCH 584/648] Security observability (#398) * Work in progress Analysis tools * Thursday updates * Friday fixes and improvements * Friday code * Separate analyzers into a separate container * Wire in analyzers container in the Helm chart * Hide analysis UI features when not enabled * Fix sidepanel bug with fallback metadata * Bug fix for change in way tab links are hidden * Remove debug logging * Add refresh button to report selector drop down. Change no reports icon * Add support for adding breadcrumbs in the sub nav bar * Fix unit tests * Fix format issues * Final front-end unit test fixes * Fix issues when deploying via Helm with mariadb * Analyzers container fix. Allow helm chart to be packaged. * Build script fixes * Remove file * WIP: Add support for Clair image scanning * Use klar * Remove binary * Add clair helm chart for dev * Fixes * USe end var for clair server address * Latest updates * Improvements * Minor fixes * Tweak * Fix 1.16 detecton issue with the analyzers * Chart fixes * Changes following first run of script * Changes following npm install * Update custom-src to new model - expose custom module's module's - Add routing module - Tweak stratos.config.ts log output - remove custom-src dir * update naming... custom extensions --> suse extensions * A few tidyups to help review * Fix build issue due to merge * Fixes following merge from upstream * Remove clair from this PR * Ignore example packages when there's a stratos config file * Changes following review * Changes following merge * Update dir names, remove examples folder * Add back in custom-src deploy content, also add product version to config * Revert change needed downstream... (only needed when suse extension is included) * Remove unused wip report viewers * Fix after merge * Move new terminal & config code to plugin, fix more build files * Fix imports and add doc * Fix compilation issues * Change following merge * Tweaks to logging * Fix bug where report can not be deleted * Fix kube config connect after merge, also fix subtype & error on connect * Fix e2e * Improve drop-down menu * Remove strange merge artifacts * Remove build file * Fix graph overview * Numerous improvements to graph parsing and presentation * Remove logging. Add no reports message to workload analysis * Add support for CRDs. KubeCF renders correctly. * Allow which engines are enabled to be configured * Fix issue where reports are not filtered by endpoint * Minor changes following review * Fixes for a few more issues * Add Analyzers image build to Concourse CI * Multiple small fixes - fix text search in analysis list - fix title of links in analysers info page - handle slow connections by only polling analysis list when not already * Fix kubeGuid for helm world * Add AnalysisReportRunnerComponent - Still need to add this to other places * Delete reports when endpoint is unregistered * Buf fixes. Use breadcrumbs in sub-nav * Add run analysis button to workload analyis and graph tabs * Fix select of overlay in workload graphs page * Change default sort order of analysis list to age * Ensure table cell links update on row change * Align table action's icon better * Use a side panel for analyzer info * Add actions/effects for all used analyis actions - Add new ResetPaginationOfType action, like ResetPagination but applies to all types - Allows user to refresh reports list after kicking off new report on namespace & workload tabs - Handle missing report param in reports returned from get all reports * Remove some console.logs, converted some to console.info * Update Kube Dashboard, allow download link to be configurable - Default download link updated to v2.0.3 - Can configured link by setting env var `STRATOS_KUBERNETES_DASHBOARD_IMAGE` - Can configure env var in helm via `console.kubeDashboardImage` - Kube Dashboard now expanded by default (to show namespace drop down) * Fix after merge * Changes following review * Fix expand of kube dashboard header by default * Changes following review * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * WIP Wire in alerts to workload graph - need to understand if namespace should be checked when matching node/resource to alert - need to apply correct colour * Fix workload security analysis overlay slide in * Hide analysis headers info in tech preview & tie in tech preview check to analysisService.hideAnalysis$ - Q should the backend plugins be available in tech preview, see TODO * Hide the Workload Graph view if in tech preview * Fix disable of analysis plugin when tech preview is switched off * Adderss PR feedback * Minor tidy ups, fix analysis in graph - apply typing to many places - handle kube resources that we fail to fetch/parse - wire in analysis overlay to graphs and resource slide in * Remove debug code Co-authored-by: Richard Cox --- .gitignore | 1 + deploy/ci/suse-console-dev-releases.yml | 26 +- .../console/templates/analyzers.yaml | 62 ++++ deploy/kubernetes/console/values.yaml | 2 + deploy/kubernetes/custom/__stratos.tpl | 2 + deploy/kubernetes/custom/custom-build.sh | 5 + docs/extensions.md | 2 +- ...terminal-dev.md => suse-extensions-dev.md} | 22 +- package-lock.json | 347 ++++++++++++++---- .../application-tabs-base.component.ts | 2 +- .../sass/components/text-status.theme.scss | 9 + .../packages/core/sass/mat-desktop.scss | 7 + .../dashboard-base.component.html | 14 +- .../dashboard-base.component.scss | 36 +- .../dashboard-base.component.ts | 9 +- .../page-side-nav/page-side-nav.component.ts | 3 +- .../card-number-metric.component.html | 14 + .../card-number-metric.component.scss | 22 ++ .../card-number-metric.component.theme.scss | 20 +- .../card-number-metric.component.ts | 47 ++- .../app-table-cell-default.component.ts | 6 +- .../table-cell-actions.component.html | 5 +- .../page-sub-nav/page-sub-nav.component.ts | 9 +- .../ssh-viewer/ssh-viewer.component.ts | 22 +- .../src/shared/services/session.service.ts | 20 + .../src/shared/services/snackbar.service.ts | 15 +- .../packages/core/src/shared/shared.module.ts | 2 + src/frontend/packages/core/tab-nav.service.ts | 17 +- .../store/src/actions/pagination.actions.ts | 8 + .../entity-pagination-request-pipeline.ts | 2 +- .../pagination-reducer-create-pagination.ts | 2 +- .../pagination-reducer-reset-pagination.ts | 70 +++- .../pagination-reducer/pagination.reducer.ts | 5 + .../suse-extensions/sass/_all-theme.scss | 6 + .../analysis-report-runner.component.html | 18 + .../analysis-report-runner.component.scss | 0 .../analysis-report-runner.component.spec.ts | 25 ++ .../analysis-report-runner.component.ts | 49 +++ .../analysis-report-selector.component.html | 22 ++ .../analysis-report-selector.component.scss | 3 + ...analysis-report-selector.component.spec.ts | 38 ++ .../analysis-report-selector.component.ts | 87 +++++ .../analysis-report-viewer.component.html | 1 + .../analysis-report-viewer.component.scss | 0 .../analysis-report-viewer.component.spec.ts | 29 ++ .../analysis-report-viewer.component.ts | 76 ++++ .../kube-score-report-viewer.component.html | 18 + .../kube-score-report-viewer.component.scss | 23 ++ ...kube-score-report-viewer.component.spec.ts | 38 ++ .../kube-score-report-viewer.component.ts | 53 +++ .../popeye-report-viewer.component.html | 60 +++ .../popeye-report-viewer.component.scss | 43 +++ .../popeye-report-viewer.component.spec.ts | 38 ++ .../popeye-report-viewer.component.ts | 59 +++ .../resource-alert-preview.component.html | 5 + .../resource-alert-preview.component.scss | 14 + .../resource-alert-preview.component.spec.ts | 34 ++ .../resource-alert-preview.component.ts | 23 ++ .../resource-alert-view.component.html | 16 + .../resource-alert-view.component.scss | 14 + .../resource-alert-view.component.spec.ts | 38 ++ .../resource-alert-view.component.ts | 46 +++ .../kubernetes/kubernetes-entity-catalog.ts | 3 + .../kubernetes/kubernetes-entity-factory.ts | 8 + .../kubernetes/kubernetes-entity-generator.ts | 16 + ...s-namespace-analysis-report.component.html | 13 + ...s-namespace-analysis-report.component.scss | 0 ...amespace-analysis-report.component.spec.ts | 46 +++ ...tes-namespace-analysis-report.component.ts | 50 +++ .../kubernetes-namespace.component.ts | 18 +- .../kubernetes-resource-viewer.component.html | 7 +- ...bernetes-resource-viewer.component.spec.ts | 3 +- .../kubernetes-resource-viewer.component.ts | 56 ++- .../kubernetes-tab-base.component.ts | 23 +- .../custom/kubernetes/kubernetes.module.ts | 62 +++- .../custom/kubernetes/kubernetes.routing.ts | 23 +- .../kubernetes/kubernetes.store.module.ts | 4 +- .../analysis-reports-list-config.service.ts | 123 +++++++ .../analysis-reports-list-source.ts | 63 ++++ .../analysis-status-cell.component.html | 8 + .../analysis-status-cell.component.scss | 22 ++ .../analysis-status-cell.component.spec.ts | 32 ++ .../analysis-status-cell.component.ts | 16 + .../kubernetes-labels-cell.component.spec.ts | 2 +- .../services/analysis-report.types.ts | 22 ++ .../services/kubernetes.analysis.service.ts | 164 +++++++++ .../kubernetes/services/kubernetes.service.ts | 19 +- .../services/kubescore-report.helper.ts | 57 +++ .../services/popeye-report.helper.ts | 69 ++++ .../kubernetes/services/route.helper.ts | 11 + .../action-builders/kube.action-builders.ts | 39 ++ .../kubernetes/store/analysis.effects.ts | 264 +++++++++++++ .../kubernetes/store/anaylsis.actions.ts | 69 ++++ .../custom/kubernetes/store/kube.getIds.ts | 2 +- .../src/custom/kubernetes/store/kube.types.ts | 27 ++ .../kubernetes/store/kubernetes.actions.ts | 3 - .../kubernetes/store/kubernetes.effects.ts | 2 +- .../analysis-info-card.component.html | 4 + .../analysis-info-card.component.scss | 21 ++ .../analysis-info-card.component.spec.ts | 29 ++ .../analysis-info-card.component.theme.scss | 21 ++ .../analysis-info-card.component.ts | 55 +++ .../kubernetes-analysis-info.component.html | 7 + .../kubernetes-analysis-info.component.scss | 5 + ...kubernetes-analysis-info.component.spec.ts | 38 ++ .../kubernetes-analysis-info.component.ts | 23 ++ .../kubernetes-analysis-report.component.html | 7 + .../kubernetes-analysis-report.component.scss | 43 +++ ...bernetes-analysis-report.component.spec.ts | 32 ++ ...netes-analysis-report.component.theme.scss | 13 + .../kubernetes-analysis-report.component.ts | 80 ++++ .../kubernetes-analysis-tab.component.html | 5 + .../kubernetes-analysis-tab.component.scss | 0 .../kubernetes-analysis-tab.component.spec.ts | 42 +++ .../kubernetes-analysis-tab.component.ts | 24 ++ .../helm-release-tab-base.component.spec.ts | 9 +- .../helm-release-tab-base.component.ts | 50 +-- .../workloads/release/icon-helper.ts | 77 ++++ .../helm-release-analysis-tab.component.html | 12 + .../helm-release-analysis-tab.component.scss | 0 ...elm-release-analysis-tab.component.spec.ts | 42 +++ .../helm-release-analysis-tab.component.ts | 42 +++ .../tabs/helm-release-helper.service.ts | 4 +- ...helm-release-resource-graph.component.html | 51 ++- ...helm-release-resource-graph.component.scss | 6 + ...m-release-resource-graph.component.spec.ts | 9 +- .../helm-release-resource-graph.component.ts | 171 +++++++-- .../helm-release-summary-tab.component.html | 15 +- ...helm-release-summary-tab.component.spec.ts | 14 +- .../helm-release-summary-tab.component.ts | 162 ++++---- .../store/workloads-entity-factory.ts | 4 +- .../store/workloads-entity-generator.ts | 4 +- .../kubernetes/workloads/workload.types.ts | 40 +- .../workloads/workloads-entity-catalog.ts | 4 +- .../kubernetes/workloads/workloads.module.ts | 2 + .../kubernetes/workloads/workloads.routing.ts | 4 +- .../assets/core/custom/kubescore.md | 7 + .../assets/core/custom/kubescore.png | Bin 0 -> 36127 bytes .../suse-theme/assets/core/custom/popeye.md | 7 + .../suse-theme/assets/core/custom/popeye.png | Bin 0 -> 124519 bytes .../suse-theme/assets/core/custom/sonobuoy.md | 5 + .../assets/core/custom/sonobuoy.png | Bin 0 -> 46789 bytes .../assets/core/custom/sonobuoy.svg | 81 ++++ src/frontend/packages/theme/_helper.scss | 2 +- src/jetstream/config.example | 5 +- src/jetstream/go.mod | 1 + src/jetstream/go.sum | 14 + src/jetstream/load_plugins.go | 3 + .../analysis/20200210105400_Analysis.go | 43 +++ .../plugins/analysis/container/Dockerfile | 61 +++ .../plugins/analysis/container/go.mod | 12 + .../plugins/analysis/container/go.sum | 48 +++ .../plugins/analysis/container/kubescore.go | 59 +++ .../plugins/analysis/container/main.go | 171 +++++++++ .../plugins/analysis/container/popeye.go | 108 ++++++ .../plugins/analysis/container/routes.go | 90 +++++ .../plugins/analysis/container/run.go | 130 +++++++ .../container/scripts/kubescore-runner.sh | 16 + .../container/scripts/sonobuoy-runner.sh | 19 + .../plugins/analysis/container/sonobuoy.go_ | 92 +++++ .../plugins/analysis/container/status.go | 74 ++++ .../plugins/analysis/container/types.go | 48 +++ src/jetstream/plugins/analysis/list.go | 228 ++++++++++++ src/jetstream/plugins/analysis/main.go | 139 +++++++ src/jetstream/plugins/analysis/run.go | 188 ++++++++++ src/jetstream/plugins/analysis/status.go | 109 ++++++ .../analysis/store/analysis_store_db.go | 164 +++++++++ src/jetstream/plugins/analysis/store/main.go | 39 ++ .../plugins/kubernetes/endpoint_config.go | 19 +- .../plugins/kubernetes/get_release.go | 2 +- src/jetstream/plugins/kubernetes/go.mod | 1 - src/jetstream/plugins/kubernetes/go.sum | 23 +- .../plugins/kubernetes/helm/graph.go | 38 +- .../plugins/kubernetes/helm/release.go | 140 +++++-- 174 files changed, 6005 insertions(+), 409 deletions(-) create mode 100644 deploy/kubernetes/console/templates/analyzers.yaml rename docs/suse/{kube-terminal-dev.md => suse-extensions-dev.md} (60%) create mode 100644 src/frontend/packages/core/src/shared/services/session.service.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.html create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.spec.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.html create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.spec.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.html create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.spec.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.html create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.spec.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.html create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.spec.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.html create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.spec.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.html create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.spec.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.html create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.spec.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-config.service.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-source.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.html create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.spec.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/services/analysis-report.types.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes.analysis.service.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubescore-report.helper.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/services/popeye-report.helper.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/services/route.helper.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/store/analysis.effects.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/store/anaylsis.actions.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.html create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.spec.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.theme.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.html create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.spec.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.html create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.spec.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.theme.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.html create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.spec.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/icon-helper.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.html create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.spec.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.ts create mode 100644 src/frontend/packages/suse-theme/assets/core/custom/kubescore.md create mode 100644 src/frontend/packages/suse-theme/assets/core/custom/kubescore.png create mode 100644 src/frontend/packages/suse-theme/assets/core/custom/popeye.md create mode 100644 src/frontend/packages/suse-theme/assets/core/custom/popeye.png create mode 100644 src/frontend/packages/suse-theme/assets/core/custom/sonobuoy.md create mode 100644 src/frontend/packages/suse-theme/assets/core/custom/sonobuoy.png create mode 100644 src/frontend/packages/suse-theme/assets/core/custom/sonobuoy.svg create mode 100644 src/jetstream/plugins/analysis/20200210105400_Analysis.go create mode 100644 src/jetstream/plugins/analysis/container/Dockerfile create mode 100644 src/jetstream/plugins/analysis/container/go.mod create mode 100644 src/jetstream/plugins/analysis/container/go.sum create mode 100644 src/jetstream/plugins/analysis/container/kubescore.go create mode 100644 src/jetstream/plugins/analysis/container/main.go create mode 100644 src/jetstream/plugins/analysis/container/popeye.go create mode 100644 src/jetstream/plugins/analysis/container/routes.go create mode 100644 src/jetstream/plugins/analysis/container/run.go create mode 100755 src/jetstream/plugins/analysis/container/scripts/kubescore-runner.sh create mode 100755 src/jetstream/plugins/analysis/container/scripts/sonobuoy-runner.sh create mode 100644 src/jetstream/plugins/analysis/container/sonobuoy.go_ create mode 100644 src/jetstream/plugins/analysis/container/status.go create mode 100644 src/jetstream/plugins/analysis/container/types.go create mode 100644 src/jetstream/plugins/analysis/list.go create mode 100644 src/jetstream/plugins/analysis/main.go create mode 100644 src/jetstream/plugins/analysis/run.go create mode 100644 src/jetstream/plugins/analysis/status.go create mode 100644 src/jetstream/plugins/analysis/store/analysis_store_db.go create mode 100644 src/jetstream/plugins/analysis/store/main.go diff --git a/.gitignore b/.gitignore index b34c80c94b..fd305dfd93 100644 --- a/.gitignore +++ b/.gitignore @@ -95,6 +95,7 @@ src/jetstream/console-database.db src/jetstream/config.properties src/jetstream/db/dbconf.yml src/jetstream/plugins/monocular/chart-repo/chartrepo +src/jetstream/plugins/analysis/container/analyzers # Customisations - these can be removed in the future # Left in for now to prevent these files being checked-in, if they are still present diff --git a/deploy/ci/suse-console-dev-releases.yml b/deploy/ci/suse-console-dev-releases.yml index ef2247a720..be33e148f5 100644 --- a/deploy/ci/suse-console-dev-releases.yml +++ b/deploy/ci/suse-console-dev-releases.yml @@ -66,12 +66,18 @@ resources: password: ((docker-password)) repository: ((docker-repository))/stratos-chartsync - name: kube-terminal-image - type: docker-image - source: - username: ((docker-username)) - password: ((docker-password)) - repository: ((docker-repository))/stratos-kube-terminal - + type: docker-image + source: + username: ((docker-username)) + password: ((docker-password)) + repository: ((docker-repository))/stratos-kube-terminal +- name: analyzers-image + type: docker-image + source: + username: ((docker-username)) + password: ((docker-password)) + repository: ((docker-repository))/stratos-analyzers + # Artifacts - name: image-tag type: s3 @@ -199,6 +205,14 @@ jobs: tag: image-tag/v2-alpha-tag patch_base_reg: ((patch-base-reg)) patch_base_tag: ((patch-base-tag)) + - put: analyzers-image + params: + dockerfile: stratos/src/jetstream/plugins/analysis/container/Dockerfile + build: stratos/src/jetstream/plugins/analysis/container/ + tag: image-tag/v2-alpha-tag + patch_base_reg: ((patch-base-reg)) + patch_base_tag: ((patch-base-tag)) + - name: create-chart plan: - get: stratos diff --git a/deploy/kubernetes/console/templates/analyzers.yaml b/deploy/kubernetes/console/templates/analyzers.yaml new file mode 100644 index 0000000000..ca4d5a0d6d --- /dev/null +++ b/deploy/kubernetes/console/templates/analyzers.yaml @@ -0,0 +1,62 @@ +--- +{{- if semverCompare ">=1.16" (printf "%s.%s" .Capabilities.KubeVersion.Major (trimSuffix "+" .Capabilities.KubeVersion.Minor) )}} +apiVersion: apps/v1 +{{- else }} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Deployment +metadata: + name: stratos-analyzers + labels: + app.kubernetes.io/name: "stratos" + app.kubernetes.io/instance: "{{ .Release.Name }}" + app.kubernetes.io/version: "{{ .Chart.AppVersion }}" + app.kubernetes.io/component: "stratos-analyzers" + helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" +spec: + selector: + matchLabels: + app.kubernetes.io/name: "stratos" + app.kubernetes.io/component: "stratos-analyzers" + template: + metadata: + labels: + app.kubernetes.io/name: "stratos" + app.kubernetes.io/instance: "{{ .Release.Name }}" + app.kubernetes.io/version: "{{ .Chart.AppVersion }}" + app.kubernetes.io/component: "stratos-analyzers" + helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" + app: "{{ .Release.Name }}" + spec: + containers: + - name: analyzers + image: {{.Values.kube.registry.hostname}}/{{.Values.kube.organization}}/stratos-analyzers:{{.Values.consoleVersion}} + imagePullPolicy: {{.Values.imagePullPolicy}} + ports: + - name: api + containerPort: 8090 + env: + - name: ANALYSIS_SCRIPTS_DIR + value: "/scripts" + - name: ANALYSIS_REPORTS_DIR + value: "/reports" +--- +apiVersion: v1 +kind: Service +metadata: + name: "{{ .Release.Name }}-analyzers" + labels: + app.kubernetes.io/name: "stratos" + app.kubernetes.io/instance: "{{ .Release.Name }}" + app.kubernetes.io/version: "{{ .Chart.AppVersion }}" + app.kubernetes.io/component: "stratos-analyzers-service" + helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" +spec: + type: ClusterIP + ports: + - name: analyzers + port: 8090 + targetPort: 8090 + selector: + app: "{{ .Release.Name }}" + app.kubernetes.io/component: "stratos-analyzers" diff --git a/deploy/kubernetes/console/values.yaml b/deploy/kubernetes/console/values.yaml index 2335e6ccbb..5dbf69f47d 100644 --- a/deploy/kubernetes/console/values.yaml +++ b/deploy/kubernetes/console/values.yaml @@ -57,6 +57,7 @@ console: servicePort: 80 # nodePort: 30001 + # Name of config map that provides the template files for user invitation emails templatesConfigMapName: @@ -120,6 +121,7 @@ images: fdbserver: stratos-fdbserver fdbdoclayer: stratos-fdbdoclayer chartsync: stratos-chartsync + analyzers: stratos-analyzers # Specify which storage class should be used for PVCs #storageClass: default diff --git a/deploy/kubernetes/custom/__stratos.tpl b/deploy/kubernetes/custom/__stratos.tpl index 732932914d..d5eb091928 100644 --- a/deploy/kubernetes/custom/__stratos.tpl +++ b/deploy/kubernetes/custom/__stratos.tpl @@ -12,6 +12,8 @@ value: "mongodb://{{ .Release.Name }}-fdbdoclayer:27016" - name: SYNC_SERVER_URL value: "http://{{ .Release.Name }}-chartsync:8080" +- name: ANALYSIS_SERVICES_API + value: "http://{{ .Release.Name }}-analyzers:8090" - name: STRATOS_KUBERNETES_NAMESPACE value: "{{ .Release.Namespace }}" - name: STRATOS_KUBERNETES_TERMINAL_IMAGE diff --git a/deploy/kubernetes/custom/custom-build.sh b/deploy/kubernetes/custom/custom-build.sh index 35d6202755..b44f9fbd41 100644 --- a/deploy/kubernetes/custom/custom-build.sh +++ b/deploy/kubernetes/custom/custom-build.sh @@ -25,4 +25,9 @@ function custom_image_build() { # Build and push an image for the Kubernetes Terminal log "-- Building/publishing Kubernetes Terminal" patchAndPushImage stratos-kube-terminal Dockerfile.kubeterminal "${STRATOS_PATH}/deploy/containers/kube-terminal" + + # Analzyers container + log "-- Building/publishing Stratos Analyzers" + patchAndPushImage stratos-analyzers Dockerfile "${STRATOS_PATH}/src/jetstream/plugins/analysis/container" + } \ No newline at end of file diff --git a/docs/extensions.md b/docs/extensions.md index 918c25df18..64c182e6a0 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -174,7 +174,7 @@ First, create the custom-src folder structure - from the top-level of the Strato ``` mkdir -p custom-src/frontend/app/custom -mkdir -p custom-src/frontend/assets/custom +mkdir -p /frontend/assets/custom ``` Next, run the customize task: diff --git a/docs/suse/kube-terminal-dev.md b/docs/suse/suse-extensions-dev.md similarity index 60% rename from docs/suse/kube-terminal-dev.md rename to docs/suse/suse-extensions-dev.md index fe552b064c..9ac1e4e7b3 100644 --- a/docs/suse/kube-terminal-dev.md +++ b/docs/suse/suse-extensions-dev.md @@ -18,4 +18,24 @@ you will need to edit the `src/jetstream/config.properties` file and set these t The Jetstream backend should be configured. -> Note: Ensure you set `ENABLE_TECH_PREVIEW=true` to enable the Kubernetes Terminal feature. \ No newline at end of file +> Note: Ensure you set `ENABLE_TECH_PREVIEW=true` to enable the Kubernetes Terminal feature. + + +# Enabling Security Obvervability Analyzers in local development + +You need to build the docker image for the analyzers container. + +``` +cd src/jetstream/plugins/analysis/container +docker build . -t stratos-analyzers +``` + +Now run this container - this will provide the analysis engines to Stratos: + +`docker run -d -p 8090:8090 stratos-analyzers` + +Edit your Jetstream `config.properties` file and add the following lines: + +``` +ANALYSIS_SERVICES_API=http://127.0.0.1:8090 +``` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ae80a769d8..8bbb6d91b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1610,9 +1610,9 @@ } }, "@babel/helper-validator-identifier": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", - "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", "dev": true }, "@babel/helper-wrap-function": { @@ -1756,6 +1756,146 @@ } } }, + "@babel/helpers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", + "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.4.tgz", + "integrity": "sha512-toLIHUIAgcQygFZRAQcsLQV3CBuX6yOIru1kJk/qqqvcRmZrYe6WavZTSG+bB8MxhnL9YPf+pKQfuiP161q7ng==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", + "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.4.tgz", + "integrity": "sha512-8jHII4hf+YVDsskTF6WuMB3X4Eh+PsUkC2ljq22so5rHvH+T8BzyL94VOdyFLNR8tBSVXOTbNHOKpR4TfRxVtA==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.4.tgz", + "integrity": "sha512-aSy7p5THgSYm4YyxNGz6jZpXf+Ok40QF3aA2LyIONkDHpAcJzDUqlCKXv6peqYUs2gmic849C/t2HKw2a2K20Q==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.4", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.4.tgz", + "integrity": "sha512-UTCFOxC3FsFHb7lkRMVvgLzaRVamXuAs2Tz4wajva4WxtVY82eZeaUBtC2Zt95FU9TiznuC0Zk35tsim8jeVpg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "@babel/highlight": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", @@ -2163,11 +2303,7 @@ "@babel/code-frame": { "version": "7.10.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", - "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.1" - } + "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==" }, "@babel/helper-function-name": { "version": "7.10.1", @@ -2181,58 +2317,110 @@ } }, "@babel/helper-get-function-arity": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz", - "integrity": "sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.10.4" } }, "@babel/helper-validator-identifier": { "version": "7.10.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", - "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==", - "dev": true + "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==" }, "@babel/highlight": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz", - "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==", - "dev": true, + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", "requires": { - "@babel/helper-validator-identifier": "^7.10.1", + "@babel/helper-validator-identifier": "^7.9.0", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.2.tgz", - "integrity": "sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.4.tgz", + "integrity": "sha512-8jHII4hf+YVDsskTF6WuMB3X4Eh+PsUkC2ljq22so5rHvH+T8BzyL94VOdyFLNR8tBSVXOTbNHOKpR4TfRxVtA==", "dev": true }, "@babel/template": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.1.tgz", - "integrity": "sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.1", - "@babel/parser": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + } } }, "@babel/types": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.2.tgz", - "integrity": "sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.4.tgz", + "integrity": "sha512-UTCFOxC3FsFHb7lkRMVvgLzaRVamXuAs2Tz4wajva4WxtVY82eZeaUBtC2Zt95FU9TiznuC0Zk35tsim8jeVpg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.1", + "@babel/helper-validator-identifier": "^7.10.4", "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + } + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -2398,7 +2586,7 @@ "integrity": "sha512-AR0E/lZMfLstScFwztApGeyTHJ5u3JUKMjneqRItWeEqDdHWZwAOKycvQNCasCK/3r5YXsuNG25funcJDu7Y2g==", "dev": true, "requires": { - "regenerator-transform": "^0.14.2" + "@babel/helper-plugin-utils": "^7.10.1" } }, "@babel/plugin-transform-spread": { @@ -2436,7 +2624,6 @@ "integrity": "sha512-qX8KZcmbvA23zDi+lk9s6hC1FM7jgLHYIjuLgULgc8QtYnmB3tAVIYkNoKRQ75qWBeyzcoMoK8ZQmogGtC/w0g==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.1", "@babel/helper-plugin-utils": "^7.10.1" } }, @@ -2642,6 +2829,14 @@ "@babel/helper-validator-identifier": "^7.9.0", "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + } } }, "@cfstratos/ajsf-core": { @@ -2858,12 +3053,12 @@ } }, "@rollup/plugin-json": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.0.2.tgz", - "integrity": "sha512-t4zJMc98BdH42mBuzjhQA7dKh0t4vMJlUka6Fz0c+iO5IVnWaEMiYBy1uBj9ruHZzXBW23IPDGL9oCzBkQ9Udg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", + "integrity": "sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==", "dev": true, "requires": { - "@rollup/pluginutils": "^3.0.4" + "@rollup/pluginutils": "^3.0.8" } }, "@rollup/plugin-node-resolve": { @@ -3445,12 +3640,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz", "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==", "dev": true, - "optional": true - }, - "angular2-virtual-scroll": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/angular2-virtual-scroll/-/angular2-virtual-scroll-0.4.16.tgz", - "integrity": "sha512-6NWk0DjCh4ebU8+LgfBoKYyp3McxDA/k5vTnEiV32VpVnyhN//eThZpVpggI1D2fJBqgTAY09C8v++qXHHLP7A==", "requires": { "@webassemblyjs/ast": "1.8.5", "@webassemblyjs/helper-buffer": "1.8.5", @@ -3800,7 +3989,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -4078,6 +4266,16 @@ "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", "dev": true }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==" + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + }, "util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", @@ -4086,21 +4284,6 @@ "requires": { "inherits": "2.0.1" } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true } } }, @@ -4763,6 +4946,25 @@ "safe-buffer": "^5.2.0" }, "dependencies": { + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -5090,16 +5292,6 @@ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } } } }, @@ -5202,7 +5394,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -5213,7 +5404,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -5542,7 +5732,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -5550,8 +5739,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "color-string": { "version": "1.5.3", @@ -7410,8 +7598,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "1.8.1", @@ -9315,8 +9502,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-glob": { "version": "1.0.0", @@ -10921,8 +11107,7 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { "version": "3.13.1", diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-tabs-base.component.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-tabs-base.component.ts index b7af4a19ea..12db72f9ba 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-tabs-base.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-tabs-base.component.ts @@ -89,7 +89,7 @@ export class ApplicationTabsBaseComponent implements OnInit, OnDestroy { switchMap(space => this.currentUserPermissionsService.can(CfCurrentUserPermissions.APPLICATION_VIEW_ENV_VARS, this.applicationService.cfGuid, space.metadata.guid) ), - map(can => !can) + map(can => !can), ); this.tabLinks = [ diff --git a/src/frontend/packages/core/sass/components/text-status.theme.scss b/src/frontend/packages/core/sass/components/text-status.theme.scss index 68b3d1821b..a054375175 100644 --- a/src/frontend/packages/core/sass/components/text-status.theme.scss +++ b/src/frontend/packages/core/sass/components/text-status.theme.scss @@ -6,6 +6,7 @@ $status-warning: map-get($status-colors, warning); $status-danger: map-get($status-colors, danger); $status-tentative: map-get($status-colors, tentative); + $status-info: map-get($status-colors, info); .text-success { color: $status-success; @@ -23,6 +24,10 @@ color: $status-tentative; } + .text-info { + color: $status-info; + } + // Border colors .border-success { @@ -41,4 +46,8 @@ border-color: $status-tentative; } + .border-info { + border-color: $status-info; + } + } diff --git a/src/frontend/packages/core/sass/mat-desktop.scss b/src/frontend/packages/core/sass/mat-desktop.scss index 3538518e13..cf474347e0 100644 --- a/src/frontend/packages/core/sass/mat-desktop.scss +++ b/src/frontend/packages/core/sass/mat-desktop.scss @@ -17,6 +17,13 @@ $desktop-toggle-button-item-height: $desktop-menu-item-height - 2px; line-height: $desktop-menu-item-height; min-width: 128px; padding: 0 24px; + + &.hasIcon { + padding-left: 16px; + .mat-icon { + margin-right: 12px; + } + } } .mat-menu-panel { diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.html b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.html index 2c4b59dc38..d7ca4b06f5 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.html +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.html @@ -16,10 +16,18 @@
-
+
-
- {{ activeTabLabel }} +
+ {{ data[0] }} +
+
+ + {{ breadcrumbDef.value }} + {{ breadcrumbDef.value }} + chevron_right +
diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss index 26a2d129ce..34b3de01a3 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss @@ -79,6 +79,11 @@ $app-header-height: 56px; } .page-header-sub-nav { + $breadcrumb-opacity: .7; + $breadcrumb-hover-opacity: 1; + $breadcrumb-padding: 3px; + $font-size: 20px; + align-items: center; border-bottom: 1px solid rgba(0, 0, 0, .1); display: flex; @@ -91,7 +96,6 @@ $app-header-height: 56px; } &__title { display: none; - $font-size: 20px; font-size: $font-size; font-weight: bold; line-height: $font-size; @@ -106,4 +110,34 @@ $app-header-height: 56px; opacity: .6; width: 100%; } + &__breadcrumb { + font-size: $font-size; + font-weight: bold; + line-height: $font-size; + } + &__breadcrumb, + &__breadcrumb-separator { + opacity: $breadcrumb-opacity; + } + &__breadcrumb-separator { + font-size: 24px; + margin: 0 $breadcrumb-padding; + user-select: none; + } + &__breadcrumbs { + align-items: center; + display: flex; + justify-content: center; + } + &__breadcrumb-nolink { + opacity: $breadcrumb-hover-opacity; + } + &__breadcrumb-link { + cursor: pointer; + outline: none; + &:hover { + opacity: $breadcrumb-hover-opacity; + } + } + } diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts index 8310e6723e..42a8998120 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts @@ -17,7 +17,9 @@ import { stratosEntityCatalog } from '../../../../../store/src/stratos-entity-ca import { TabNavService } from '../../../../tab-nav.service'; import { CustomizationService } from '../../../core/customizations.types'; import { EndpointsService } from '../../../core/endpoints.service'; +import { IHeaderBreadcrumbLink } from '../../../shared/components/page-header/page-header.types'; import { SidePanelService } from '../../../shared/services/side-panel.service'; +import { IPageSideNavTab } from '../page-side-nav/page-side-nav.component'; import { PageHeaderService } from './../../../core/page-header-service/page-header.service'; import { SideNavItem } from './../side-nav/side-nav.component'; @@ -30,7 +32,7 @@ import { SideNavItem } from './../side-nav/side-nav.component'; export class DashboardBaseComponent implements OnInit, OnDestroy, AfterViewInit { public activeTabLabel$: Observable; - public subNavData$: Observable<[string, Portal]>; + public subNavData$: Observable<[string, Portal, IPageSideNavTab, IHeaderBreadcrumbLink[]]>; public isMobile$: Observable; public sideNavMode$: Observable; public sideNavMode: string; @@ -133,8 +135,9 @@ export class DashboardBaseComponent implements OnInit, OnDestroy, AfterViewInit this.tabNavService.getCurrentTabHeaderObservable().pipe( startWith(null) ), - this.tabNavService.tabSubNav$ - ); + this.tabNavService.tabSubNav$, + this.tabNavService.tabSubNavBreadcrumbs$ + ).pipe(map(([tabNav, tabSubNav, tabSubNavBreadcrumb]) => [tabNav ? tabNav.label : null, tabSubNav, tabNav, tabSubNavBreadcrumb])); // Register all health checks for endpoint types that support this entityCatalog.getAllEndpointTypes().forEach(epType => { diff --git a/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.ts b/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.ts index b74dbed831..bab4cd21dc 100644 --- a/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.ts +++ b/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.ts @@ -10,6 +10,7 @@ import { TabNavService } from '../../../../tab-nav.service'; import { StratosTabMetadata } from '../../../core/extension/extension-service'; import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; import { IBreadcrumb } from '../../../shared/components/breadcrumbs/breadcrumbs.types'; +import { map } from 'rxjs/operators'; @@ -54,7 +55,7 @@ export class PageSideNavComponent implements OnInit { } ngOnInit() { - this.activeTab$ = this.tabNavService.getCurrentTabHeaderObservable(); + this.activeTab$ = this.tabNavService.getCurrentTabHeaderObservable().pipe(map(item => item ? item.label : null)); } } diff --git a/src/frontend/packages/core/src/shared/components/cards/card-number-metric/card-number-metric.component.html b/src/frontend/packages/core/src/shared/components/cards/card-number-metric/card-number-metric.component.html index ea0067edbc..b0747565c1 100644 --- a/src/frontend/packages/core/src/shared/components/cards/card-number-metric/card-number-metric.component.html +++ b/src/frontend/packages/core/src/shared/components/cards/card-number-metric/card-number-metric.component.html @@ -14,6 +14,20 @@
{{ labelSingular && value === '1' ? labelSingular : label }}
+
+
+ info + {{ alertInfo.info }} +
+
+ warning +
{{ alertInfo.warning }}
+
+
+ error + {{ alertInfo.error }} +
+
diff --git a/src/frontend/packages/core/src/shared/components/cards/card-number-metric/card-number-metric.component.scss b/src/frontend/packages/core/src/shared/components/cards/card-number-metric/card-number-metric.component.scss index 482e94a189..78aa3599eb 100644 --- a/src/frontend/packages/core/src/shared/components/cards/card-number-metric/card-number-metric.component.scss +++ b/src/frontend/packages/core/src/shared/components/cards/card-number-metric/card-number-metric.component.scss @@ -40,4 +40,26 @@ &__limit { font-size: 18px; } + &__alerts { + cursor: pointer; + display: flex; + flex: 0; + flex-direction: column; + } + &__alert-badge { + align-items: center; + border-radius: 4px; + color: #fff; + display: flex; + font-size: 14px; + margin-bottom: 2px; + padding: 2px 4px; + + &> mat-icon { + font-size: 16px; + height: 16px; + margin-right: 2px; + width: 16x; + } + } } diff --git a/src/frontend/packages/core/src/shared/components/cards/card-number-metric/card-number-metric.component.theme.scss b/src/frontend/packages/core/src/shared/components/cards/card-number-metric/card-number-metric.component.theme.scss index eba218e2e2..2493db6d56 100644 --- a/src/frontend/packages/core/src/shared/components/cards/card-number-metric/card-number-metric.component.theme.scss +++ b/src/frontend/packages/core/src/shared/components/cards/card-number-metric/card-number-metric.component.theme.scss @@ -1,6 +1,12 @@ @mixin app-card-number-metric-theme($theme, $app-theme) { $status-colors: map-get($app-theme, status); $subdued: mat-color($app-theme, subdued-color); + + $status-colors: map-get($app-theme, status); + $status-warning: map-get($status-colors, warning); + $status-danger: map-get($status-colors, danger); + $status-info: map-get($status-colors, info); + .number-metric-card { &__icon, &__anchor, @@ -9,4 +15,16 @@ color: $subdued; } } -} + + .number-metric-card__alert-badge { + &-error { + background-color: $status-danger; + } + &-info { + background-color: $status-info; + } + &-warning { + background-color: $status-warning; + } + } +} \ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/cards/card-number-metric/card-number-metric.component.ts b/src/frontend/packages/core/src/shared/components/cards/card-number-metric/card-number-metric.component.ts index f0170d28b4..fa02ab447e 100644 --- a/src/frontend/packages/core/src/shared/components/cards/card-number-metric/card-number-metric.component.ts +++ b/src/frontend/packages/core/src/shared/components/cards/card-number-metric/card-number-metric.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnChanges, OnInit } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'; import { Store } from '@ngrx/store'; import { BehaviorSubject } from 'rxjs'; @@ -8,6 +8,14 @@ import { StratosStatus } from '../../../../../../store/src/types/shared.types'; import { UtilsService } from '../../../../core/utils.service'; import { determineCardStatus } from '../card-status/card-status.component'; +enum AlertLevel { + OK = 0, + Info, + Warning, + Error, + Unknown, +} + @Component({ selector: 'app-card-number-metric', templateUrl: './card-number-metric.component.html', @@ -26,6 +34,16 @@ export class CardNumberMetricComponent implements OnInit, OnChanges { @Input() textOnly = false; @Input() labelAtTop = false; @Input() link: () => void | string; + @Output() showAlerts = new EventEmitter(); + + @Input('alerts') + set alerts(alerts) { + if (alerts) { + this.processAlerts(alerts); + } + } + + alertInfo: any; formattedValue: string; formattedLimit: string; @@ -102,4 +120,31 @@ export class CardNumberMetricComponent implements OnInit, OnChanges { this.link(); } } + + processAlerts(alerts) { + this.alertInfo = { + info: 0, + warning: 0, + error: 0 + }; + + alerts.forEach((alert) => { + switch (alert.level as AlertLevel) { + case AlertLevel.Warning: + this.alertInfo.warning++; + break; + case AlertLevel.Error: + this.alertInfo.error++; + break; + case AlertLevel.Info: + this.alertInfo.info++; + break; + } + }); + } + + public alertsClicked() { + this.showAlerts.emit(this.alertInfo); + } + } diff --git a/src/frontend/packages/core/src/shared/components/list/list-table/app-table-cell-default/app-table-cell-default.component.ts b/src/frontend/packages/core/src/shared/components/list/list-table/app-table-cell-default/app-table-cell-default.component.ts index 979c260eb6..5d12e93d83 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-table/app-table-cell-default/app-table-cell-default.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-table/app-table-cell-default/app-table-cell-default.component.ts @@ -1,5 +1,5 @@ import { Component, Input, OnDestroy } from '@angular/core'; -import { Subscription, Observable } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; import { objectHelper } from '../../../../../core/helper-classes/object.helpers'; import { pathGet } from '../../../../../core/utils.service'; @@ -22,6 +22,7 @@ export class TableCellDefaultComponent extends TableCellCustom implements this.pRow = row; if (row) { this.setValue(row, this.schemaKey); + this.setSyncLink(); } } @@ -32,6 +33,7 @@ export class TableCellDefaultComponent extends TableCellCustom implements this.pSchemaKey = schemaKey; if (this.row) { this.setValue(this.row, schemaKey); + this.setSyncLink(); } } @@ -63,7 +65,7 @@ export class TableCellDefaultComponent extends TableCellCustom implements } private setSyncLink() { - if (!this.cellDefinition.getLink) { + if (!this.cellDefinition || !this.cellDefinition.getLink) { return; } const linkValue = this.cellDefinition.getLink(this.row); diff --git a/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-actions/table-cell-actions.component.html b/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-actions/table-cell-actions.component.html index 01680e8f60..107f5c8149 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-actions/table-cell-actions.component.html +++ b/src/frontend/packages/core/src/shared/components/list/list-table/table-cell-actions/table-cell-actions.component.html @@ -3,8 +3,9 @@ - diff --git a/src/frontend/packages/core/src/shared/components/page-sub-nav/page-sub-nav.component.ts b/src/frontend/packages/core/src/shared/components/page-sub-nav/page-sub-nav.component.ts index 3ca1675980..8c66274b02 100644 --- a/src/frontend/packages/core/src/shared/components/page-sub-nav/page-sub-nav.component.ts +++ b/src/frontend/packages/core/src/shared/components/page-sub-nav/page-sub-nav.component.ts @@ -1,7 +1,8 @@ import { TemplatePortal } from '@angular/cdk/portal'; -import { AfterViewInit, Component, OnDestroy, TemplateRef, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, Input, OnDestroy, TemplateRef, ViewChild } from '@angular/core'; import { TabNavService } from '../../../../tab-nav.service'; +import { IHeaderBreadcrumbLink } from '../page-header/page-header.types'; @Component({ selector: 'app-page-sub-nav', @@ -9,6 +10,12 @@ import { TabNavService } from '../../../../tab-nav.service'; styleUrls: ['./page-sub-nav.component.scss'] }) export class PageSubNavComponent implements AfterViewInit, OnDestroy { + + @Input('breadcrumbs') + set breadcrumbs(crumbs: IHeaderBreadcrumbLink[]) { + this.tabNavService.setSubNavBreadcrumbs(crumbs); + } + @ViewChild('subNavTmpl', { static: true }) subNavTmpl: TemplateRef; constructor(private tabNavService: TabNavService) { } diff --git a/src/frontend/packages/core/src/shared/components/ssh-viewer/ssh-viewer.component.ts b/src/frontend/packages/core/src/shared/components/ssh-viewer/ssh-viewer.component.ts index 8d9cedccb8..8c075008f8 100644 --- a/src/frontend/packages/core/src/shared/components/ssh-viewer/ssh-viewer.component.ts +++ b/src/frontend/packages/core/src/shared/components/ssh-viewer/ssh-viewer.component.ts @@ -122,7 +122,7 @@ export class SshViewerComponent implements OnInit, OnDestroy { this.xterm.write(String.fromCharCode(parseInt(c, 16))); } } else { - console.log('Error') + console.error('Error: ', this.errorMessage) const eMsg = this.errorMessage; this.errorMessage = eMsg; } @@ -145,17 +145,17 @@ export class SshViewerComponent implements OnInit, OnDestroy { parseInt(chars[1], 16) === 93 && parseInt(chars[2], 16) === 50 && parseInt(chars[3], 16) === 59) { - let title = ''; - for (let i = 4; i < chars.length - 1; i++) { - title += String.fromCharCode(parseInt(chars[i], 16)); - } - if (title.length > 0 && title.charAt(0) === '!') { - this.errorMessage = title.substr(1); - console.log(this.errorMessage); - return true; - } - this.message = title; + let title = ''; + for (let i = 4; i < chars.length - 1; i++) { + title += String.fromCharCode(parseInt(chars[i], 16)); } + if (title.length > 0 && title.charAt(0) === '!') { + this.errorMessage = title.substr(1); + console.error(this.errorMessage); + return true; + } + this.message = title; + } return false; } } diff --git a/src/frontend/packages/core/src/shared/services/session.service.ts b/src/frontend/packages/core/src/shared/services/session.service.ts new file mode 100644 index 0000000000..1c133f2106 --- /dev/null +++ b/src/frontend/packages/core/src/shared/services/session.service.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { first, map } from 'rxjs/operators'; + +import { GeneralEntityAppState } from '../../../../store/src/app-state'; +import { selectSessionData } from '../../../../store/src/reducers/auth.reducer'; + +@Injectable() +export class SessionService { + + constructor(private store: Store) { } + + isTechPreview(): Observable { + return this.store.select(selectSessionData()).pipe( + first(), + map(sessionData => sessionData.config.enableTechPreview || false) + ) + } +} \ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/services/snackbar.service.ts b/src/frontend/packages/core/src/shared/services/snackbar.service.ts index 13ba4eb2c6..bfa94ce28e 100644 --- a/src/frontend/packages/core/src/shared/services/snackbar.service.ts +++ b/src/frontend/packages/core/src/shared/services/snackbar.service.ts @@ -4,26 +4,31 @@ import { MatSnackBar, MatSnackBarRef, SimpleSnackBar } from '@angular/material/s import { SnackBarReturnComponent } from '../components/snackbar-return/snackbar-return.component'; /** - * Servicve for showing snackbars + * Service for showing snackbars */ @Injectable({ providedIn: 'root', }) export class SnackBarService { - constructor(public snackBar: MatSnackBar) {} + constructor(public snackBar: MatSnackBar) { } private snackBars: MatSnackBarRef[] = []; public show(message: string, closeMessage?: string, duration: number = 5000) { this.snackBars.push(this.snackBar.open(message, closeMessage, { - duration: closeMessage ? null :duration + duration: closeMessage ? null : duration })); } - public showReturn(message: string, returnUrl: string, returnLabel: string) { + public showReturn(message: string, returnUrl: string | string[], returnLabel: string, duration?: number) { this.snackBars.push(this.snackBar.openFromComponent(SnackBarReturnComponent, { - data: { message, returnUrl, returnLabel } + duration, + data: { + message, + returnUrl, + returnLabel, + } })); } diff --git a/src/frontend/packages/core/src/shared/shared.module.ts b/src/frontend/packages/core/src/shared/shared.module.ts index e5915d0af6..160c4d90a7 100644 --- a/src/frontend/packages/core/src/shared/shared.module.ts +++ b/src/frontend/packages/core/src/shared/shared.module.ts @@ -117,6 +117,7 @@ import { UsageBytesPipe } from './pipes/usage-bytes.pipe'; import { ValuesPipe } from './pipes/values.pipe'; import { LongRunningOperationsService } from './services/long-running-op.service'; import { MetricsRangeSelectorService } from './services/metrics-range-selector.service'; +import { SessionService } from './services/session.service'; import { UserPermissionDirective } from './user-permission.directive'; @@ -322,6 +323,7 @@ import { UserPermissionDirective } from './user-permission.directive'; InternalEventMonitorFactory, MetricsRangeSelectorService, LongRunningOperationsService, + SessionService ] }) export class SharedModule { } diff --git a/src/frontend/packages/core/tab-nav.service.ts b/src/frontend/packages/core/tab-nav.service.ts index fbdad8cad7..d11ed06666 100644 --- a/src/frontend/packages/core/tab-nav.service.ts +++ b/src/frontend/packages/core/tab-nav.service.ts @@ -5,6 +5,7 @@ import { asapScheduler, BehaviorSubject, combineLatest, Observable, Subject } fr import { filter, map, observeOn, publishReplay, refCount, startWith } from 'rxjs/operators'; import { IPageSideNavTab } from './src/features/dashboard/page-side-nav/page-side-nav.component'; +import { IHeaderBreadcrumbLink } from './src/shared/components/page-header/page-header.types'; @Injectable() @@ -21,6 +22,9 @@ export class TabNavService { private tabSubNavSubject: BehaviorSubject>; public tabSubNav$: Observable>; + private tabSubNavBreadcrumbsSubject: BehaviorSubject; + public tabSubNavBreadcrumbs$: Observable; + private pageHeaderSubject: BehaviorSubject>; public pageHeader$: Observable>; @@ -36,6 +40,10 @@ export class TabNavService { this.tabSubNavSubject.next(portal); } + public setSubNavBreadcrumbs(breadcrumbs: IHeaderBreadcrumbLink[]) { + this.tabSubNavBreadcrumbsSubject.next(breadcrumbs); + } + public setPageHeader(portal: Portal) { this.pageHeaderSubject.next(portal); } @@ -43,12 +51,13 @@ export class TabNavService { public clear() { this.tabNavsSubject.next(undefined); this.tabHeaderSubject.next(undefined); - this.tabSubNavSubject.next(undefined); + this.clearSubNav(); this.pageHeaderSubject.next(undefined); } public clearSubNav() { this.tabSubNavSubject.next(undefined); + this.tabSubNavBreadcrumbsSubject.next(undefined); } public getCurrentTabHeaderObservable() { @@ -63,7 +72,7 @@ export class TabNavService { ); } - public getCurrentTabHeader = (tabs: IPageSideNavTab[]) => { + private getCurrentTabHeader = (tabs: IPageSideNavTab[]) => { if (!tabs) { return null; } @@ -74,7 +83,7 @@ export class TabNavService { if (!activeTab) { return null; } - return activeTab.label; + return activeTab; } private observeSubject(subject: Subject) { @@ -93,6 +102,8 @@ export class TabNavService { this.tabHeader$ = this.observeSubject(this.tabHeaderSubject); this.tabSubNavSubject = new BehaviorSubject(undefined); this.tabSubNav$ = this.observeSubject(this.tabSubNavSubject); + this.tabSubNavBreadcrumbsSubject = new BehaviorSubject(undefined); + this.tabSubNavBreadcrumbs$ = this.observeSubject(this.tabSubNavBreadcrumbsSubject); this.pageHeaderSubject = new BehaviorSubject(undefined); this.pageHeader$ = this.observeSubject(this.pageHeaderSubject); } diff --git a/src/frontend/packages/store/src/actions/pagination.actions.ts b/src/frontend/packages/store/src/actions/pagination.actions.ts index b36603d1e6..0a6f540eec 100644 --- a/src/frontend/packages/store/src/actions/pagination.actions.ts +++ b/src/frontend/packages/store/src/actions/pagination.actions.ts @@ -6,6 +6,7 @@ import { PaginationClientFilter, PaginationParam } from '../types/pagination.typ export const CLEAR_PAGINATION_OF_TYPE = '[Pagination] Clear all pages of type'; export const CLEAR_PAGINATION_OF_ENTITY = '[Pagination] Clear pagination of entity'; export const RESET_PAGINATION = '[Pagination] Reset pagination'; +export const RESET_PAGINATION_OF_TYPE = '[Pagination] Reset pagination of type'; export const CREATE_PAGINATION = '[Pagination] Create pagination'; export const CLEAR_PAGES = '[Pagination] Clear pages only'; export const SET_PAGE = '[Pagination] Set page'; @@ -57,6 +58,13 @@ export class ResetPagination extends BasePaginationAction implements Action { type = RESET_PAGINATION; } +export class ResetPaginationOfType extends BasePaginationAction implements Action { + constructor(pEntityConfig: Partial) { + super(pEntityConfig); + } + type = RESET_PAGINATION_OF_TYPE; +} + export class CreatePagination extends BasePaginationAction implements Action { /** * @param seed The pagination key for the section we should use as a seed when creating the new pagination section. diff --git a/src/frontend/packages/store/src/entity-request-pipeline/entity-pagination-request-pipeline.ts b/src/frontend/packages/store/src/entity-request-pipeline/entity-pagination-request-pipeline.ts index fa82840e99..509b5f661d 100644 --- a/src/frontend/packages/store/src/entity-request-pipeline/entity-pagination-request-pipeline.ts +++ b/src/frontend/packages/store/src/entity-request-pipeline/entity-pagination-request-pipeline.ts @@ -109,7 +109,7 @@ export const basePaginatedRequestPipeline: EntityRequestPipeline = ( ); // Keep, helpful for debugging below chain via tap - // const debug = (val, location) => console.log(`${entity.endpointType}:${entity.entityKey}:${location}: `, val); + // const debug = (val, location) => console.warn(`${entity.endpointType}:${entity.entityKey}:${location}: `, val); return getRequestObjectObservable(request).pipe( first(), diff --git a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-create-pagination.ts b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-create-pagination.ts index 7e57e52957..a34f1d65cb 100644 --- a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-create-pagination.ts +++ b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-create-pagination.ts @@ -1,6 +1,6 @@ +import { CreatePagination } from '../../actions/pagination.actions'; import { entityCatalog } from '../../entity-catalog/entity-catalog'; import { EntityCatalogEntityConfig } from '../../entity-catalog/entity-catalog.types'; -import { CreatePagination } from '../../actions/pagination.actions'; import { PaginationEntityState, PaginationState } from '../../types/pagination.types'; import { spreadClientPagination } from './pagination-reducer.helper'; diff --git a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-reset-pagination.ts b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-reset-pagination.ts index 603b2b767f..18c1566775 100644 --- a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-reset-pagination.ts +++ b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-reset-pagination.ts @@ -37,26 +37,68 @@ export function getDefaultPaginationEntityState(ignoreMaxed?: boolean): Paginati }; } -export function paginationResetPagination(state: PaginationState, action: ResetPagination): PaginationState { + +export function paginationResetPagination(state: PaginationState, action: ResetPagination, allTypes = false): PaginationState { const entityKey = entityCatalog.getEntityKey(action.entityConfig); - if (!state[entityKey] || !state[entityKey][action.paginationKey]) { + + if (!state[entityKey]) { return state; } - const { ids, pageRequests, pageCount, currentPage, totalResults } = getDefaultPaginationEntityState(); + + const entityState = allTypes ? + paginationResetAllPaginationSections(state, entityKey) : + paginationResetPaginationSection(state, action.paginationKey, entityKey); + + if (!entityState) { + return state; + } + const newState = { ...state }; - const entityState = { - ...newState[entityKey], - [action.paginationKey]: { - ...newState[entityKey][action.paginationKey], - ids, - pageRequests, - pageCount, - currentPage, - totalResults, - } - } as PaginationEntityTypeState; return { ...newState, [entityKey]: entityState }; } + +/** + * Reset all pagination sections of an entity type + */ +function paginationResetAllPaginationSections(state: PaginationState, entityKey: string): PaginationEntityTypeState { + return Object.entries(state[entityKey]).reduce((res, [paginationKey, paginationSection]) => { + res[paginationKey] = paginationResetPaginationState(paginationSection); + return res; + }, {} as PaginationEntityTypeState); +} + +/** + * Reset a single pagination section of an entity type + */ +function paginationResetPaginationSection(state: PaginationState, paginationKey: string, entityKey: string): PaginationEntityTypeState { + + const paginationSection = state[entityKey][paginationKey] + if (!paginationSection) { + return; + } + + const entityState: PaginationEntityTypeState = { + ...state[entityKey], + [paginationKey]: paginationResetPaginationState(paginationSection) + }; + return entityState; +} + +/** + * Reset a pagination section (retain initial/user sort/filter/etc) + */ +function paginationResetPaginationState(oldEntityState: PaginationEntityState) { + const { ids, pageRequests, pageCount, currentPage, totalResults } = getDefaultPaginationEntityState(); + const entityState: PaginationEntityState = { + ...oldEntityState, + ids, + pageRequests, + pageCount, + currentPage, + totalResults, + } + return entityState; +} diff --git a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination.reducer.ts b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination.reducer.ts index f39aa853f0..d24616c794 100644 --- a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination.reducer.ts +++ b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination.reducer.ts @@ -14,6 +14,7 @@ import { IgnorePaginationMaxedState, REMOVE_PARAMS, RESET_PAGINATION, + RESET_PAGINATION_OF_TYPE, SET_CLIENT_FILTER, SET_CLIENT_FILTER_KEY, SET_CLIENT_PAGE, @@ -121,6 +122,10 @@ function paginate(action, state = {}, updatePagination) { return paginationResetPagination(state, action); } + if (action.type === RESET_PAGINATION_OF_TYPE && !action.keepPages) { + return paginationResetPagination(state, action, true); + } + if (action.type === CLEAR_PAGINATION_OF_TYPE) { const clearAction = action as ClearPaginationOfType; const clearEntityType = entityCatalog.getEntityKey(clearAction.entityConfig.endpointType, clearAction.entityConfig.entityType); diff --git a/src/frontend/packages/suse-extensions/sass/_all-theme.scss b/src/frontend/packages/suse-extensions/sass/_all-theme.scss index b6b53feafa..9f21f4317f 100644 --- a/src/frontend/packages/suse-extensions/sass/_all-theme.scss +++ b/src/frontend/packages/suse-extensions/sass/_all-theme.scss @@ -1,3 +1,7 @@ +// Theming for the copmponents in the Kubernetes package + +@import '../src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.theme'; +@import '../src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.theme'; @import '../src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.theme'; @mixin apply-theme-suse-extensions($stratos-theme) { @@ -5,6 +9,8 @@ $theme: map-get($stratos-theme, theme); $app-theme: map-get($stratos-theme, app-theme); + @include kube-analysis-report-theme($theme, $app-theme); + @include kube-analysis-card-theme($theme, $app-theme); @include monocular-chart-card($theme, $app-theme); } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.html new file mode 100644 index 0000000000..6bfa187493 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.html @@ -0,0 +1,18 @@ + +
+ + + + + + + +
+
\ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.spec.ts new file mode 100644 index 0000000000..f2d53545a3 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AnalysisReportRunnerComponent } from './analysis-report-runner.component'; + +describe('AnalysisReportRunnerComponent', () => { + let component: AnalysisReportRunnerComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AnalysisReportRunnerComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AnalysisReportRunnerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.ts new file mode 100644 index 0000000000..e14601dd36 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.ts @@ -0,0 +1,49 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { SidePanelService } from 'frontend/packages/core/src/shared/services/side-panel.service'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { KubernetesAnalysisService, KubernetesAnalysisType } from '../../services/kubernetes.analysis.service'; +import { + KubernetesAnalysisInfoComponent, +} from '../../tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component'; + +@Component({ + selector: 'app-analysis-report-runner', + templateUrl: './analysis-report-runner.component.html', + styleUrls: ['./analysis-report-runner.component.scss'] +}) +export class AnalysisReportRunnerComponent implements OnInit { + + canShow$: Observable; + analyzers$: Observable; + @Input() kubeId: string; + @Input() namespace: string; + @Input() app: string; + + constructor( + public analysisService: KubernetesAnalysisService, + private sidePanelService: SidePanelService, + ) { + this.canShow$ = analysisService.hideAnalysis$.pipe(map(h => !h)); + } + + public runAnalysis(id: string) { + this.analysisService.run(id, this.kubeId, this.namespace, this.app); + } + + ngOnInit(): void { + if (this.namespace) { + this.analyzers$ = this.analysisService.namespaceAnalyzers$ + } else { + this.analyzers$ = this.analysisService.analyzers$; + } + } + + showAnalyzersInfo() { + this.sidePanelService.showModal(KubernetesAnalysisInfoComponent, { + analyzers$: this.analysisService.analyzers$ + }); + } + +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.html new file mode 100644 index 0000000000..4c13aaac65 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.html @@ -0,0 +1,22 @@ + +
+ + + + + + + + +
+
\ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.scss new file mode 100644 index 0000000000..2cd2769879 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.scss @@ -0,0 +1,3 @@ +.analysis-menu-divider { + margin: 4px 0; +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.spec.ts new file mode 100644 index 0000000000..d23be84d77 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.spec.ts @@ -0,0 +1,38 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { MDAppModule } from './../../../../core/md.module'; + +import { AnalysisReportSelectorComponent } from './analysis-report-selector.component'; +import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; +import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; +import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../kubernetes.testing.module'; + +describe('AnalysisReportSelectorComponent', () => { + let component: AnalysisReportSelectorComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AnalysisReportSelectorComponent ], + imports: [ + KubernetesBaseTestModules, + MDAppModule + ], + providers: [ + KubernetesAnalysisService, + KubernetesEndpointService, + KubeBaseGuidMock, + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AnalysisReportSelectorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.ts new file mode 100644 index 0000000000..ffb13fa10c --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.ts @@ -0,0 +1,87 @@ +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; +import * as moment from 'moment'; +import { Observable, Subscription } from 'rxjs'; +import { map, tap } from 'rxjs/operators'; + +import { safeUnsubscribe } from '../../../../../../core/src/core/utils.service'; +import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; +import { AnalysisReport } from '../../store/kube.types'; + +@Component({ + selector: 'app-analysis-report-selector', + templateUrl: './analysis-report-selector.component.html', + styleUrls: ['./analysis-report-selector.component.scss'] +}) +export class AnalysisReportSelectorComponent implements OnInit, OnDestroy { + + public selection = { title: 'None' }; + + public canShow$: Observable; + public analyzers$: Observable; + + @Input() endpoint; + @Input() path; + @Input() prompt = 'Overlay Analysis'; + @Input() allowNone = true; + @Input() autoSelect; + + @Output() selected = new EventEmitter(); + @Output() reportCount = new EventEmitter(); + + autoSelected = false; + + subs: Subscription[] = []; + + constructor(public analysisService: KubernetesAnalysisService) { + this.canShow$ = analysisService.hideAnalysis$.pipe(map(h => !h)); + } + + ngOnInit() { + this.analyzers$ = this.analysisService.getByPath(this.endpoint, this.path).pipe( + map(reports => { + const res = []; + if (this.allowNone) { + res.push({ title: 'None' }); + } + if (reports) { + reports.forEach(r => { + const c = { ...r }; + const title = c.type.substr(0, 1).toUpperCase() + c.type.substr(1); + const age = moment(c.created).fromNow(true); + c.title = `${title} (${age})`; + res.push(c); + }); + } + this.reportCount.next(res.length); + return res; + }), + tap(reports => { + if (!this.autoSelected && this.autoSelect && reports.length > 0) { + this.onSelected(reports[0]); + } + }) + ) + } + + + // Selection changed + public onSelected(d) { + this.selection = d; + if (!d.id) { + this.selected.emit(null); + } else { + this.selected.next(d); + } + } + + public refreshReports($event: MouseEvent) { + this.analysisService.getByPath(this.endpoint, this.path, true) + $event.preventDefault(); + $event.cancelBubble = true; + } + + ngOnDestroy() { + safeUnsubscribe(...this.subs) + } + +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.html new file mode 100644 index 0000000000..c527914e5a --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.html @@ -0,0 +1 @@ + diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.spec.ts new file mode 100644 index 0000000000..ed39ab0acf --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.spec.ts @@ -0,0 +1,29 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AnalysisReportViewerComponent } from './analysis-report-viewer.component'; +import { KubernetesBaseTestModules } from '../kubernetes.testing.module'; + +describe('AnalysisReportViewerComponent', () => { + let component: AnalysisReportViewerComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AnalysisReportViewerComponent ], + imports: [ + KubernetesBaseTestModules, + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AnalysisReportViewerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.ts new file mode 100644 index 0000000000..735ff9b6ce --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.ts @@ -0,0 +1,76 @@ +import { + Component, + ComponentFactoryResolver, + ComponentRef, + Input, + OnDestroy, + Type, + ViewChild, + ViewContainerRef, +} from '@angular/core'; + +import { AnalysisReport } from '../store/kube.types'; +import { KubeScoreReportViewerComponent } from './kube-score-report-viewer/kube-score-report-viewer.component'; +import { PopeyeReportViewerComponent } from './popeye-report-viewer/popeye-report-viewer.component'; + +export interface IReportViewer { + report: AnalysisReport; +} + +@Component({ + selector: 'app-analysis-report-viewer', + templateUrl: './analysis-report-viewer.component.html', + styleUrls: ['./analysis-report-viewer.component.scss'] +}) +export class AnalysisReportViewerComponent implements OnDestroy { + + // Component reference for the dynamically created auth form + @ViewChild('reportViewer', { read: ViewContainerRef, static: true }) + public container: ViewContainerRef; + private reportComponentRef: ComponentRef; + + private id: string; + + @Input('report') + set report(report: AnalysisReport) { + if (report === null || report.id === this.id) { + return; + } + this.id = report.id; + this.updateReport(report); + } + + constructor(private resolver: ComponentFactoryResolver) { } + + updateReport(report) { + switch (report.format) { + case 'popeye': + this.createComponent(PopeyeReportViewerComponent, report); + break; + case 'kubescore': + this.createComponent(KubeScoreReportViewerComponent, report); + break; + } + } + + // Dynamically create the component for the report type type + createComponent(component: Type, report: AnalysisReport) { + if (!component || !this.container) { + return; + } + + if (this.reportComponentRef) { + this.reportComponentRef.destroy(); + } + const factory = this.resolver.resolveComponentFactory(component); + this.reportComponentRef = this.container.createComponent(factory); + // this.reportComponentRef.instance.setReport(report); + this.reportComponentRef.instance.report = report; + } + + ngOnDestroy() { + if (this.reportComponentRef) { + this.reportComponentRef.destroy(); + } + } +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.html new file mode 100644 index 0000000000..a5dfac6b45 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.html @@ -0,0 +1,18 @@ +
+
{{ group._name }}
+
+
+
+ check_circle + info + warning + error + help_outline +
+
{{ check.Check.Name }}
+
+
+
{{ comment.Summary }}
+
+
+
\ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.scss new file mode 100644 index 0000000000..6c76b78b25 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.scss @@ -0,0 +1,23 @@ +.report { + &__group { + margin-bottom: 10px; + } + &__group-name { + font-weight: bold; + padding: 4px 0; + } + &__check { + align-items: center; + display: flex; + flex-direction: row; + margin-left: 10px; + } + &__icon { + margin-right: 4px; + } + &__comment { + display: list-item; + list-style: square; + margin-left: 58px; + } +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.spec.ts new file mode 100644 index 0000000000..a93f22f618 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.spec.ts @@ -0,0 +1,38 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { MDAppModule } from './../../../../core/md.module'; + +import { KubeScoreReportViewerComponent } from './kube-score-report-viewer.component'; +import { KubernetesBaseTestModules, KubeBaseGuidMock } from '../../kubernetes.testing.module'; +import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; +import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; + +describe('KubeScoreReportViewerComponent', () => { + let component: KubeScoreReportViewerComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ KubeScoreReportViewerComponent ], + imports: [ + KubernetesBaseTestModules, + MDAppModule + ], + providers: [ + KubernetesAnalysisService, + KubernetesEndpointService, + KubeBaseGuidMock, + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubeScoreReportViewerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.ts new file mode 100644 index 0000000000..0a53ea6921 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.ts @@ -0,0 +1,53 @@ +import { Component, OnInit } from '@angular/core'; + +import { IReportViewer } from '../analysis-report-viewer.component'; + +@Component({ + selector: 'app-kube-score-report-viewer', + templateUrl: './kube-score-report-viewer.component.html', + styleUrls: ['./kube-score-report-viewer.component.scss'] +}) +export class KubeScoreReportViewerComponent implements OnInit, IReportViewer { + + /* + Kube Score grading + + See: https://github.com/zegl/kube-score/blob/eca7bda47f5b3c523a0f41945cb1adda0a4e2e2e/scorecard/scorecard.go + GradeCritical Grade = 1 + GradeWarning Grade = 5 + GradeAlmostOK Grade = 7 + GradeAllOK Grade = 10 + */ + + report: any; + processed: any; + + constructor() { } + + ngOnInit() { + this.processed = []; + // Turn the report into an array + if (this.report) { + Object.keys(this.report.report).forEach(key => { + const filtered = this.filter(this.report.report[key]); + if (filtered.length > 0) { + this.processed.push({ + ...this.report.report[key], + _checks: filtered, + _name: key, + }); + } + }); + } + } + + public filter(report) { + const filtered = []; + report.Checks.forEach(r => { + if (r.Grade !== 10 && !r.Skipped) { + filtered.push(r); + } + }); + return filtered; + } +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.html new file mode 100644 index 0000000000..6b198581cf --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.html @@ -0,0 +1,60 @@ +
+
+
Report
+
+
Score
+
{{processed.popeye.score }}
+
+
+
Grade
+
{{processed.popeye.grade }}
+
+
+ +
+
+
+
{{ section.sanitizer }}
+
+
OK
+
{{section.tally.ok }}
+
+
+
Info
+
{{section.tally.info }}
+
+
+
Warning
+
{{section.tally.warning }}
+
+
+
Error
+
{{section.tally.error }}
+
+
+
Score
+
{{section.tally.score }}
+
+
+ + + + + + + +
{{ group.name }} +
+
+ check_circle + info + warning + error + help_outline +
+ {{ issue.message }} +
+
+
+
+
diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.scss new file mode 100644 index 0000000000..f70ee0c2c3 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.scss @@ -0,0 +1,43 @@ +.report { + &__report-header { + align-items: center; + display: flex; + margin-bottom: 8px; + } + &__header { + align-items: center; + display: flex; + } + &__title { + flex: 1; + } + &__stat { + display: flex; + flex-direction: column; + padding: 5px 12px; + &>div:first-child { + opacity: 0.8; + } + } + &__score { + flex: 0; + font-size: 20px; + } + &__grade { + flex: 0; + font-size: 20px; + } + &__table { + margin-left: 20px; + } + &__issue { + align-items: center; + display: flex; + } + &__icon { + padding-right: 4px; + } + &__table-name { + vertical-align: top; + } +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.spec.ts new file mode 100644 index 0000000000..ccbe5c5081 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.spec.ts @@ -0,0 +1,38 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { MDAppModule } from './../../../../core/md.module'; + +import { PopeyeReportViewerComponent } from './popeye-report-viewer.component'; +import { KubernetesBaseTestModules, KubeBaseGuidMock } from '../../kubernetes.testing.module'; +import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; +import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; + +describe('PopeyeReportViewerComponent', () => { + let component: PopeyeReportViewerComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PopeyeReportViewerComponent ], + imports: [ + KubernetesBaseTestModules, + MDAppModule + ], + providers: [ + KubernetesAnalysisService, + KubernetesEndpointService, + KubeBaseGuidMock, + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PopeyeReportViewerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.ts new file mode 100644 index 0000000000..1537046212 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.ts @@ -0,0 +1,59 @@ +import { Component, OnInit } from '@angular/core'; + +import { IReportViewer } from '../analysis-report-viewer.component'; + +@Component({ + selector: 'app-popeye-report-viewer', + templateUrl: './popeye-report-viewer.component.html', + styleUrls: ['./popeye-report-viewer.component.scss'] +}) +export class PopeyeReportViewerComponent implements OnInit, IReportViewer { + + report: any; + processed: any; + + ngOnInit() { + this.processed = this.apply(this.report); + } + + private apply(response) { + if (response) { + // In order to supplement the sanitizers with extra properties need to create new obj (see spread below and `reduce`) + response = { + ...response, + report: { + ...response.report, + popeye: { + ...response.report.popeye + } + } + } + // Make the response easier to render + response.report.popeye.sanitizers = response.report.popeye.sanitizers.reduce((ss, oldS) => { + const s = { ...oldS } + const groups = []; + let totalIssues = 0; + if (s.issues) { + Object.keys(s.issues).forEach(key => { + const issues = s.issues[key]; + totalIssues += issues.length; + if (issues.length > 0) { + groups.push({ + name: key, + issues + }); + } + }); + s.hide = totalIssues === 0; + } else { + s.hide = true; + } + s.groups = groups; + ss.push(s); + return ss; + }, []); + + return response.report; + } + } +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.html new file mode 100644 index 0000000000..0e7a5b9605 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.html @@ -0,0 +1,5 @@ + +
+ +
+
diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.scss new file mode 100644 index 0000000000..d7d483462b --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.scss @@ -0,0 +1,14 @@ +.alert { + &__info { + align-items: center; + display: flex; + margin: 4px 20px; + } + &__icon { + margin-right: 8px; + } + &__group { + font-weight: bold; + padding: 4px 0; + } +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.spec.ts new file mode 100644 index 0000000000..f78f26e839 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.spec.ts @@ -0,0 +1,34 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ResourceAlertPreviewComponent } from './resource-alert-preview.component'; +import { ResourceAlertViewComponent } from './resource-alert-view/resource-alert-view.component'; +import { SidePanelService } from 'frontend/packages/core/src/shared/services/side-panel.service'; +import { KubernetesBaseTestModules } from '../../kubernetes.testing.module'; + +describe('ResourceAlertPreviewComponent', () => { + let component: ResourceAlertPreviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ResourceAlertPreviewComponent, ResourceAlertViewComponent ], + imports: [ + KubernetesBaseTestModules, + ], + providers: [ + SidePanelService, + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ResourceAlertPreviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.ts new file mode 100644 index 0000000000..5abfac176e --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.ts @@ -0,0 +1,23 @@ +import { Component } from '@angular/core'; +import { PreviewableComponent } from 'frontend/packages/core/src/shared/previewable-component'; + +@Component({ + selector: 'app-resource-alert-preview', + templateUrl: './resource-alert-preview.component.html', + styleUrls: ['./resource-alert-preview.component.scss'] +}) +export class ResourceAlertPreviewComponent implements PreviewableComponent { + + title: string; + + resource: any; + alerts: any; + + constructor() { } + + setProps(props: { [key: string]: any; }): void { + this.resource = props.resource; + this.title = `${this.resource.kind} Alerts`; + } + +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.html new file mode 100644 index 0000000000..f9193d84c4 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.html @@ -0,0 +1,16 @@ +
+
+
{{group.name}}
+
+
+
+ info + warning + error + help_outline +
+ {{ alert.message }} +
+
+
+
diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.scss new file mode 100644 index 0000000000..d7d483462b --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.scss @@ -0,0 +1,14 @@ +.alert { + &__info { + align-items: center; + display: flex; + margin: 4px 20px; + } + &__icon { + margin-right: 8px; + } + &__group { + font-weight: bold; + padding: 4px 0; + } +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.spec.ts new file mode 100644 index 0000000000..0ffbf5f5ff --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.spec.ts @@ -0,0 +1,38 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { MDAppModule } from './../../../../../core/md.module'; + +import { ResourceAlertViewComponent } from './resource-alert-view.component'; +import { KubernetesBaseTestModules, KubeBaseGuidMock } from '../../../kubernetes.testing.module'; +import { KubernetesAnalysisService } from '../../../services/kubernetes.analysis.service'; +import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; + +describe('ResourceAlertViewComponent', () => { + let component: ResourceAlertViewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ResourceAlertViewComponent ], + imports: [ + KubernetesBaseTestModules, + MDAppModule + ], + providers: [ + KubernetesAnalysisService, + KubernetesEndpointService, + KubeBaseGuidMock, + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ResourceAlertViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.ts new file mode 100644 index 0000000000..15368b31e4 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.ts @@ -0,0 +1,46 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'app-resource-alert-view', + templateUrl: './resource-alert-view.component.html', + styleUrls: ['./resource-alert-view.component.scss'] +}) +export class ResourceAlertViewComponent { + + alertInfo; + + @Input() + set alerts(data: any) { + if (data) { + const alerts = data.alerts ? data.alerts : data; + this.alertInfo = this.normalize(alerts); + } + } + + @Input() showHeader = true; + + normalize(data) { + // Normalize the alerts into groups + const normalized = {}; + data.forEach(item => { + const path = item.namespace ? `${item.namespace}/${item.name}` : item.name; + if (!normalized[path]) { + normalized[path] = []; + } + normalized[path].push({ + ...item, + path + }); + }); + + const arr = []; + Object.keys(normalized).forEach(group => { + arr.push({ + name: group, + alerts: normalized[group] + }); + }); + return arr; + } + +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-catalog.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-catalog.ts index 816ab0bd47..e41dfd779d 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-catalog.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-catalog.ts @@ -4,6 +4,7 @@ import { } from '../../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; import { IFavoriteMetadata } from '../../../../store/src/types/user-favorites.types'; import { + AnalysisReportsActionBuilders, KubeDashboardActionBuilders, KubeDeploymentActionBuilders, KubeNamespaceActionBuilders, @@ -13,6 +14,7 @@ import { KubeStatefulSetsActionBuilders, } from './store/action-builders/kube.action-builders'; import { + AnalysisReport, KubernetesDeployment, KubernetesNamespace, KubernetesNode, @@ -34,6 +36,7 @@ export class KubeEntityCatalog { public namespace: StratosCatalogEntity; public service: StratosCatalogEntity public dashboard: StratosCatalogEntity; + public analysisReport: StratosCatalogEntity; } /** diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-factory.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-factory.ts index 1fed3c1b20..02637d2f5a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-factory.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-factory.ts @@ -22,6 +22,7 @@ export const kubernetesServicesEntityType = 'kubernetesService'; export const kubernetesStatefulSetsEntityType = 'kubernetesStatefulSet'; export const kubernetesDeploymentsEntityType = 'kubernetesDeployment'; export const kubernetesDashboardEntityType = 'kubernetesDashboard'; +export const analysisReportEntityType = 'analysisReport'; export const getKubeAppId = (object: KubernetesApp) => object.name; @@ -104,6 +105,13 @@ entityCache[kubernetesDashboardEntityType] = new KubernetesEntitySchema( { idAttribute: getGuidFromKubeDashboardObj } ); +// Analysis Reports - should not be bound to an endpoint +entityCache[analysisReportEntityType] = new KubernetesEntitySchema( + analysisReportEntityType, + {}, + { idAttribute: 'id' } +); + entityCache[metricEntityType] = new KubernetesEntitySchema(metricEntityType); export function addKubernetesEntitySchema(key: string, newSchema: EntitySchema) { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-generator.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-generator.ts index 1d8dc13dac..814f8a3e1f 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-generator.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-generator.ts @@ -24,6 +24,7 @@ import { KubernetesGKEAuthFormComponent } from './auth-forms/kubernetes-gke-auth import { KubeConfigRegistrationComponent } from './kube-config-registration/kube-config-registration.component'; import { kubeEntityCatalog } from './kubernetes-entity-catalog'; import { + analysisReportEntityType, KUBERNETES_ENDPOINT_TYPE, kubernetesDashboardEntityType, kubernetesDeploymentsEntityType, @@ -35,6 +36,8 @@ import { kubernetesStatefulSetsEntityType, } from './kubernetes-entity-factory'; import { + AnalysisReportsActionBuilders, + analysisReportsActionBuilders, KubeDashboardActionBuilders, kubeDashboardActionBuilders, KubeDeploymentActionBuilders, @@ -186,6 +189,7 @@ export function generateKubernetesEntities(): StratosBaseCatalogEntity[] { generateNamespacesEntity(endpointDefinition), generateServicesEntity(endpointDefinition), generateDashboardEntity(endpointDefinition), + generateAnalysisReportsEntity(endpointDefinition), generateMetricEntity(endpointDefinition), ...generateWorkloadsEntities(endpointDefinition) ]; @@ -283,6 +287,18 @@ function generateDashboardEntity(endpointDefinition: StratosEndpointExtensionDef return kubeEntityCatalog.dashboard; } +function generateAnalysisReportsEntity(endpointDefinition: StratosEndpointExtensionDefinition) { + const definition = { + type: analysisReportEntityType, + schema: kubernetesEntityFactory(analysisReportEntityType), + endpoint: endpointDefinition + }; + kubeEntityCatalog.analysisReport = new StratosCatalogEntity(definition, { + actionBuilders: analysisReportsActionBuilders + }); + return kubeEntityCatalog.analysisReport +} + function generateMetricEntity(endpointDefinition: StratosEndpointExtensionDefinition) { const definition: IStratosEntityDefinition = { type: metricEntityType, diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.html new file mode 100644 index 0000000000..650e5a18aa --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.html @@ -0,0 +1,13 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.spec.ts new file mode 100644 index 0000000000..5e5fdfaf98 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.spec.ts @@ -0,0 +1,46 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { MDAppModule } from './../../../../core/md.module'; + +import { KubernetesNamespaceAnalysisReportComponent } from './kubernetes-namespace-analysis-report.component'; +import { KubernetesBaseTestModules, KubeBaseGuidMock } from '../../kubernetes.testing.module'; +import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; +import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; +import { + AnalysisReportSelectorComponent +} from './../../analysis-report-viewer/analysis-report-selector/analysis-report-selector.component'; +import { AnalysisReportViewerComponent } from './../../analysis-report-viewer/analysis-report-viewer.component'; +import { KubernetesNamespaceService } from '../../services/kubernetes-namespace.service'; +import { TabNavService } from 'frontend/packages/core/tab-nav.service'; + +describe('KubernetesNamespaceAnalysisReportComponent', () => { + let component: KubernetesNamespaceAnalysisReportComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ KubernetesNamespaceAnalysisReportComponent, AnalysisReportSelectorComponent, AnalysisReportViewerComponent ], + imports: [ + KubernetesBaseTestModules, + MDAppModule + ], + providers: [ + KubernetesAnalysisService, + KubernetesEndpointService, + KubeBaseGuidMock, + KubernetesNamespaceService, + TabNavService, + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubernetesNamespaceAnalysisReportComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.ts new file mode 100644 index 0000000000..cdaa70db35 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.ts @@ -0,0 +1,50 @@ +import { Component } from '@angular/core'; +import { Subject } from 'rxjs'; + +import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; +import { KubernetesNamespaceService } from '../../services/kubernetes-namespace.service'; +import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; +import { AnalysisReport } from '../../store/kube.types'; + +@Component({ + selector: 'app-kubernetes-namespace-analysis-report-tab', + templateUrl: './kubernetes-namespace-analysis-report.component.html', + styleUrls: ['./kubernetes-namespace-analysis-report.component.scss'], + providers: [ + KubernetesAnalysisService + ] +}) +export class KubernetesNamespaceAnalysisReportComponent { + + public report$ = new Subject(); + + path: string; + + currentReport = null; + + endpointID: string; + + noReportsAvailable = false; + + constructor( + public analyzerService: KubernetesAnalysisService, + public endpointService: KubernetesEndpointService, + public kubeNamespaceService: KubernetesNamespaceService, + ) { + this.endpointID = this.endpointService.kubeGuid; + this.path = `${this.kubeNamespaceService.namespaceName}`; + this.report$.next(null); + } + + public analysisChanged(report) { + if (report.id !== this.currentReport) { + this.currentReport = report.id; + this.analyzerService.getByID(this.endpointID, report.id).subscribe(r => this.report$.next(r)); + } + } + + public onReportCount(count: number) { + this.noReportsAvailable = count === 0; + } + +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace.component.ts index 3847972a17..45ab5eb4a9 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace.component.ts @@ -8,6 +8,7 @@ import { BaseKubeGuid } from '../kubernetes-page.types'; import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; import { KubernetesNamespaceService } from '../services/kubernetes-namespace.service'; import { KubernetesService } from '../services/kubernetes.service'; +import { KubernetesAnalysisService } from '../services/kubernetes.analysis.service'; @Component({ selector: 'app-kubernetes-namespace', @@ -27,21 +28,20 @@ import { KubernetesService } from '../services/kubernetes.service'; }, KubernetesService, KubernetesEndpointService, - KubernetesNamespaceService + KubernetesNamespaceService, + KubernetesAnalysisService, ] }) export class KubernetesNamespaceComponent { - tabLinks = [ - { link: 'pods', label: 'Pods', icon: 'pod', iconFont: 'stratos-icons' }, - { link: 'services', label: 'Services', icon: 'service', iconFont: 'stratos-icons' } - ]; + tabLinks = []; public breadcrumbs$: Observable; constructor( public kubeEndpointService: KubernetesEndpointService, - public kubeNamespaceService: KubernetesNamespaceService + public kubeNamespaceService: KubernetesNamespaceService, + public analysisService: KubernetesAnalysisService, ) { this.breadcrumbs$ = kubeEndpointService.endpoint$.pipe( map(endpoint => ([{ @@ -51,5 +51,11 @@ export class KubernetesNamespaceComponent { }]) ) ); + + this.tabLinks = [ + { link: 'pods', label: 'Pods', icon: 'pod', iconFont: 'stratos-icons' }, + { link: 'services', label: 'Services', icon: 'service', iconFont: 'stratos-icons' }, + { link: 'analysis', label: 'Analysis', icon: 'assignment', hidden$: this.analysisService.hideAnalysis$ }, + ]; } } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.html index cb780e3041..c3ebbdd9e1 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.html @@ -13,7 +13,7 @@ {{ resource.age }}
- +
{{ label.name }}
{{ label.value }}
@@ -27,7 +27,10 @@
- + + + +
\ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.spec.ts index 5df03aea30..75fb79c994 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.spec.ts @@ -5,6 +5,7 @@ import { SidePanelService } from '../../../shared/services/side-panel.service'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../kubernetes.testing.module'; import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; import { KubernetesResourceViewerComponent } from './kubernetes-resource-viewer.component'; +import { ResourceAlertViewComponent } from './../analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component'; describe('KubernetesResourceViewerComponent', () => { let component: KubernetesResourceViewerComponent; @@ -12,7 +13,7 @@ describe('KubernetesResourceViewerComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [KubernetesResourceViewerComponent], + declarations: [KubernetesResourceViewerComponent, KubernetesResourceViewerComponent, ResourceAlertViewComponent], imports: KubernetesBaseTestModules, providers: [ KubernetesEndpointService, diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.ts index 871f4e9a17..4672958d66 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.ts @@ -6,10 +6,11 @@ import { filter, first, map, publishReplay, refCount, switchMap } from 'rxjs/ope import { EndpointsService } from '../../../../../core/src/core/endpoints.service'; import { PreviewableComponent } from '../../../../../core/src/shared/previewable-component'; import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; -import { BasicKubeAPIResource, KubeAPIResource } from '../store/kube.types'; +import { BasicKubeAPIResource, KubeAPIResource, KubeStatus } from '../store/kube.types'; export interface KubernetesResourceViewerConfig { title: string; + analysis?: any; resource$: Observable; resourceKind: string; } @@ -44,32 +45,42 @@ export class KubernetesResourceViewerComponent implements PreviewableComponent { public hasPodMetrics$: Observable; public podRouterLink$: Observable; + private analysis; + public alerts; + setProps(props: KubernetesResourceViewerConfig) { this.title = props.title; + this.analysis = props.analysis; this.resource$ = props.resource$.pipe( - map((item: any) => {// KubeAPIResource + filter(item => !!item), + map((item: (KubeAPIResource | KubeStatus)) => { const resource: KubernetesResourceViewerResource = {} as KubernetesResourceViewerResource; const newItem = {} as any; resource.raw = item; - Object.keys(item || []).forEach(k => { - if (k !== 'endpointId' && k !== 'releaseTitle' && k !== 'expandedStatus') { + if (k !== 'endpointId' && k !== 'releaseTitle' && k !== 'expandedStatus' && k !== '_metadata') { newItem[k] = item[k]; } }); resource.jsonView = newItem; - resource.age = moment(item.metadata.creationTimestamp).fromNow(true); - resource.creationTimestamp = item.metadata.creationTimestamp; - - resource.labels = []; - Object.keys(item.metadata.labels || []).forEach(labelName => { - resource.labels.push({ - name: labelName, - value: item.metadata.labels[labelName] + + const fallback = item['_metadata'] || {}; + + const ts = item.metadata ? item.metadata.creationTimestamp : fallback.creationTimestamp; + resource.age = moment(ts).fromNow(true); + resource.creationTimestamp = ts; + + if (item.metadata && item.metadata.labels) { + resource.labels = []; + Object.keys(item.metadata.labels || []).forEach(labelName => { + resource.labels.push({ + name: labelName, + value: item.metadata.labels[labelName] + }); }); - }); + } if (item.metadata && item.metadata.annotations) { resource.annotations = []; @@ -81,8 +92,13 @@ export class KubernetesResourceViewerComponent implements PreviewableComponent { }); } - resource.kind = item.kind || props.resourceKind; - resource.apiVersion = item.apiVersion || this.getVersionFromSelfLink(item.metadata.selfLink); + resource.kind = item['kind'] || fallback.kind || props.resourceKind; + resource.apiVersion = item['apiVersion'] || fallback.apiVersion || this.getVersionFromSelfLink(item.metadata['selfLink']); + + // Apply analysis if there is one - if this is a k8s resource (i.e. not a container) + if (item.metadata) { + this.applyAnalysis(resource); + } return resource; }), publishReplay(1), @@ -123,4 +139,14 @@ export class KubernetesResourceViewerComponent implements PreviewableComponent { return this.kubeEndpointService.kubeGuid || res.endpointId || res.metadata.kubeId; } + private applyAnalysis(resource) { + let id = (resource.kind || 'pod').toLowerCase(); + id = `${id}/${resource.raw.metadata.namespace}/${resource.raw.metadata.name}`; + if (this.analysis && this.analysis.alerts[id]) { + this.alerts = this.analysis.alerts[id]; + } else { + this.alerts = null; + } + } + } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.ts index a24a892eb2..fcd9c0ba5f 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.ts @@ -7,6 +7,7 @@ import { FavoritesConfigMapper } from '../../../../../store/src/favorite-config- import { UserFavoriteEndpoint } from '../../../../../store/src/types/user-favorites.types'; import { BaseKubeGuid } from '../kubernetes-page.types'; import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; +import { KubernetesAnalysisService } from '../services/kubernetes.analysis.service'; import { KubernetesService } from '../services/kubernetes.service'; @Component({ @@ -27,16 +28,12 @@ import { KubernetesService } from '../services/kubernetes.service'; }, KubernetesService, KubernetesEndpointService, + KubernetesAnalysisService, ] }) export class KubernetesTabBaseComponent implements OnInit { - tabLinks = [ - { link: 'summary', label: 'Summary', icon: 'kubernetes', iconFont: 'stratos-icons' }, - { link: 'nodes', label: 'Nodes', icon: 'node', iconFont: 'stratos-icons' }, - { link: 'namespaces', label: 'Namespaces', icon: 'namespace', iconFont: 'stratos-icons' }, - { link: 'pods', label: 'Pods', icon: 'pod', iconFont: 'stratos-icons' }, - ]; + tabLinks = []; public isFetching$: Observable; public favorite$: Observable; @@ -44,7 +41,19 @@ export class KubernetesTabBaseComponent implements OnInit { constructor( public kubeEndpointService: KubernetesEndpointService, - public favoritesConfigMapper: FavoritesConfigMapper) { } + public favoritesConfigMapper: FavoritesConfigMapper, + public analysisService: KubernetesAnalysisService, + ) { + this.tabLinks = [ + { link: 'summary', label: 'Summary', icon: 'kubernetes', iconFont: 'stratos-icons' }, + { link: 'analysis', label: 'Analysis', icon: 'assignment', hidden$: this.analysisService.hideAnalysis$ }, + { link: '-', label: 'Cluster' }, + { link: 'nodes', label: 'Nodes', icon: 'node', iconFont: 'stratos-icons' }, + { link: 'namespaces', label: 'Namespaces', icon: 'namespace', iconFont: 'stratos-icons' }, + { link: '-', label: 'Resources' }, + { link: 'pods', label: 'Pods', icon: 'pod', iconFont: 'stratos-icons' }, + ]; + } ngOnInit() { this.isFetching$ = this.kubeEndpointService.endpoint$.pipe( diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.module.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.module.ts index faa30bd980..9d5c1db436 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.module.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.module.ts @@ -4,10 +4,31 @@ import { NgxChartsModule } from '@swimlane/ngx-charts'; import { CoreModule } from '../../../../core/src/core/core.module'; import { SharedModule } from '../../../../core/src/shared/shared.module'; +import { + AnalysisReportRunnerComponent, +} from './analysis-report-viewer/analysis-report-runner/analysis-report-runner.component'; +import { + AnalysisReportSelectorComponent, +} from './analysis-report-viewer/analysis-report-selector/analysis-report-selector.component'; +import { AnalysisReportViewerComponent } from './analysis-report-viewer/analysis-report-viewer.component'; +import { + KubeScoreReportViewerComponent, +} from './analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component'; +import { PopeyeReportViewerComponent } from './analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component'; +import { + ResourceAlertPreviewComponent, +} from './analysis-report-viewer/resource-alert-preview/resource-alert-preview.component'; +import { + ResourceAlertViewComponent, +} from './analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component'; +import { KubeConsoleComponent } from './kube-terminal/kube-console.component'; import { KubedashConfigurationComponent, } from './kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component'; import { KubernetesDashboardTabComponent } from './kubernetes-dashboard/kubernetes-dashboard.component'; +import { + KubernetesNamespaceAnalysisReportComponent, +} from './kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component'; import { KubernetesNamespacePodsComponent, } from './kubernetes-namespace/kubernetes-namespace-pods/kubernetes-namespace-pods.component'; @@ -32,6 +53,7 @@ import { KubernetesResourceViewerComponent } from './kubernetes-resource-viewer/ import { KubernetesTabBaseComponent } from './kubernetes-tab-base/kubernetes-tab-base.component'; import { KubernetesRoutingModule } from './kubernetes.routing'; import { KubernetesComponent } from './kubernetes/kubernetes.component'; +import { AnalysisStatusCellComponent } from './list-types/analysis-status-cell/analysis-status-cell.component'; import { KubernetesLabelsCellComponent } from './list-types/kubernetes-labels-cell/kubernetes-labels-cell.component'; import { KubeNamespacePodCountComponent, @@ -87,14 +109,22 @@ import { PodMetricsComponent } from './pod-metrics/pod-metrics.component'; import { KubernetesEndpointService } from './services/kubernetes-endpoint.service'; import { KubernetesNodeService } from './services/kubernetes-node.service'; import { KubernetesService } from './services/kubernetes.service'; +import { + AnalysisInfoCardComponent, +} from './tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component'; +import { + KubernetesAnalysisInfoComponent, +} from './tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component'; +import { + KubernetesAnalysisReportComponent, +} from './tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component'; +import { KubernetesAnalysisTabComponent } from './tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component'; import { KubernetesNamespacesTabComponent } from './tabs/kubernetes-namespaces-tab/kubernetes-namespaces-tab.component'; import { KubernetesNodesTabComponent } from './tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component'; import { KubernetesPodsTabComponent } from './tabs/kubernetes-pods-tab/kubernetes-pods-tab.component'; import { KubernetesSummaryTabComponent } from './tabs/kubernetes-summary-tab/kubernetes-summary.component'; -import { KubeConsoleComponent } from './kube-terminal/kube-console.component'; /* tslint:disable:max-line-length */ - /* tslint:enable */ @NgModule({ @@ -115,6 +145,7 @@ import { KubeConsoleComponent } from './kube-terminal/kube-console.component'; KubernetesNamespacesTabComponent, KubernetesDashboardTabComponent, KubernetesSummaryTabComponent, + KubernetesAnalysisTabComponent, PodMetricsComponent, KubernetesNodeLinkComponent, KubernetesNodeIpsComponent, @@ -149,6 +180,18 @@ import { KubeConsoleComponent } from './kube-terminal/kube-console.component'; KubeServiceCardComponent, KubedashConfigurationComponent, KubernetesPodContainersComponent, + KubernetesAnalysisReportComponent, + KubernetesAnalysisInfoComponent, + AnalysisInfoCardComponent, + AnalysisReportViewerComponent, + PopeyeReportViewerComponent, + AnalysisReportSelectorComponent, + AnalysisReportRunnerComponent, + ResourceAlertPreviewComponent, + ResourceAlertViewComponent, + KubeScoreReportViewerComponent, + AnalysisStatusCellComponent, + KubernetesNamespaceAnalysisReportComponent, ], providers: [ KubernetesService, @@ -173,9 +216,22 @@ import { KubeConsoleComponent } from './kube-terminal/kube-console.component'; KubeServiceCardComponent, KubernetesResourceViewerComponent, KubernetesPodContainersComponent, + PopeyeReportViewerComponent, + KubeScoreReportViewerComponent, + AnalysisReportSelectorComponent, + ResourceAlertPreviewComponent, + AnalysisStatusCellComponent, ], exports: [ - KubernetesResourceViewerComponent + KubernetesResourceViewerComponent, + AnalysisReportViewerComponent, + PopeyeReportViewerComponent, + KubeScoreReportViewerComponent, + AnalysisReportSelectorComponent, + AnalysisReportRunnerComponent, + ResourceAlertPreviewComponent, + ResourceAlertViewComponent, + AnalysisStatusCellComponent, ] }) export class KubernetesModule { } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.routing.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.routing.ts index c7681161ce..35c15386f8 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.routing.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.routing.ts @@ -1,3 +1,4 @@ +import { KubernetesNamespaceAnalysisReportComponent } from './kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component'; import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; @@ -24,6 +25,11 @@ import { KubernetesPodsTabComponent } from './tabs/kubernetes-pods-tab/kubernete import { KubernetesSummaryTabComponent } from './tabs/kubernetes-summary-tab/kubernetes-summary.component'; import { KubedashConfigurationComponent } from './kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component'; import { KubeConsoleComponent } from './kube-terminal/kube-console.component'; +import { KubernetesAnalysisTabComponent } from './tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component'; +import { KubernetesAnalysisReportComponent } from './tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component'; +import { + KubernetesAnalysisInfoComponent +} from './tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component'; const kubernetes: Routes = [{ path: '', @@ -81,7 +87,10 @@ const kubernetes: Routes = [{ path: 'services', component: KubernetesNamespaceServicesComponent }, - + { + path: 'analysis', + component: KubernetesNamespaceAnalysisReportComponent + } ] }, { @@ -109,6 +118,18 @@ const kubernetes: Routes = [{ path: 'pods', component: KubernetesPodsTabComponent }, + { + path: 'analysis', + component: KubernetesAnalysisTabComponent + }, + { + path: 'analysis/report/:id', + component: KubernetesAnalysisReportComponent + }, + { + path: 'analysis/info', + component: KubernetesAnalysisInfoComponent + }, ] }, { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.store.module.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.store.module.ts index f0a76a9017..4b37169198 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.store.module.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.store.module.ts @@ -1,12 +1,14 @@ import { NgModule } from '@angular/core'; import { EffectsModule } from '@ngrx/effects'; +import { AnalysisEffects } from './store/analysis.effects'; import { KubernetesEffects } from './store/kubernetes.effects'; @NgModule({ imports: [ EffectsModule.forFeature([ - KubernetesEffects + AnalysisEffects, + KubernetesEffects, ]) ] }) diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-config.service.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-config.service.ts new file mode 100644 index 0000000000..9e68483e80 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-config.service.ts @@ -0,0 +1,123 @@ +import { Injectable, NgZone } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { ITableColumn } from 'frontend/packages/core/src/shared/components/list/list-table/table.types'; +import { + IListAction, + IListConfig, + IListMultiFilterConfig, + ListViewTypes, +} from 'frontend/packages/core/src/shared/components/list/list.component.types'; +import * as moment from 'moment'; +import { of } from 'rxjs'; + +import { ListView } from '../../../../../store/src/actions/list.actions'; +import { AppState } from '../../../../../store/src/app-state'; +import { defaultHelmKubeListPageSize } from '../../kubernetes/list-types/kube-helm-list-types'; +import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; +import { KubernetesAnalysisService } from '../services/kubernetes.analysis.service'; +import { AnalysisReport } from '../store/kube.types'; +import { AnalysisReportsDataSource } from './analysis-reports-list-source'; +import { AnalysisStatusCellComponent } from './analysis-status-cell/analysis-status-cell.component'; + +@Injectable() +export class AnalysisReportsListConfig implements IListConfig { + AppsDataSource: AnalysisReportsDataSource; + isLocal = true; + multiFilterConfigs: IListMultiFilterConfig[]; + + guid: string; + + columns: Array> = [ + { + columnId: 'name', headerCell: () => 'Name', + cellDefinition: { + getValue: (row: AnalysisReport) => row.name, + getLink: row => row.status === 'completed' ? `/kubernetes/${this.guid}/analysis/report/${row.id}` : null + }, + sort: { + type: 'sort', + orderKey: 'name', + field: 'name' + }, + cellFlex: '2', + }, + { + columnId: 'type', + headerCell: () => 'Type', + cellDefinition: { + getValue: (row: AnalysisReport) => row.type.charAt(0).toUpperCase() + row.type.substring(1) + }, + sort: { + type: 'sort', + orderKey: 'type', + field: 'type' + }, + cellFlex: '1' + }, + { + columnId: 'age', + headerCell: () => 'Age', + cellDefinition: { + getValue: (row: AnalysisReport) => { + return moment(row.created).fromNow(true); + } + }, + sort: { + type: 'sort', + orderKey: 'age', + field: 'created' + }, + cellFlex: '1' + }, + { + columnId: 'status', + headerCell: () => 'Status', + cellComponent: AnalysisStatusCellComponent, + sort: { + type: 'sort', + orderKey: 'status', + field: 'status' + }, + cellFlex: '1' + } + ]; + + pageSizeOptions = defaultHelmKubeListPageSize; + viewType = ListViewTypes.TABLE_ONLY; + defaultView = 'table' as ListView; + + enableTextFilter = true; + text = { + filter: 'Filter by Name', + noEntries: 'There are no Analysis Reports' + }; + + constructor( + store: Store, + kubeEndpointService: KubernetesEndpointService, + private analysisService: KubernetesAnalysisService, + ngZone: NgZone, + ) { + this.guid = kubeEndpointService.baseKube.guid; + this.AppsDataSource = new AnalysisReportsDataSource(store, this, kubeEndpointService, ngZone); + } + + private listActionDelete: IListAction = { + action: (item) => this.analysisService.delete(item.endpoint, item), + label: 'Delete', + icon: 'delete', + description: ``, + createEnabled: row$ => of(true) + }; + + private singleActions = [ + this.listActionDelete, + ]; + + getGlobalActions = () => []; + getMultiActions = () => []; + getSingleActions = () => this.singleActions; + getColumns = () => this.columns; + getDataSource = () => this.AppsDataSource; + getMultiFiltersConfigs = () => []; +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-source.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-source.ts new file mode 100644 index 0000000000..6ccd5e3d98 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-source.ts @@ -0,0 +1,63 @@ +import { NgZone } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { safeUnsubscribe } from 'frontend/packages/core/src/core/utils.service'; +import { ListDataSource } from 'frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source'; +import { IListConfig } from 'frontend/packages/core/src/shared/components/list/list.component.types'; +import { interval, Subscription } from 'rxjs'; +import { first, map } from 'rxjs/operators'; + +import { AppState } from '../../../../../store/src/app-state'; +import { isFetchingPage } from '../../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper'; +import { kubeEntityCatalog } from '../kubernetes-entity-catalog'; +import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; +import { GetAnalysisReports } from '../store/anaylsis.actions'; +import { AnalysisReport } from '../store/kube.types'; + +export class AnalysisReportsDataSource extends ListDataSource { + + + private analysisAction: GetAnalysisReports; + private pollInterval: Subscription; + + constructor( + store: Store, + listConfig: IListConfig, + endpointService: KubernetesEndpointService, + ngZone: NgZone, + ) { + const action = kubeEntityCatalog.analysisReport.actions.getMultiple(endpointService.baseKube.guid); + super({ + store, + action, + schema: action.entity[0], + getRowUniqueId: (entity: AnalysisReport) => action.entity[0].getId(entity), + paginationKey: action.paginationKey, + isLocal: true, + transformEntities: [{ type: 'filter', field: 'name' }], + listConfig, + }); + this.analysisAction = action; + + this.startPoll(store, ngZone); + } + + destroy() { + safeUnsubscribe(this.pollInterval); + } + + private startPoll(store: Store, ngZone: NgZone) { + ngZone.runOutsideAngular(() => this.pollInterval = interval(5000).subscribe(() => this.poll(store, ngZone))); + } + private poll(store: Store, ngZone: NgZone) { + kubeEntityCatalog.analysisReport.store.getPaginationMonitor(this.analysisAction.kubeGuid).pagination$.pipe( + first(), + map(isFetchingPage) + ).subscribe(isFetchingPage => { + if (!isFetchingPage) { + ngZone.run(() => { + store.dispatch(this.analysisAction); + }); + } + }) + } +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.html new file mode 100644 index 0000000000..6ff4563a31 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.html @@ -0,0 +1,8 @@ +
+ Running +
+
Completed
+
+
Error
+ warning +
\ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.scss new file mode 100644 index 0000000000..27dcfcf08b --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.scss @@ -0,0 +1,22 @@ +.status { + &__running { + align-items: center; + display: flex; + + &> mat-progress-spinner { + margin-right: 8px; + } + } + &__error { + align-items: center; + display: flex; + + mat-icon { + cursor: help; + font-size: 20px; + height: 20px; + margin-left: 8px; + width: 20px; + } + } +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.spec.ts new file mode 100644 index 0000000000..3c1fb10915 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.spec.ts @@ -0,0 +1,32 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MDAppModule } from './../../../../core/md.module'; +import { AnalysisStatusCellComponent } from './analysis-status-cell.component'; +import { + AnalysisReportSelectorComponent +} from './../../analysis-report-viewer/analysis-report-selector/analysis-report-selector.component'; + +describe('AnalysisStatusCellComponent', () => { + let component: AnalysisStatusCellComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AnalysisStatusCellComponent, AnalysisReportSelectorComponent ], + imports: [ + MDAppModule, + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AnalysisStatusCellComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.ts new file mode 100644 index 0000000000..acc74ee6d6 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; +import { TableCellCustom } from 'frontend/packages/core/src/shared/components/list/list.types'; + +@Component({ + selector: 'app-analysis-status-cell', + templateUrl: './analysis-status-cell.component.html', + styleUrls: ['./analysis-status-cell.component.scss'] +}) +export class AnalysisStatusCellComponent extends TableCellCustom { + + constructor() { + super(); + this.row = {}; + } + + } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.spec.ts index e70ac26fdf..68f0f28e01 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.spec.ts @@ -1,8 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BaseTestModules } from '../../../../../../core/test-framework/core-test.helper'; -import { KubernetesStatus } from '../../store/kube.types'; import { KubernetesLabelsCellComponent } from './kubernetes-labels-cell.component'; +import { KubernetesStatus } from '../../store/kube.types'; describe('KubernetesLabelsCellComponent', () => { let component: KubernetesLabelsCellComponent; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/analysis-report.types.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/analysis-report.types.ts new file mode 100644 index 0000000000..620640c6c9 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/analysis-report.types.ts @@ -0,0 +1,22 @@ +export enum ResourceAlertLevel { + OK = 0, + Info, + Warning, + Error, + Unknown, +} + +// We re-map an analysis reprot into a map of resource alerts that is better for us +// to overlay in the UI to show issues from reports +export interface ResourceAlert { + apiVersion?: string; + kind: string; + message: string; + namespace: string; + name: string; + level: ResourceAlertLevel; +} + +export interface ResourceAlertMap { + [key: string]: ResourceAlert[]; +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes.analysis.service.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes.analysis.service.ts new file mode 100644 index 0000000000..14749dc1c8 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes.analysis.service.ts @@ -0,0 +1,164 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Store } from '@ngrx/store'; +import { combineLatest, Observable } from 'rxjs'; +import { filter, first, map, pairwise, startWith, tap } from 'rxjs/operators'; + +import { SnackBarService } from '../../../../../core/src/shared/services/snackbar.service'; +import { ResetPaginationOfType } from '../../../../../store/src/actions/pagination.actions'; +import { AppState } from '../../../../../store/src/app-state'; +import { ListActionState, RequestInfoState } from '../../../../../store/src/reducers/api-request-reducer/types'; +import { kubeEntityCatalog } from '../kubernetes-entity-catalog'; +import { GetAnalysisReports } from '../store/anaylsis.actions'; +import { AnalysisReport } from '../store/kube.types'; +import { getHelmReleaseDetailsFromGuid } from '../workloads/store/workloads-entity-factory'; +import { KubernetesEndpointService } from './kubernetes-endpoint.service'; + +export interface KubernetesAnalysisType { + name: string; + id: string; + namespaceAware: boolean; + iconUrl?: string; + descriptionUrl?: string; +} + +@Injectable() +export class KubernetesAnalysisService { + kubeGuid: string; + + public analyzers$: Observable; + public namespaceAnalyzers$: Observable; + + public enabled$: Observable; + public hideAnalysis$: Observable; + + private action: GetAnalysisReports; + + constructor( + public kubeEndpointService: KubernetesEndpointService, + public activatedRoute: ActivatedRoute, + public store: Store, + private snackbarService: SnackBarService + ) { + this.kubeGuid = kubeEndpointService.kubeGuid || getHelmReleaseDetailsFromGuid(activatedRoute.snapshot.params.guid).endpointId; + + // Is the backend plugin available? + this.enabled$ = this.store.select('auth').pipe( + map(auth => auth.sessionData.plugins && auth.sessionData.plugins.analysis) + ); + + this.hideAnalysis$ = this.enabled$.pipe( + map(enabled => !enabled), + startWith(true), + ) + + const allEngines = { + popeye: + { + name: 'PopEye', + id: 'popeye', + namespaceAware: true, + // iconUrl: '/core/assets/custom/popeye.png', + // iconWidth: '80', + descriptionUrl: '/core/assets/custom/popeye.md' + }, + 'kube-score': + { + name: 'Kube Score', + id: 'kube-score', + namespaceAware: true, + // iconUrl: '/core/assets/custom/kubescore.png', + // iconWidth: '120', + descriptionUrl: '/core/assets/custom/kubescore.md' + } + // { + // name: 'Sonobuoy', + // id: 'sonobuoy', + // namespaceAware: false, + // iconUrl: '/core/assets/custom/sonobuoy.png', + // iconWidth: '70', + // descriptionUrl: '/core/assets/custom/sonobuoy.md' + // } + }; + + // Determine which analyzers are enabled + this.analyzers$ = this.store.select('auth').pipe( + filter(auth => !!auth.sessionData['plugin-config']), + map(auth => auth.sessionData['plugin-config'].analysisEngines), + map(engines => engines.split(',').map(e => allEngines[e.trim()]).filter(e => !!e)) + ); + + this.namespaceAnalyzers$ = combineLatest( + this.analyzers$, + this.enabled$ + ).pipe( + map(([a, enabled]) => { + if (!enabled) { + return null; + } + return a.filter(v => v.namespaceAware); + }) + ); + + this.action = kubeEntityCatalog.analysisReport.actions.getMultiple(this.kubeGuid) + } + + public delete(endpointID: string, item: { id: string }) { + return kubeEntityCatalog.analysisReport.api.delete(endpointID, item.id); + } + + public refresh() { + this.store.dispatch(new ResetPaginationOfType(this.action)); + } + + public run(id: string, endpointID: string, namespace?: string, app?: string): Observable { + const obs$ = kubeEntityCatalog.analysisReport.api.run(endpointID, id, namespace, app).pipe( + pairwise(), + filter(([oldE, newE]) => oldE.creating && !newE.creating), + map(([, newE]) => newE), + first() + ) + obs$.subscribe(() => { + const type = id.charAt(0).toUpperCase() + id.substring(1); + let msg; + if (app) { + msg = `${type} analysis started for workload '${app}'`; + } else if (namespace) { + msg = `${type} analysis started for namespace '${namespace}'`; + } else { + msg = `${type} analysis started for the Kubernetes cluster`; + } + this.snackbarService.showReturn(msg, ['kubernetes', endpointID, 'analysis'], 'View', 5000); + this.refresh(); + }); + return obs$; + } + + public getByID(endpoint: string, id: string, refresh = false): Observable { + if (refresh) { + kubeEntityCatalog.analysisReport.api.getById(endpoint, id) + } + + const entityService = kubeEntityCatalog.analysisReport.store.getById.getEntityService(endpoint, id); + return entityService.waitForEntity$.pipe( + map(e => e.entity), + tap(entity => { + if (!refresh && !entity.report) { + kubeEntityCatalog.analysisReport.api.getById(endpoint, id); + refresh = true; + } + }), + filter(entity => !!entity.report) + ); + } + + public getByPath(endpointID: string, path: string, refresh = false): Observable { + if (refresh) { + kubeEntityCatalog.analysisReport.api.getByPath(endpointID, path) + } + return kubeEntityCatalog.analysisReport.store.getByPath.getPaginationService(endpointID, path).entities$.pipe( + filter(entities => !!entities) + ); + } + +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes.service.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes.service.ts index 678b9eceed..09c03d154e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes.service.ts @@ -2,11 +2,11 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { map, shareReplay } from 'rxjs/operators'; -import { endpointEntityType, stratosEntityFactory } from '../../../../../store/src/helpers/stratos-entity-factory'; import { PaginationMonitor } from '../../../../../store/src/monitors/pagination-monitor'; -import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; +import { stratosEntityCatalog } from '../../../../../store/src/stratos-entity-catalog'; import { APIResource, EntityInfo } from '../../../../../store/src/types/api.types'; -import { endpointListKey, EndpointModel } from '../../../../../store/src/types/endpoint.types'; +import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; +import { KUBERNETES_ENDPOINT_TYPE } from '../kubernetes-entity-factory'; @Injectable() export class KubernetesService { @@ -14,18 +14,11 @@ export class KubernetesService { kubeEndpointsMonitor: PaginationMonitor; waitForAppEntity$: Observable>; - constructor( - private paginationMonitorFactory: PaginationMonitorFactory - ) { - // TODO: RC update with stratos entity catalog - this.kubeEndpointsMonitor = this.paginationMonitorFactory.create( - endpointListKey, - stratosEntityFactory(endpointEntityType), - true - ); + constructor() { + this.kubeEndpointsMonitor = stratosEntityCatalog.endpoint.store.getAll.getPaginationMonitor() this.kubeEndpoints$ = this.kubeEndpointsMonitor.currentPage$.pipe( - map(endpoints => endpoints.filter(e => e.cnsi_type === 'k8s')), + map(endpoints => endpoints.filter(e => e.cnsi_type === KUBERNETES_ENDPOINT_TYPE)), shareReplay(1) ); } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubescore-report.helper.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubescore-report.helper.ts new file mode 100644 index 0000000000..b07c0e7abf --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubescore-report.helper.ts @@ -0,0 +1,57 @@ +import { ResourceAlert, ResourceAlertLevel, ResourceAlertMap } from './analysis-report.types'; + +export class KubeScoreReportHelper { + + constructor(public report: any) {} + + public map() { + if (!this.report.report) { + return; + } + + const kubescore = this.report.report; + // Go through the report and re-map + const result = {} as ResourceAlertMap; + + Object.keys(kubescore).forEach(key => { + const item = kubescore[key]; + let id = item.TypeMeta.kind.toLowerCase(); + id = `${id}/${item.ObjectMeta.namespace}/${item.ObjectMeta.name}`; + + item.Checks.forEach(check => { + if (check.Grade !== 10 && !check.Skipped) { + // Add an alert for each comment + check.Comments.forEach(comment => { + // Include this comment + const alert = { + kind: item.TypeMeta.kind.toLowerCase(), + namespace: item.ObjectMeta.namespace, + name: item.ObjectMeta.name, + message: comment.Summary, + level: this.convertMessageLevel(check.Grade) + } as ResourceAlert; + if (!result[id]) { + result[id] = [] as ResourceAlert[]; + } + result[id].push(alert); + }); + } + }); + }); + this.report.alerts = result; + } + private convertMessageLevel(level: number): ResourceAlertLevel { + switch (level) { + case 10: + return ResourceAlertLevel.OK; + case 7: + return ResourceAlertLevel.Info; + case 5: + return ResourceAlertLevel.Warning; + case 1: + return ResourceAlertLevel.Error; + default: + return ResourceAlertLevel.Unknown; + } + } +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/popeye-report.helper.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/popeye-report.helper.ts new file mode 100644 index 0000000000..12de4c61c1 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/popeye-report.helper.ts @@ -0,0 +1,69 @@ +import { ResourceAlert, ResourceAlertLevel, ResourceAlertMap } from './analysis-report.types'; + +export class PopeyeReportHelper { + + constructor(public report: any) { } + + // Map the report to the alert format + public map() { + if (!this.report.report || !this.report.report.popeye) { + return; + } + + const popeye = this.report.report.popeye; + // Go through the report and re-map + const result = {} as ResourceAlertMap; + popeye.sanitizers.forEach(s => { + // We just care about issues + const resourceType = s.sanitizer; + if (s.issues) { + Object.keys(s.issues).forEach(resourcePath => { + const issues = s.issues[resourcePath]; + issues.forEach(issue => { + // Level must be greater than 0 (OK) + if (issue.level > 0) { + let namespace; + let name; + if (resourcePath.indexOf('/') !== -1) { + // Has a namespace + namespace = resourcePath.split('/')[0]; + name = resourcePath.split('/')[1]; + } else { + name = resourcePath; + namespace = ''; + } + const alert = { + kind: resourceType, + namespace, + name, + message: issue.message, + level: this.convertMessageLevel(issue.level) + } as ResourceAlert; + const id = `${resourceType}/${resourcePath}`; + if (!result[id]) { + result[id] = [] as ResourceAlert[]; + } + result[id].push(alert); + } + }); + }); + } + }); + + this.report.alerts = result; + } + private convertMessageLevel(level: number): ResourceAlertLevel { + switch (level) { + case 0: + return ResourceAlertLevel.OK; + case 1: + return ResourceAlertLevel.Info; + case 2: + return ResourceAlertLevel.Warning; + case 3: + return ResourceAlertLevel.Error; + default: + return ResourceAlertLevel.Unknown; + } + } +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/route.helper.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/route.helper.ts new file mode 100644 index 0000000000..ccbb8f7bc1 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/route.helper.ts @@ -0,0 +1,11 @@ +import { ActivatedRoute } from '@angular/router'; + +export function getParentURL(route: ActivatedRoute, removeLastParts = 1): string { + const reducer = (a: string, v) => { + const p = v.url.join('/'); + return p.length > 0 ? `${a}/${p}` : a; + }; + let res = route.snapshot.pathFromRoot.reduce(reducer, '').split('/'); + res.splice(-removeLastParts, removeLastParts); + return res.join('/'); +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/action-builders/kube.action-builders.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/action-builders/kube.action-builders.ts index 58ad75827c..362c418497 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/action-builders/kube.action-builders.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/action-builders/kube.action-builders.ts @@ -2,6 +2,13 @@ import { OrchestratedActionBuilders, } from '../../../../../../store/src/entity-catalog/action-orchestrator/action-orchestrator'; import { GetHelmReleasePods, GetHelmReleaseServices } from '../../workloads/store/workloads.actions'; +import { + DeleteAnalysisReport, + GetAnalysisReportById, + GetAnalysisReports, + GetAnalysisReportsByPath, + RunAnalysisReport, +} from '../anaylsis.actions'; import { CreateKubernetesNamespace, GeKubernetesDeployments, @@ -145,3 +152,35 @@ export interface KubeDashboardActionBuilders extends OrchestratedActionBuilders export const kubeDashboardActionBuilders: KubeDashboardActionBuilders = { get: (kubeGuid: string) => new GetKubernetesDashboard(kubeGuid) } + +export interface AnalysisReportsActionBuilders extends OrchestratedActionBuilders { + getMultiple: ( + kubeGuid: string + ) => GetAnalysisReports; + getById: ( + kubeGuid: string, + id: string, + ) => GetAnalysisReportById; + getByPath: ( + kubeGuid: string, + path: string, + ) => GetAnalysisReportsByPath; + delete: ( + kubeGuid: string, + id: string, + ) => DeleteAnalysisReport; + run: ( + kubeGuid: string, + id: string, + namespace?: string, + app?: string + ) => RunAnalysisReport; +} + +export const analysisReportsActionBuilders: AnalysisReportsActionBuilders = { + getMultiple: (kubeGuid: string) => new GetAnalysisReports(kubeGuid), + getById: (kubeGuid: string, id: string) => new GetAnalysisReportById(kubeGuid, id), + getByPath: (kubeGuid: string, path: string) => new GetAnalysisReportsByPath(kubeGuid, path), + delete: (kubeGuid: string, id: string) => new DeleteAnalysisReport(kubeGuid, id), + run: (kubeGuid: string, id: string, namespace?: string, app?: string) => new RunAnalysisReport(kubeGuid, id, namespace, app) +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/analysis.effects.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/analysis.effects.ts new file mode 100644 index 0000000000..4f8a7d24c2 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/analysis.effects.ts @@ -0,0 +1,264 @@ +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Store } from '@ngrx/store'; +import { catchError, flatMap, mergeMap } from 'rxjs/operators'; + +import { environment } from '../../../../../core/src/environments/environment'; +import { AppState } from '../../../../../store/src/app-state'; +import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; +import { ApiRequestTypes } from '../../../../../store/src/reducers/api-request-reducer/request-helpers'; +import { NormalizedResponse } from '../../../../../store/src/types/api.types'; +import { + StartRequestAction, + WrapperRequestActionFailed, + WrapperRequestActionSuccess, +} from '../../../../../store/src/types/request.types'; +import { KubeScoreReportHelper } from '../services/kubescore-report.helper'; +import { PopeyeReportHelper } from '../services/popeye-report.helper'; +import { + DELETE_ANALYSIS_REPORT_TYPES, + DeleteAnalysisReport, + GET_ANALYSIS_REPORT_BY_ID_TYPES, + GET_ANALYSIS_REPORTS_BY_PATH_TYPES, + GET_ANALYSIS_REPORTS_TYPES, + GetAnalysisReportById, + GetAnalysisReports, + GetAnalysisReportsByPath, + RUN_ANALYSIS_REPORT_TYPES, + RunAnalysisReport, +} from './anaylsis.actions'; +import { AnalysisReport } from './kube.types'; + +@Injectable() +export class AnalysisEffects { + proxyAPIVersion = environment.proxyAPIVersion; + + constructor( + private http: HttpClient, + private actions$: Actions, + private store: Store, + ) { } + + @Effect() + fetchAnalysisReports$ = this.actions$.pipe( + ofType(GET_ANALYSIS_REPORTS_TYPES[0]), + flatMap(action => { + this.store.dispatch(new StartRequestAction(action)); + const headers = new HttpHeaders({}); + const requestArgs = { + headers + }; + const url = `/pp/${this.proxyAPIVersion}/analysis/reports/${action.kubeGuid}`; + const entityKey = entityCatalog.getEntityKey(action); + return this.http.get(url, requestArgs).pipe( + mergeMap(response => { + const res: NormalizedResponse = { + entities: { [entityKey]: {} }, + result: [] + }; + const items: any = response as Array; + items.forEach(item => { + const id = item.id; + res.entities[entityKey][id] = item; + res.result.push(id); + }); + return [new WrapperRequestActionSuccess(res, action)]; + }), + catchError(error => [ + new WrapperRequestActionFailed(error.message, action, 'fetch', { + endpointIds: [action.kubeGuid], + url: error.url || url, + eventCode: error.status ? error.status + '' : '500', + message: 'Kubernetes Analysis Report request error', + error + }) + ]) + ); + }) + ); + + @Effect() + fetchAnalysisReportById$ = this.actions$.pipe( + ofType(GET_ANALYSIS_REPORT_BY_ID_TYPES[0]), + flatMap(action => { + this.store.dispatch(new StartRequestAction(action)); + + const url = `/pp/${this.proxyAPIVersion}/analysis/reports/${action.kubeGuid}/${action.guid}`; + const headers = new HttpHeaders({}); + const requestArgs = { + headers, + }; + const entityKey = entityCatalog.getEntityKey(action); + + return this.http.get(url, requestArgs).pipe( + mergeMap(response => { + this.processReport(response); + + const res: NormalizedResponse = { + entities: { + [entityKey]: { + [action.guid]: response + } + }, + result: [action.guid] + }; + return [new WrapperRequestActionSuccess(res, action)]; + }), + catchError(error => [ + new WrapperRequestActionFailed(error.message, action, 'fetch', { + endpointIds: [action.kubeGuid], + url: error.url || url, + eventCode: error.status ? error.status + '' : '500', + message: 'Kubernetes Analysis Report request error', + error + }) + ]) + ); + }) + ); + + @Effect() + fetchAnalysisReportByPath$ = this.actions$.pipe( + ofType(GET_ANALYSIS_REPORTS_BY_PATH_TYPES[0]), + flatMap(action => { + this.store.dispatch(new StartRequestAction(action)); + + const url = `/pp/${this.proxyAPIVersion}/analysis/completed/${action.kubeGuid}/${action.path}`; + const headers = new HttpHeaders({}); + const requestArgs = { + headers, + }; + const schema = action.entity[0]; + const entityKey = entityCatalog.getEntityKey(action); + return this.http.get(url, requestArgs).pipe( + mergeMap((response: AnalysisReport[]) => { + const res: NormalizedResponse = { + entities: { + [entityKey]: {} + }, + result: [] + }; + response.forEach(report => { + const guid = schema.getId(report); + res.entities[entityKey][guid] = report; + res.result.push(guid); + }) + return [new WrapperRequestActionSuccess(res, action)]; + }), + catchError(error => [ + new WrapperRequestActionFailed(error.message, action, 'fetch', { + endpointIds: [action.kubeGuid], + url: error.url || url, + eventCode: error.status ? error.status + '' : '500', + message: 'Kubernetes Analysis Report request error', + error + }) + ]) + ); + }) + ); + + @Effect() + deleteAnalysisReport$ = this.actions$.pipe( + ofType(DELETE_ANALYSIS_REPORT_TYPES[0]), + flatMap(action => { + const type: ApiRequestTypes = 'delete'; + + this.store.dispatch(new StartRequestAction(action, type)); + + const url = `/pp/${this.proxyAPIVersion}/analysis/reports`; + const headers = new HttpHeaders({}); + const requestArgs = { + headers, + body: [action.guid] + }; + + return this.http.delete(url, requestArgs).pipe( + mergeMap(() => { + const res: NormalizedResponse = { + entities: { [entityCatalog.getEntityKey(action)]: {} }, + result: [] + }; + return [new WrapperRequestActionSuccess(res, action, type)]; + }), + catchError(error => [ + new WrapperRequestActionFailed(error.message, action, type, { + endpointIds: [action.kubeGuid], + url: error.url || url, + eventCode: error.status ? error.status + '' : '500', + message: 'Kubernetes Analysis Report request error', + error + }) + ]) + ); + }) + ); + + @Effect() + runAnalysisReport$ = this.actions$.pipe( + ofType(RUN_ANALYSIS_REPORT_TYPES[0]), + flatMap(action => { + const type: ApiRequestTypes = 'create'; + + this.store.dispatch(new StartRequestAction(action, type)); + + const { namespace, app } = action; + const body = { + namespace, + app, + }; + + // Start an Analysis + const url = `/pp/${this.proxyAPIVersion}/analysis/run/${action.guid}/${action.kubeGuid}`; + const headers = new HttpHeaders({}); + const requestArgs = { + headers, + }; + + return this.http.post(url, body, requestArgs).pipe( + mergeMap((response: AnalysisReport) => { + const res: NormalizedResponse = { + entities: { [entityCatalog.getEntityKey(action)]: { [response.id]: response } }, + result: [response.id] + }; + return [new WrapperRequestActionSuccess(res, action, type)]; + }), + catchError(error => [ + new WrapperRequestActionFailed(error.message, action, type, { + endpointIds: [action.kubeGuid], + url: error.url || url, + eventCode: error.status ? error.status + '' : '500', + message: 'Kubernetes Analysis Report request error', + error + }) + ]) + ); + + }) + ); + + + private processReport(report: any) { + // Check the path of the report + if (report.path.split('/').length !== 2) { + return; + } + + switch (report.format) { + case 'popeye': + const helper = new PopeyeReportHelper(report); + helper.map(); + break; + case 'kubescore': + const kubeScoreHelper = new KubeScoreReportHelper(report); + kubeScoreHelper.map(); + break; + default: + console.warn('Do not know how to handle this report type: ', report.format); + break; + } + } + + +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/anaylsis.actions.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/anaylsis.actions.ts new file mode 100644 index 0000000000..e9da8d7a27 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/anaylsis.actions.ts @@ -0,0 +1,69 @@ +import { getActions } from '../../../../../store/src/actions/action.helper'; +import { PaginatedAction } from '../../../../../store/src/types/pagination.types'; +import { analysisReportEntityType, KUBERNETES_ENDPOINT_TYPE, kubernetesEntityFactory } from '../kubernetes-entity-factory'; +import { KubeAction, KubePaginationAction, KubeSingleEntityAction } from './kubernetes.actions'; + +export const GET_ANALYSIS_REPORTS_TYPES = getActions('ANALYSIS', 'Get reports'); +export const GET_ANALYSIS_REPORT_BY_ID_TYPES = getActions('ANALYSIS', 'Get report by id'); +export const GET_ANALYSIS_REPORTS_BY_PATH_TYPES = getActions('ANALYSIS', 'Get report by path'); +export const DELETE_ANALYSIS_REPORT_TYPES = getActions('ANALYSIS', 'Delete report'); +export const RUN_ANALYSIS_REPORT_TYPES = getActions('ANALYSIS', 'Run'); + +abstract class AnalysisAction implements KubeAction { + constructor(public kubeGuid: string, public actions: string[]) { + this.type = this.actions[0]; + } + endpointType = KUBERNETES_ENDPOINT_TYPE; + entityType = analysisReportEntityType; + entity = [kubernetesEntityFactory(analysisReportEntityType)]; + type: string; +} +abstract class AnalysisPaginationAction extends AnalysisAction implements KubePaginationAction, PaginatedAction { + flattenPagination = true; + constructor(kubeGuid: string, actionTypes: string[], public paginationKey: string) { + super(kubeGuid, actionTypes); + } +} +abstract class AnalysisSingleEntityAction extends AnalysisAction implements KubeSingleEntityAction { + constructor(kubeGuid: string, actionTypes: string[], public guid: string) { + super(kubeGuid, actionTypes); + } +} + + +/** + * Get the analysis reports for the given endpoint ID + */ +export class GetAnalysisReports extends AnalysisPaginationAction { + constructor(public kubeGuid: string) { + super(kubeGuid, GET_ANALYSIS_REPORTS_TYPES, kubeGuid) + } + initialParams = { + 'order-direction': 'asc', + 'order-direction-field': 'age', + }; +} + +export class GetAnalysisReportById extends AnalysisSingleEntityAction { + constructor(kubeGuid: string, id: string) { + super(kubeGuid, GET_ANALYSIS_REPORT_BY_ID_TYPES, id); + } +} + +export class GetAnalysisReportsByPath extends AnalysisPaginationAction { + constructor(kubeGuid: string, public path: string) { + super(kubeGuid, GET_ANALYSIS_REPORTS_BY_PATH_TYPES, path); + } +} + +export class DeleteAnalysisReport extends AnalysisSingleEntityAction { + constructor(kubeGuid: string, id: string) { + super(kubeGuid, DELETE_ANALYSIS_REPORT_TYPES, id); + } +} + +export class RunAnalysisReport extends AnalysisSingleEntityAction { + constructor(kubeGuid: string, id: string, public namespace?: string, public app?: string) { + super(kubeGuid, RUN_ANALYSIS_REPORT_TYPES, id); + } +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kube.getIds.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kube.getIds.ts index f08ff16fb3..898062b926 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kube.getIds.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kube.getIds.ts @@ -14,7 +14,7 @@ const deliminate = (...args: string[]) => args.join('_:_'); const debugMissingKubeId = (entity: BasicKubeAPIResource, func: (...args: string[]) => string, ...args: string[]) => { if (!environment.production && (!entity.metadata || !entity.metadata.kubeId)) { - console.log(`Kube entity does not have a kubeId, this is probably a bug: `, entity); + console.warn(`Kube entity does not have a kubeId, this is probably a bug: `, entity); } return func(...args); } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kube.types.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kube.types.ts index b6b2721e86..3a4a5e60b9 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kube.types.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kube.types.ts @@ -484,3 +484,30 @@ export interface HostPath { export interface Item { key: string; } + +export interface KubeStatus { + kind: string; + apiVersion: string; + metadata: Metadata; + status: string; + message: string; + reason: string; + details: {} + code: number +} + +// Analysis Reports + +export interface AnalysisReport { + id: string; + endpoint: string; + type: string; + name: string; + path: string; + created: Date; + read: boolean; + status: string; + duration: number; + report?: any; + title?: string; +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kubernetes.actions.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kubernetes.actions.ts index cec88d6f34..b35b07a92b 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kubernetes.actions.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kubernetes.actions.ts @@ -81,7 +81,6 @@ export const GET_KUBE_DASHBOARD = '[KUBERNETES Endpoint] Get K8S Dashboard Info' export const GET_KUBE_DASHBOARD_SUCCESS = '[KUBERNETES Endpoint] Get Dashboard Success'; export const GET_KUBE_DASHBOARD_FAILURE = '[KUBERNETES Endpoint] Get Dashboard Failure'; - const defaultSortParams = { 'order-direction': 'desc' as SortDirection, 'order-direction-field': 'name' @@ -371,5 +370,3 @@ export class FetchKubernetesChartMetricsAction extends MetricsChartAction { ); } } - - diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kubernetes.effects.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kubernetes.effects.ts index 5f47b4a890..e839283471 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kubernetes.effects.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kubernetes.effects.ts @@ -127,7 +127,7 @@ export class KubernetesEffects { endpointIds: [action.kubeGuid], url: error.url || url, eventCode: error.status ? error.status + '' : '500', - message: 'Kubernetes API request error', + message: 'Kubernetes Dashboard request error', error }) ])); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.html new file mode 100644 index 0000000000..a695a022d0 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.html @@ -0,0 +1,4 @@ +
+
+
+
diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.scss new file mode 100644 index 0000000000..0f224e7f96 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.scss @@ -0,0 +1,21 @@ +.info { + + &__card { + display: flex; + } + + &__card-icon { + flex: 0 0 140px; + width: 140px; + &>img { + max-width: 120px; + } + margin-right: 10px; + text-align: center; + } + + &__card-text { + flex: 1; + } + +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.spec.ts new file mode 100644 index 0000000000..88894b763d --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.spec.ts @@ -0,0 +1,29 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AnalysisInfoCardComponent } from './analysis-info-card.component'; + +describe('AnalysisInfoCardComponent', () => { + let component: AnalysisInfoCardComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AnalysisInfoCardComponent ], + imports: [ + HttpClientTestingModule, + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AnalysisInfoCardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.theme.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.theme.scss new file mode 100644 index 0000000000..06983eb900 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.theme.scss @@ -0,0 +1,21 @@ + +@mixin kube-analysis-card-theme($theme, $app-theme) { + + .info__card { + + P { + font-size: 14px; + line-height: 1.24em; + } + + h2 { + font-size: 18px; + padding: 0; + } + + h2 { + font-size: 16px; + padding: 0; + } + } +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.ts new file mode 100644 index 0000000000..56fcba9997 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.ts @@ -0,0 +1,55 @@ +import { HttpClient } from '@angular/common/http'; +import { Component, Input } from '@angular/core'; +import * as markdown from 'marked'; +import { Observable, of } from 'rxjs'; +import { catchError, map } from 'rxjs/operators'; + +@Component({ + selector: 'app-analysis-info-card', + templateUrl: './analysis-info-card.component.html', + styleUrls: ['./analysis-info-card.component.scss'] +}) +export class AnalysisInfoCardComponent { + + public loading = true; + public content$: Observable; + private renderer = new markdown.Renderer(); + + public mAanalyzer = {}; + + @Input() set analyzer(analyzer: any) { + if (analyzer && analyzer.descriptionUrl) { + this.content$ = this.getDescription(analyzer.descriptionUrl); + } + this.mAanalyzer = analyzer; + } + + get analyzer() { + return this.mAanalyzer; + } + + constructor(private http: HttpClient) { + this.renderer.link = (href, title, text) => `
${text}`; + this.renderer.code = (text: string) => `${text}`; + } + + private getDescription(url): Observable { + return this.http.get(url, { responseType: 'text' }).pipe( + map(resp => { + this.loading = false; + return markdown(resp, { + renderer: this.renderer + }); + }), + catchError((error) => { + this.loading = false; + if (error.status === 404) { + return of('

Unable to load description for this Analyzer

'); + } else { + return of('

An error occurred retrieving description for this Analyzer

'); + } + } + )); + } + +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.html new file mode 100644 index 0000000000..b88ffd84a2 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.html @@ -0,0 +1,7 @@ + +
+
+ +
+
+
diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.scss new file mode 100644 index 0000000000..53951f99ae --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.scss @@ -0,0 +1,5 @@ +.info__title { + padding: 0; + margin: 0 0 30px 0; + font-size: 22px; +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.spec.ts new file mode 100644 index 0000000000..0a35b86689 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.spec.ts @@ -0,0 +1,38 @@ + +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { KubernetesAnalysisInfoComponent } from './kubernetes-analysis-info.component'; +import { AnalysisInfoCardComponent } from './analysis-info-card/analysis-info-card.component'; +import { KubernetesBaseTestModules, KubeBaseGuidMock } from '../../../kubernetes.testing.module'; +import { KubernetesAnalysisService } from '../../../services/kubernetes.analysis.service'; +import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; + +describe('KubernetesAnalysisInfoComponent', () => { + let component: KubernetesAnalysisInfoComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ KubernetesAnalysisInfoComponent, AnalysisInfoCardComponent ], + imports: [ + KubernetesBaseTestModules, + ], + providers: [ + KubernetesAnalysisService, + KubernetesEndpointService, + KubeBaseGuidMock, + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubernetesAnalysisInfoComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.ts new file mode 100644 index 0000000000..76cce96ccc --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.ts @@ -0,0 +1,23 @@ +import { Component } from '@angular/core'; +import { PreviewableComponent } from 'frontend/packages/core/src/shared/previewable-component'; +import { Observable } from 'rxjs'; + +import { KubernetesAnalysisService } from '../../../services/kubernetes.analysis.service'; + + +@Component({ + selector: 'app-kubernetes-analysis-info', + templateUrl: './kubernetes-analysis-info.component.html', + styleUrls: ['./kubernetes-analysis-info.component.scss'], + providers: [ + KubernetesAnalysisService + ] +}) +export class KubernetesAnalysisInfoComponent implements PreviewableComponent { + + analyzers$: Observable; + + setProps(props: { [key: string]: any; }) { + this.analyzers$ = props.analyzers$; + } +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.html new file mode 100644 index 0000000000..25926fc569 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.scss new file mode 100644 index 0000000000..f70ee0c2c3 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.scss @@ -0,0 +1,43 @@ +.report { + &__report-header { + align-items: center; + display: flex; + margin-bottom: 8px; + } + &__header { + align-items: center; + display: flex; + } + &__title { + flex: 1; + } + &__stat { + display: flex; + flex-direction: column; + padding: 5px 12px; + &>div:first-child { + opacity: 0.8; + } + } + &__score { + flex: 0; + font-size: 20px; + } + &__grade { + flex: 0; + font-size: 20px; + } + &__table { + margin-left: 20px; + } + &__issue { + align-items: center; + display: flex; + } + &__icon { + padding-right: 4px; + } + &__table-name { + vertical-align: top; + } +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.spec.ts new file mode 100644 index 0000000000..f1f2d4fd8c --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.spec.ts @@ -0,0 +1,32 @@ + +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { KubernetesAnalysisReportComponent } from './kubernetes-analysis-report.component'; +import { KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; +import { AnalysisReportViewerComponent } from './../../../analysis-report-viewer/analysis-report-viewer.component'; + +describe('KubernetesAnalysisReportComponent', () => { + let component: KubernetesAnalysisReportComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ KubernetesAnalysisReportComponent, AnalysisReportViewerComponent ], + imports: [ + KubernetesBaseTestModules, +// MDAppModule + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubernetesAnalysisReportComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.theme.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.theme.scss new file mode 100644 index 0000000000..4ea5002e16 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.theme.scss @@ -0,0 +1,13 @@ + +@mixin kube-analysis-report-theme($theme, $app-theme) { + $backgrounds: map-get($theme, background); + $background: mat-color($backgrounds, card); + $background-color: map-get($app-theme, app-background-color); + $darker-background-color: darken($background-color, 4%); + .report__header { + background-color: $darker-background-color; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + padding-left: 10px; + } +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.ts new file mode 100644 index 0000000000..4ca7a3fc19 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.ts @@ -0,0 +1,80 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { IHeaderBreadcrumbLink } from 'frontend/packages/core/src/shared/components/page-header/page-header.types'; +import { BehaviorSubject, Observable, of, Subject } from 'rxjs'; +import { catchError, first, map, startWith } from 'rxjs/operators'; + +import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; +import { KubernetesAnalysisService } from '../../../services/kubernetes.analysis.service'; +import { getParentURL } from '../../../services/route.helper'; + +@Component({ + selector: 'app-kubernetes-analysis-report', + templateUrl: './kubernetes-analysis-report.component.html', + styleUrls: ['./kubernetes-analysis-report.component.scss'] +}) +export class KubernetesAnalysisReportComponent implements OnInit { + + report$: Observable; + private errorMsg = new Subject(); + errorMsg$ = this.errorMsg.pipe(startWith('')); + isLoading$: Observable; + + endpointID: string; + id: string; + + private breadcrumbsSubject: BehaviorSubject; + public breadcrumbs$: Observable; + + constructor( + private analysisService: KubernetesAnalysisService, + private route: ActivatedRoute, + private kubeEndpointService: KubernetesEndpointService, + ) { + this.id = route.snapshot.params.id; + + this.breadcrumbsSubject = new BehaviorSubject(undefined); + this.breadcrumbs$ = this.breadcrumbsSubject.asObservable(); + this.breadcrumbsSubject.next([ + { value: 'Analysis', routerLink: getParentURL(route, 2) }, + { value: 'Report' }, + ]); + } + + ngOnInit() { + this.report$ = this.analysisService.getByID(this.kubeEndpointService.baseKube.guid, this.id).pipe( + map((response: any) => { + if (!response.type) { + this.error(); + return false; + } + this.errorMsg.next(''); + return response; + }), + catchError((e, c) => { + this.error(); + return of(false); + }) + ); + + this.isLoading$ = this.report$.pipe( + map(() => false), + startWith(true) + ); + + // When the report has loaded, update the name in the breadcrumbs + this.report$.pipe(first()).subscribe(report => { + this.breadcrumbsSubject.next([ + { value: 'Analysis', routerLink: getParentURL(this.route, 2) }, + { value: report.name }, + ]); + }); + } + + error() { + const msg = { firstLine: 'Failed to load Analysis Report' }; + this.errorMsg.next(msg); + } +} + + diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.html new file mode 100644 index 0000000000..42ebd5c5d8 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.html @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.spec.ts new file mode 100644 index 0000000000..b13da9b832 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.spec.ts @@ -0,0 +1,42 @@ + +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { MDAppModule } from './../../../../core/md.module'; + +import { KubernetesAnalysisTabComponent } from './kubernetes-analysis-tab.component'; +import { KubernetesBaseTestModules, KubeBaseGuidMock } from '../../kubernetes.testing.module'; +import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; +import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; +import { AnalysisReportViewerComponent } from './../../analysis-report-viewer/analysis-report-viewer.component'; +import { TabNavService } from 'frontend/packages/core/tab-nav.service'; + +describe('KubernetesAnalysisTabComponent', () => { + let component: KubernetesAnalysisTabComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ KubernetesAnalysisTabComponent, AnalysisReportViewerComponent ], + imports: [ + KubernetesBaseTestModules, + MDAppModule + ], + providers: [ + KubernetesAnalysisService, + KubernetesEndpointService, + KubeBaseGuidMock, + TabNavService, + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubernetesAnalysisTabComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.ts new file mode 100644 index 0000000000..f0db6bbd91 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.ts @@ -0,0 +1,24 @@ +import { Component } from '@angular/core'; +import { ListConfig } from 'frontend/packages/core/src/shared/components/list/list.component.types'; + +import { AnalysisReportsListConfig } from '../../list-types/analysis-reports-list-config.service'; +import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; +import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; + +@Component({ + selector: 'app-kubernetes-analysis-tab', + templateUrl: './kubernetes-analysis-tab.component.html', + styleUrls: ['./kubernetes-analysis-tab.component.scss'], + providers: [ + KubernetesAnalysisService, + { + provide: ListConfig, + useClass: AnalysisReportsListConfig, + } + ] +}) +export class KubernetesAnalysisTabComponent { + + constructor(public kubeEndpointService: KubernetesEndpointService) { } + +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.spec.ts index 4777afe99b..96996ee411 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.spec.ts @@ -1,8 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TabNavService } from 'frontend/packages/core/tab-nav.service'; -import { HelmReleaseProviders, KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; +import { HelmReleaseProviders, KubernetesBaseTestModules, KubeBaseGuidMock } from '../../../kubernetes.testing.module'; import { HelmReleaseTabBaseComponent } from './helm-release-tab-base.component'; +import { KubernetesAnalysisService } from '../../../services/kubernetes.analysis.service'; +import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; describe('HelmReleaseTabBaseComponent', () => { @@ -15,7 +17,10 @@ describe('HelmReleaseTabBaseComponent', () => { declarations: [HelmReleaseTabBaseComponent], providers: [ ...HelmReleaseProviders, - TabNavService + TabNavService, + KubernetesAnalysisService, + KubernetesEndpointService, + KubeBaseGuidMock, ] }) .compileComponents(); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts index 9a838e889f..b84d708c7c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts @@ -7,12 +7,14 @@ import { catchError, map, share, switchMap } from 'rxjs/operators'; import { LoggerService } from '../../../../../../../core/src/core/logger.service'; import { IPageSideNavTab } from '../../../../../../../core/src/features/dashboard/page-side-nav/page-side-nav.component'; +import { SessionService } from '../../../../../../../core/src/shared/services/session.service'; import { SnackBarService } from '../../../../../../../core/src/shared/services/snackbar.service'; import { AppState } from '../../../../../../../store/src/app-state'; import { entityCatalog } from '../../../../../../../store/src/entity-catalog/entity-catalog'; import { EntityRequestAction, WrapperRequestActionSuccess } from '../../../../../../../store/src/types/request.types'; import { kubeEntityCatalog } from '../../../kubernetes-entity-catalog'; import { KubernetesPodExpandedStatusHelper } from '../../../services/kubernetes-expanded-state'; +import { KubernetesAnalysisService } from '../../../services/kubernetes.analysis.service'; import { KubernetesPod, KubeService } from '../../../store/kube.types'; import { KubePaginationAction } from '../../../store/kubernetes.actions'; import { HelmReleaseGraph, HelmReleaseGuid, HelmReleasePod, HelmReleaseService } from '../../workload.types'; @@ -26,6 +28,7 @@ import { HelmReleaseHelperService } from '../tabs/helm-release-helper.service'; styleUrls: ['./helm-release-tab-base.component.scss'], providers: [ HelmReleaseHelperService, + KubernetesAnalysisService, { provide: HelmReleaseGuid, useFactory: (activatedRoute: ActivatedRoute) => ({ @@ -43,8 +46,6 @@ export class HelmReleaseTabBaseComponent implements OnDestroy { private sub: Subscription; - // private connection: Connection; - public breadcrumbs = [{ breadcrumbs: [ { value: 'Workloads', routerLink: '/workloads' } @@ -53,23 +54,28 @@ export class HelmReleaseTabBaseComponent implements OnDestroy { public title = ''; - tabLinks: IPageSideNavTab[] = [ - { link: 'summary', label: 'Summary', icon: 'helm', iconFont: 'stratos-icons' }, - { link: 'notes', label: 'Notes', icon: 'subject' }, - { link: 'values', label: 'Values', icon: 'list' }, - { link: '-', label: 'Resources' }, - // { link: 'graph', label: 'Overview', icon: 'share' }, - { link: 'pods', label: 'Pods', icon: 'pod', iconFont: 'stratos-icons' }, - { link: 'services', label: 'Services', icon: 'service', iconFont: 'stratos-icons' } - ]; + tabLinks: IPageSideNavTab[]; + constructor( public helmReleaseHelper: HelmReleaseHelperService, private store: Store, private logService: LoggerService, - private snackbarService: SnackBarService + private analysisService: KubernetesAnalysisService, + private snackbarService: SnackBarService, + sessionService: SessionService ) { this.title = this.helmReleaseHelper.releaseTitle; + this.tabLinks = [ + { link: 'summary', label: 'Summary', icon: 'helm', iconFont: 'stratos-icons' }, + { link: 'notes', label: 'Notes', icon: 'subject' }, + { link: 'values', label: 'Values', icon: 'list' }, + { link: 'analysis', label: 'Analysis', icon: 'assignment', hidden$: this.analysisService.hideAnalysis$ }, + { link: '-', label: 'Resources' }, + { link: 'graph', label: 'Overview', icon: 'share', hidden$: sessionService.isTechPreview().pipe(map(tp => !tp)) }, + { link: 'pods', label: 'Pods', icon: 'pod', iconFont: 'stratos-icons' }, + { link: 'services', label: 'Services', icon: 'service', iconFont: 'stratos-icons' } + ]; const releaseRef = this.helmReleaseHelper.guidAsUrlFragment(); const host = window.location.host; @@ -132,21 +138,23 @@ export class HelmReleaseTabBaseComponent implements OnDestroy { const releaseServicesAction = kubeEntityCatalog.service.actions.getInWorkload( this.helmReleaseHelper.releaseTitle, this.helmReleaseHelper.endpointGuid, - ) + ); this.populateList(releaseServicesAction, svcs); } - const resources = { ...manifest }; - resources.endpointId = this.helmReleaseHelper.endpointGuid; - resources.releaseTitle = this.helmReleaseHelper.releaseTitle; + // const resources = { ...manifest }; + // kind === 'Resources' is an array, really they should go into a pagination section + messageObj.endpointId = this.helmReleaseHelper.endpointGuid; + messageObj.releaseTitle = this.helmReleaseHelper.releaseTitle; + const releaseResourceAction = workloadsEntityCatalog.resource.actions.get( - resources.releaseTitle, - resources.endpointId, + this.helmReleaseHelper.releaseTitle, + this.helmReleaseHelper.endpointGuid, ); - this.addResource(releaseResourceAction, resources); + this.addResource(releaseResourceAction, messageObj); } else if (messageObj.kind === 'ManifestErrors') { if (messageObj.data) { - this.snackbarService.show('Errors were found when parsing this workload. Not all resources may be shown', 'Dismiss') + this.snackbarService.show('Errors were found when parsing this workload. Not all resources may be shown', 'Dismiss'); } } } @@ -181,7 +189,7 @@ export class HelmReleaseTabBaseComponent implements OnDestroy { newResource.metadata.kubeId = action.kubeGuid; // The service entity from manifest is missing this, but apply here to ensure any others are caught newResource.metadata.namespace = this.helmReleaseHelper.namespace; - const entityId = action.entity[0].getId(resource) + const entityId = action.entity[0].getId(resource); newResources[entityId] = newResource; }); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/icon-helper.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/icon-helper.ts new file mode 100644 index 0000000000..ea2d9e6df0 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/icon-helper.ts @@ -0,0 +1,77 @@ +export function getIcon(kind: string) { + const rkind = kind || 'Pod'; + if (iconMappings[rkind]) { + return iconMappings[rkind]; + } else { + return iconMappings.default; + } +} + +const iconMappings = { + Namespace: { + name: 'namespace', + font: 'stratos-icons' + }, + Container: { + name: 'container', + font: 'stratos-icons' + }, + ClusterRole: { + name: 'cluster_role', + font: 'stratos-icons' + }, + ClusterRoleBinding: { + name: 'cluster_role_binding', + font: 'stratos-icons' + }, + Deployment: { + name: 'deployment', + font: 'stratos-icons' + }, + ReplicaSet: { + name: 'replica_set', + font: 'stratos-icons' + }, + Pod: { + name: 'pod', + font: 'stratos-icons' + }, + Service: { + name: 'service', + font: 'stratos-icons' + }, + Role: { + name: 'assignment_ind', + font: 'Material Icons', + fontSet: 'material-icons' + }, + RoleBinding: { + name: 'role_binding', + font: 'stratos-icons' + }, + StatefulSet: { + name: 'stateful_set', + font: 'stratos-icons' + }, + Ingress: { + name: 'ingress', + font: 'stratos-icons' + }, + ConfigMap: { + name: 'config_map', + font: 'stratos-icons' + }, + Secret: { + name: 'config_map', + font: 'stratos-icons' + }, + ServiceAccount: { + name: 'lock', + font: 'Material Icons', + fontSet: 'material-icons' + }, + default: { + name: 'collocation', + font: 'stratos-icons' + } +}; \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.html new file mode 100644 index 0000000000..15301e10ff --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.html @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.spec.ts new file mode 100644 index 0000000000..a2c29af062 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.spec.ts @@ -0,0 +1,42 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HelmReleaseAnalysisTabComponent } from './helm-release-analysis-tab.component'; +import { AnalysisReportSelectorComponent } from './../../../../analysis-report-viewer/analysis-report-selector/analysis-report-selector.component'; +import { KubernetesBaseTestModules, KubeBaseGuidMock } from '../../../../kubernetes.testing.module'; +import { KubernetesAnalysisService } from '../../../../services/kubernetes.analysis.service'; +import { KubernetesEndpointService } from '../../../../services/kubernetes-endpoint.service'; +import { AnalysisReportViewerComponent } from '../../../../analysis-report-viewer/analysis-report-viewer.component'; +import { HelmReleaseProviders } from '../../../../kubernetes.testing.module'; +import { TabNavService } from 'frontend/packages/core/tab-nav.service'; + +describe('HelmReleaseAnalysisTabComponent', () => { + let component: HelmReleaseAnalysisTabComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ HelmReleaseAnalysisTabComponent, AnalysisReportSelectorComponent, AnalysisReportViewerComponent], + imports: [ + KubernetesBaseTestModules, + ], + providers: [ + KubernetesAnalysisService, + KubernetesEndpointService, + KubeBaseGuidMock, + HelmReleaseProviders, + TabNavService + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HelmReleaseAnalysisTabComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.ts new file mode 100644 index 0000000000..beb9436e4c --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.ts @@ -0,0 +1,42 @@ +import { Component } from '@angular/core'; +import { Subject } from 'rxjs'; + +import { KubernetesAnalysisService } from '../../../../services/kubernetes.analysis.service'; +import { AnalysisReport } from '../../../../store/kube.types'; +import { HelmReleaseHelperService } from '../helm-release-helper.service'; + +@Component({ + selector: 'app-helm-release-analysis-tab', + templateUrl: './helm-release-analysis-tab.component.html', + styleUrls: ['./helm-release-analysis-tab.component.scss'] +}) +export class HelmReleaseAnalysisTabComponent { + + public report$ = new Subject(); + + path: string; + + currentReport = null; + + noReportsAvailable = false; + + constructor( + public analaysisService: KubernetesAnalysisService, + public helmReleaseHelper: HelmReleaseHelperService + ) { + this.path = `${this.helmReleaseHelper.namespace}/${this.helmReleaseHelper.releaseTitle}`; + } + + public analysisChanged(report) { + if (report.id !== this.currentReport) { + this.currentReport = report.id; + this.analaysisService.getByID(this.helmReleaseHelper.endpointGuid, report.id).subscribe(r => this.report$.next(r)); + } + } + + + public onReportCount(count: number) { + this.noReportsAvailable = count === 0; + } + +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts index a7dd8aaa99..13b0507c23 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts @@ -10,7 +10,7 @@ import { HelmReleaseChartData, HelmReleaseGraph, HelmReleaseGuid, - HelmReleaseResource, + HelmReleaseResources, } from '../../workload.types'; import { workloadsEntityCatalog } from '../../workloads-entity-catalog'; @@ -69,7 +69,7 @@ export class HelmReleaseHelperService { ); } - public fetchReleaseResources(): Observable { + public fetchReleaseResources(): Observable { // Get helm release const action = workloadsEntityCatalog.resource.actions.get(this.releaseTitle, this.endpointGuid) return workloadsEntityCatalog.resource.store.getEntityMonitor( diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.html index 6dd8d86bda..1070d1925c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.html @@ -3,23 +3,52 @@ all_out Fit - + + + + +
- + - - /> - + + + {{ node.data.icon.name }} + {{node.label}} + {{node.data.kind}} + + + + + - \ No newline at end of file + +
+
+ Loading resources + + +
+
\ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.scss index ec3bfddc2f..beb8c49098 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.scss @@ -3,3 +3,9 @@ height: 100%; width: 100%; } + +@keyframes dash { + to { + stroke-dashoffset: 1000; + } +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.spec.ts index 238acbf3c5..99e5683d26 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.spec.ts @@ -5,6 +5,10 @@ import { TabNavService } from 'frontend/packages/core/tab-nav.service'; import { HelmReleaseProviders, KubernetesBaseTestModules } from '../../../../kubernetes.testing.module'; import { HelmReleaseResourceGraphComponent } from './helm-release-resource-graph.component'; +import { AnalysisReportSelectorComponent } from './../../../../analysis-report-viewer/analysis-report-selector/analysis-report-selector.component'; +import { KubernetesAnalysisService } from '../../../../services/kubernetes.analysis.service'; +import { KubernetesEndpointService } from '../../../../services/kubernetes-endpoint.service'; +import { KubeBaseGuidMock } from './../../../../kubernetes.testing.module'; describe('HelmReleaseResourceGraphComponent', () => { let component: HelmReleaseResourceGraphComponent; @@ -16,11 +20,14 @@ describe('HelmReleaseResourceGraphComponent', () => { ...KubernetesBaseTestModules, NgxGraphModule ], - declarations: [HelmReleaseResourceGraphComponent], + declarations: [HelmReleaseResourceGraphComponent, AnalysisReportSelectorComponent], providers: [ ...HelmReleaseProviders, SidePanelService, TabNavService, + KubernetesAnalysisService, + KubernetesEndpointService, + KubeBaseGuidMock, ] }) .compileComponents(); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.ts index d47848393b..9e73f79e75 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.ts @@ -1,13 +1,22 @@ import { Component, ComponentFactoryResolver, OnDestroy, OnInit } from '@angular/core'; -import { Edge, Node } from '@swimlane/ngx-graph'; +import { Edge } from '@swimlane/ngx-graph'; import { SidePanelService } from 'frontend/packages/core/src/shared/services/side-panel.service'; -import { BehaviorSubject, Observable, Subscription } from 'rxjs'; -import { filter, first, map } from 'rxjs/operators'; +import { BehaviorSubject, combineLatest, Observable, Subject, Subscription } from 'rxjs'; +import { distinctUntilChanged, filter, first, map, publishReplay, refCount, startWith } from 'rxjs/operators'; import { KubernetesResourceViewerComponent, } from '../../../../kubernetes-resource-viewer/kubernetes-resource-viewer.component'; -import { KubeAPIResource } from '../../../../store/kube.types'; +import { ResourceAlert, ResourceAlertLevel } from '../../../../services/analysis-report.types'; +import { KubernetesAnalysisService } from '../../../../services/kubernetes.analysis.service'; +import { + HelmReleaseGraphLink, + HelmReleaseGraphNode, + HelmReleaseGraphNodeData, + HelmReleaseResource, + HelmReleaseResources, +} from '../../../workload.types'; +import { getIcon } from '../../icon-helper'; import { HelmReleaseHelperService } from '../helm-release-helper.service'; @@ -22,6 +31,26 @@ const layouts = [ 'colaForceDirected' ]; +interface CustomHelmReleaseGraphNode extends Omit { + data: CustomHelmReleaseGraphNodeData +} + +interface CustomHelmReleaseGraphNode { + id: string; + label: string; + data: CustomHelmReleaseGraphNodeData +} + +interface CustomHelmReleaseGraphNodeData extends HelmReleaseGraphNodeData { + missing: boolean, + dash: number, + fill: string, + text: string, + icon: any, + alerts: [], + alertSummary: {} +} + @Component({ selector: 'app-helm-release-resource-graph', templateUrl: './helm-release-resource-graph.component.html', @@ -31,7 +60,7 @@ export class HelmReleaseResourceGraphComponent implements OnInit, OnDestroy { // see: https://swimlane.github.io/ngx-graph/#/#quick-start - public nodes: Node[] = []; + public nodes: CustomHelmReleaseGraphNode[] = []; public links: Edge[] = []; update$: BehaviorSubject = new BehaviorSubject(false); @@ -44,31 +73,62 @@ export class HelmReleaseResourceGraphComponent implements OnInit, OnDestroy { private graph: Subscription; + private didInitialFit = false; + + public path: string; + + private analysisReportUpdated = new Subject(); + private analysisReportUpdated$ = this.analysisReportUpdated.pipe( + startWith(null), + distinctUntilChanged(), + publishReplay(1), + refCount() + ); + constructor( private componentFactoryResolver: ComponentFactoryResolver, private helper: HelmReleaseHelperService, - private previewPanel: SidePanelService) { } + public analyzerService: KubernetesAnalysisService, + private previewPanel: SidePanelService) { + this.path = `${this.helper.namespace}/${this.helper.releaseTitle}`; + } ngOnInit() { // Listen for the graph - this.graph = this.helper.fetchReleaseGraph().subscribe(g => { - const newNodes = []; - Object.values(g.nodes).forEach((node: any) => { + this.graph = combineLatest( + this.helper.fetchReleaseGraph(), + this.analysisReportUpdated$ + ).subscribe(([g, report]) => { + const newNodes: CustomHelmReleaseGraphNode[] = []; + Object.values(g.nodes).forEach((node: HelmReleaseGraphNode) => { const colors = this.getColor(node.data.status); - newNodes.push({ + const icon = getIcon(node.data.kind); + const missing = node.data.status === 'missing'; + + const newNode: CustomHelmReleaseGraphNode = { id: node.id, label: node.label, data: { ...node.data, + missing: node.data.status === 'missing', + dash: missing ? 6 : 0, fill: colors.bg, - text: colors.fg + text: colors.fg, + icon: icon, + alerts: null, + alertSummary: {} }, - }); + }; + + // Does this node have any alerts? + this.applyAlertToNode(newNode, report) + + newNodes.push(newNode); }); this.nodes = newNodes; - const newLinks = []; + const newLinks: HelmReleaseGraphLink[] = []; Object.values(g.links).forEach((link: any) => { newLinks.push({ id: link.id, @@ -79,9 +139,48 @@ export class HelmReleaseResourceGraphComponent implements OnInit, OnDestroy { }); this.links = newLinks; this.update$.next(true); + + if (!this.didInitialFit) { + this.didInitialFit = true; + setTimeout(() => this.fitGraph(), 10); + } }); } + private applyAlertToNode(newNode, report) { + if (report && report.alerts) { + Object.values(report.alerts).forEach((group: ResourceAlert[]) => { + group.forEach(alert => { + if ( + newNode.data.kind.toLowerCase() === alert.kind && + newNode.data.metadata.name === alert.name + // namespace is undefined, however the only resources we have should be from the correct context + ) { + newNode.data.alerts = newNode.data.alerts || []; + newNode.data.alerts.push(alert); + newNode.data.alertSummary = newNode.data.alertSummary || {}; + if (alert.level > newNode.data.alertSummary.level || !newNode.data.alertSummary.level) { + newNode.data.alertSummary.color = this.alertLevelToColor(alert.level); + newNode.data.alertSummary.level = alert.level; + } + } + }); + }); + } + } + + private alertLevelToColor(level: ResourceAlertLevel) { + // These colours need to come from theme - #420 + switch (level) { + case ResourceAlertLevel.Info: + return '#42a5f5'; + case ResourceAlertLevel.Warning: + return '#ff9800'; + case ResourceAlertLevel.Error: + return '#f44336'; + } + } + ngOnDestroy() { if (this.graph) { this.graph.unsubscribe(); @@ -89,15 +188,20 @@ export class HelmReleaseResourceGraphComponent implements OnInit, OnDestroy { } // Open side panel when node is clicked - public onNodeClick(node: any) { - this.previewPanel.show( - KubernetesResourceViewerComponent, - { - title: 'Helm Release Resource Preview', - resource$: this.getResource(node) - }, - this.componentFactoryResolver - ); + public onNodeClick(node: CustomHelmReleaseGraphNode) { + this.analysisReportUpdated$.pipe(first()).subscribe(analysis => { + this.previewPanel.show( + KubernetesResourceViewerComponent, + { + title: 'Helm Release Resource Preview', + resource$: this.getResource(node), + analysis, + resourceKind: node.data.kind + }, + this.componentFactoryResolver + ); + }) + } public fitGraph() { @@ -138,12 +242,31 @@ export class HelmReleaseResourceGraphComponent implements OnInit, OnDestroy { } } - private getResource(node: any): Observable { + private getResource(node: CustomHelmReleaseGraphNode): Observable { return this.helper.fetchReleaseResources().pipe( filter(r => !!r), - map((r: any[]) => Object.values(r).find((res: any) => res.metadata.name === node.label && res.metadata.kind === node.kind)), + // tap(r => { + // console.log(node); + // console.log(r); + // }), + map((r: HelmReleaseResources) => Object.values(r.data).find((res) => { + // if (!res.metadata) { + // console.log(node, res); + // } + return res.metadata.name === node.label && res.kind === node.data.kind; + })), first(), ); } + public analysisChanged(report) { + if (report === null) { + this.analysisReportUpdated.next(null); + } else { + this.analyzerService.getByID(this.helper.endpointGuid, report.id).subscribe(results => { + this.analysisReportUpdated.next(results); + }); + } + } + } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html index f69faf6ea9..0bfbe0568a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html @@ -1,8 +1,15 @@ - + + + + + + @@ -59,7 +66,8 @@
+ [alerts]="res.alerts" (showAlerts)="showAlerts($event, res)" + iconFont="{{ res.icon.fontSet || res.icon.font }}" value="{{ res.count }}">
@@ -76,12 +84,11 @@
- Loading Resources + Loading resources
-
\ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts index 60540500e7..ebe873f1d2 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts @@ -1,8 +1,12 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TabNavService } from 'frontend/packages/core/tab-nav.service'; -import { HelmReleaseProviders, KubernetesBaseTestModules } from '../../../../kubernetes.testing.module'; +import { HelmReleaseProviders, KubernetesBaseTestModules, KubeBaseGuidMock } from '../../../../kubernetes.testing.module'; import { HelmReleaseSummaryTabComponent } from './helm-release-summary-tab.component'; +import { KubernetesAnalysisService } from '../../../../services/kubernetes.analysis.service'; +import { KubernetesEndpointService } from '../../../../services/kubernetes-endpoint.service'; +import { AnalysisReportSelectorComponent } from './../../../../analysis-report-viewer/analysis-report-selector/analysis-report-selector.component'; +import { SidePanelService } from './../../../../../../shared/services/side-panel.service'; describe('HelmReleaseSummaryTabComponent', () => { let component: HelmReleaseSummaryTabComponent; @@ -13,10 +17,14 @@ describe('HelmReleaseSummaryTabComponent', () => { imports: [ ...KubernetesBaseTestModules ], - declarations: [HelmReleaseSummaryTabComponent], + declarations: [HelmReleaseSummaryTabComponent, AnalysisReportSelectorComponent], providers: [ ...HelmReleaseProviders, - TabNavService + KubernetesAnalysisService, + KubernetesEndpointService, + KubeBaseGuidMock, + TabNavService, + SidePanelService ] }) .compileComponents(); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts index a302a6fd2a..3b3d8ce7c8 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts @@ -1,25 +1,32 @@ import { HttpClient } from '@angular/common/http'; -import { Component, OnDestroy } from '@angular/core'; +import { Component, ComponentFactoryResolver, OnDestroy } from '@angular/core'; import { Store } from '@ngrx/store'; import { LoggerService } from 'frontend/packages/core/src/core/logger.service'; import { ConfirmationDialogConfig } from 'frontend/packages/core/src/shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from 'frontend/packages/core/src/shared/components/confirmation-dialog.service'; +import { SidePanelService } from 'frontend/packages/core/src/shared/services/side-panel.service'; import { ClearPaginationOfType } from 'frontend/packages/store/src/actions/pagination.actions'; import { RouterNav } from 'frontend/packages/store/src/actions/router.actions'; import { AppState } from 'frontend/packages/store/src/app-state'; -import { combineLatest, Observable, ReplaySubject } from 'rxjs'; +import { combineLatest, Observable, ReplaySubject, Subject } from 'rxjs'; import { distinctUntilChanged, filter, first, map, publishReplay, refCount, startWith } from 'rxjs/operators'; import { SnackBarService } from '../../../../../../../../core/src/shared/services/snackbar.service'; import { endpointsEntityRequestDataSelector } from '../../../../../../../../store/src/selectors/endpoint.selectors'; -import { HelmReleaseChartData, HelmReleaseResource } from '../../../workload.types'; +import { + ResourceAlertPreviewComponent, +} from '../../../../analysis-report-viewer/resource-alert-preview/resource-alert-preview.component'; +import { KubernetesAnalysisService } from '../../../../services/kubernetes.analysis.service'; +import { HelmReleaseChartData } from '../../../workload.types'; import { workloadsEntityCatalog } from '../../../workloads-entity-catalog'; +import { getIcon } from '../../icon-helper'; import { HelmReleaseHelperService } from '../helm-release-helper.service'; +import { ResourceAlert } from './../../../../services/analysis-report.types'; @Component({ selector: 'app-helm-release-summary-tab', templateUrl: './helm-release-summary-tab.component.html', - styleUrls: ['./helm-release-summary-tab.component.scss'] + styleUrls: ['./helm-release-summary-tab.component.scss'], }) export class HelmReleaseSummaryTabComponent implements OnDestroy { // Confirmation dialogs @@ -38,6 +45,8 @@ export class HelmReleaseSummaryTabComponent implements OnDestroy { private successChartColor = '#4DD3A7'; private completedChartColour = '#7aa3e5'; + public path: string; + public podChartColors = [ { name: 'Running', @@ -60,88 +69,30 @@ export class HelmReleaseSummaryTabComponent implements OnDestroy { } ]; - - public iconMappings = { - Namespace: { - name: 'namespace', - font: 'stratos-icons' - }, - Container: { - name: 'container', - font: 'stratos-icons' - }, - ClusterRole: { - name: 'cluster_role', - font: 'stratos-icons' - }, - ClusterRoleBinding: { - name: 'cluster_role_binding', - font: 'stratos-icons' - }, - Deployment: { - name: 'deployment', - font: 'stratos-icons' - }, - ReplicaSet: { - name: 'replica_set', - font: 'stratos-icons' - }, - Pod: { - name: 'pod', - font: 'stratos-icons' - }, - Service: { - name: 'service', - font: 'stratos-icons' - }, - Role: { - name: 'assignment_ind' - }, - RoleBinding: { - name: 'role_binding', - font: 'stratos-icons' - }, - StatefulSet: { - name: 'stateful_set', - font: 'stratos-icons' - }, - Ingress: { - name: 'ingress', - font: 'stratos-icons' - }, - ConfigMap: { - name: 'config_map', - font: 'stratos-icons' - }, - Secret: { - name: 'config_map', - font: 'stratos-icons' - }, - ServiceAccount: { - name: 'lock' - }, - default: { - name: 'collocation', - font: 'stratos-icons' - } - }; - // Blue: #00B2E2 // Yellow: #FFC107 private deleted = false; public chartData$: Observable; - public resources$: Observable; + public resources$: Observable; + + // Cached analysis report + private analysisReport; + + private analysisReportUpdated = new Subject(); + private analysisReportUpdated$ = this.analysisReportUpdated.pipe(startWith(null), distinctUntilChanged()); constructor( + private componentFactoryResolver: ComponentFactoryResolver, public helmReleaseHelper: HelmReleaseHelperService, private store: Store, private confirmDialog: ConfirmationDialogService, private httpClient: HttpClient, private logService: LoggerService, - private snackbarService: SnackBarService + private snackbarService: SnackBarService, + public analyzerService: KubernetesAnalysisService, + private previewPanel: SidePanelService, ) { - this.isBusy$ = combineLatest([ this.helmReleaseHelper.isFetching$, this.busyDeletingSubject.asObservable().pipe( @@ -152,6 +103,8 @@ export class HelmReleaseSummaryTabComponent implements OnDestroy { startWith(true) ); + this.path = `${this.helmReleaseHelper.namespace}/${this.helmReleaseHelper.releaseTitle}`; + this.chartData$ = this.helmReleaseHelper.fetchReleaseChartStats().pipe( distinctUntilChanged(), map(chartData => ({ @@ -162,8 +115,11 @@ export class HelmReleaseSummaryTabComponent implements OnDestroy { ) ); - this.resources$ = this.helmReleaseHelper.fetchReleaseGraph().pipe( - map((graph: any) => { + this.resources$ = combineLatest( + this.helmReleaseHelper.fetchReleaseGraph(), + this.analysisReportUpdated$ + ).pipe( + map(([graph,]) => { const resources = {}; // Collect the resources Object.values(graph.nodes).forEach((node: any) => { @@ -173,18 +129,20 @@ export class HelmReleaseSummaryTabComponent implements OnDestroy { label: `${node.data.kind}s`, count: 0, statuses: [], - icon: this.getIcon(node.data.kind) + icon: getIcon(node.data.kind) }; } resources[node.data.kind].count++; resources[node.data.kind].statuses.push(node.data.status); }); + this.applyAnalysis(resources, this.analysisReport); return Object.values(resources).sort((a: any, b: any) => a.kind.localeCompare(b.kind)); }), publishReplay(1), refCount() ); + this.hasResources$ = combineLatest([ this.chartData$, this.resources$ @@ -206,14 +164,25 @@ export class HelmReleaseSummaryTabComponent implements OnDestroy { }, 'Delete' ); + + this.hasAllResources$ = combineLatest([ + this.resources$, + this.hasResources$ + ]).pipe( + map(([resources, hasSome]) => hasSome && resources && resources.length > 0) + ); } - private getIcon(kind: string) { - const rkind = kind || 'Pod'; - if (this.iconMappings[rkind]) { - return this.iconMappings[rkind]; + public analysisChanged(report) { + if (report === null) { + // No report selected + this.analysisReport = null; + this.analysisReportUpdated.next(''); } else { - return this.iconMappings.default; + this.analyzerService.getByID(this.helmReleaseHelper.endpointGuid, report.id).subscribe(results => { + this.analysisReport = results; + this.analysisReportUpdated.next(report.id); + }); } } @@ -283,4 +252,35 @@ export class HelmReleaseSummaryTabComponent implements OnDestroy { first() ); } + + private applyAnalysis(resources, report) { + // Clear out existing alerts for all resources + Object.values(resources).forEach((resource: any) => resource.alerts = []); + + if (report && Object.keys(resources).length > 0) { + Object.values(report.alerts).forEach((group: ResourceAlert[]) => { + group.forEach(alert => { + // Can we find a corresponding group in the resources? + const res = Object.keys(resources).find((i) => i.toLowerCase() === alert.kind); + if (res) { + const resItem = resources[res]; + if (resItem) { + resItem.alerts.push(alert); + } + } + }); + }); + } + } + + public showAlerts(alerts, resource) { + this.previewPanel.show( + ResourceAlertPreviewComponent, + { + resource, + alerts, + }, + this.componentFactoryResolver + ); + } } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-factory.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-factory.ts index abce799544..9605546f9d 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-factory.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-factory.ts @@ -1,6 +1,6 @@ import { EntitySchema } from '../../../../../../store/src/helpers/entity-schema'; import { addKubernetesEntitySchema, KubernetesEntitySchema } from '../../kubernetes-entity-factory'; -import { HelmRelease, HelmReleaseGraph, HelmReleaseResource } from '../workload.types'; +import { HelmRelease, HelmReleaseGraph, HelmReleaseResources } from '../workload.types'; export const helmReleaseEntityKey = 'helmRelease'; export const helmReleasePodEntityType = 'helmReleasePod'; @@ -22,7 +22,7 @@ export const getHelmReleaseIdByObj = (entity: HelmRelease) => getHelmReleaseId(e export const getHelmReleaseGraphId = (endpointId: string, releaseTitle: string) => `${endpointId}${separator}${releaseTitle}`; export const getHelmReleaseGraphIdByObj = (entity: HelmReleaseGraph) => getHelmReleaseGraphId(entity.endpointId, entity.releaseTitle); export const getHelmReleaseResourceId = (endpointId: string, releaseTitle: string) => `${endpointId}${separator}${releaseTitle}`; -export const getHelmReleaseResourceIdByObj = (entity: HelmReleaseResource) => getHelmReleaseResourceId(entity.endpointId, entity.releaseTitle); +export const getHelmReleaseResourceIdByObj = (entity: HelmReleaseResources) => getHelmReleaseResourceId(entity.endpointId, entity.releaseTitle); const entityCache: { [key: string]: EntitySchema diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-generator.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-generator.ts index f4a0939afa..3a8f8a0181 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-generator.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-generator.ts @@ -5,7 +5,7 @@ import { import { StratosEndpointExtensionDefinition } from '../../../../../../store/src/entity-catalog/entity-catalog.types'; import { IFavoriteMetadata } from '../../../../../../store/src/types/user-favorites.types'; import { kubernetesEntityFactory } from '../../kubernetes-entity-factory'; -import { HelmRelease, HelmReleaseGraph, HelmReleaseResource } from '../workload.types'; +import { HelmRelease, HelmReleaseGraph, HelmReleaseResources } from '../workload.types'; import { workloadsEntityCatalog } from '../workloads-entity-catalog'; import { WorkloadGraphBuilders, @@ -62,7 +62,7 @@ function generateReleaseResourceEntity(endpointDefinition: StratosEndpointExtens schema: kubernetesEntityFactory(helmReleaseResourceEntityType), endpoint: endpointDefinition }; - workloadsEntityCatalog.resource = new StratosCatalogEntity( + workloadsEntityCatalog.resource = new StratosCatalogEntity( definition, { actionBuilders: workloadResourceBuilders diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workload.types.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workload.types.ts index f91220bad4..bf5b8e708b 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workload.types.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workload.types.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; -import { KubernetesPod, KubeService } from '../store/kube.types'; +import { KubeAPIResource, KubernetesPod, KubeService, KubeStatus } from '../store/kube.types'; export interface HelmRelease { endpointId: string; @@ -36,11 +36,43 @@ export interface HelmReleasePod extends HelmReleaseEntity, KubernetesPod { } export interface HelmReleaseService extends HelmReleaseEntity, KubeService { } export interface HelmReleaseGraph extends HelmReleaseEntity { - nodes: {}; - links: {}; + nodes: { [key: string]: HelmReleaseGraphNode }; + links: { [key: string]: HelmReleaseGraphLink }; } -export type HelmReleaseResource = any; +export interface HelmReleaseGraphNode { + id: string; + label: string; + data: HelmReleaseGraphNodeData +} + +export interface HelmReleaseGraphNodeData { + kind: string, + status: string, + metadata: { + name: string, + namespace: string + } +} + +export interface HelmReleaseGraphLink { + id: string; + label?: string; + source: string; + target: string; +} + +export interface HelmReleaseResources extends HelmReleaseEntity { + data: HelmReleaseResource[], + kind: string +}; + +export interface HelmReleaseKubeAPIResource extends KubeAPIResource { + apiVersion: string; + kind: string; +} + +export type HelmReleaseResource = HelmReleaseKubeAPIResource | KubeStatus; @Injectable() export class HelmReleaseGuid { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads-entity-catalog.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads-entity-catalog.ts index 06d3a33c04..6492abd0b9 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads-entity-catalog.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads-entity-catalog.ts @@ -1,7 +1,7 @@ import { StratosCatalogEntity } from '../../../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; import { IFavoriteMetadata } from '../../../../../store/src/types/user-favorites.types'; import { WorkloadGraphBuilders, WorkloadReleaseBuilders, WorkloadResourceBuilders } from './store/workload-action-builders'; -import { HelmRelease, HelmReleaseGraph, HelmReleaseResource } from './workload.types'; +import { HelmRelease, HelmReleaseGraph, HelmReleaseResources } from './workload.types'; /** * A strongly typed collection of Workload Catalog Entities. @@ -10,7 +10,7 @@ import { HelmRelease, HelmReleaseGraph, HelmReleaseResource } from './workload.t export class WorkloadsEntityCatalog { release: StratosCatalogEntity; graph: StratosCatalogEntity - resource: StratosCatalogEntity + resource: StratosCatalogEntity } /** diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts index 5fb855b409..aaed18524c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts @@ -18,6 +18,7 @@ import { HelmReleaseValuesTabComponent } from './release/tabs/helm-release-value import { HelmReleasesTabComponent } from './releases-tab/releases-tab.component'; import { WorkloadsStoreModule } from './store/workloads.store.module'; import { WorkloadsRouting } from './workloads.routing'; +import { HelmReleaseAnalysisTabComponent } from './release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component'; @NgModule({ imports: [ @@ -39,6 +40,7 @@ import { WorkloadsRouting } from './workloads.routing'; HelmReleaseServicesTabComponent, HelmReleaseResourceGraphComponent, HelmReleaseCardComponent, + HelmReleaseAnalysisTabComponent, ], entryComponents: [ HelmReleaseCardComponent diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.routing.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.routing.ts index 8f3a4100a7..dc687ecd76 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.routing.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.routing.ts @@ -12,6 +12,7 @@ import { HelmReleaseServicesTabComponent } from './release/tabs/helm-release-ser import { HelmReleaseSummaryTabComponent } from './release/tabs/helm-release-summary-tab/helm-release-summary-tab.component'; import { HelmReleaseValuesTabComponent } from './release/tabs/helm-release-values-tab/helm-release-values-tab.component'; import { HelmReleasesTabComponent } from './releases-tab/releases-tab.component'; +import { HelmReleaseAnalysisTabComponent } from './release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component'; const routes: Routes = [ { @@ -36,7 +37,8 @@ const routes: Routes = [ { path: 'values', component: HelmReleaseValuesTabComponent }, { path: 'pods', component: HelmReleasePodsTabComponent }, { path: 'services', component: HelmReleaseServicesTabComponent }, - { path: 'graph', component: HelmReleaseResourceGraphComponent } + { path: 'graph', component: HelmReleaseResourceGraphComponent }, + { path: 'analysis', component: HelmReleaseAnalysisTabComponent }, ] }, ] diff --git a/src/frontend/packages/suse-theme/assets/core/custom/kubescore.md b/src/frontend/packages/suse-theme/assets/core/custom/kubescore.md new file mode 100644 index 0000000000..a4a592e740 --- /dev/null +++ b/src/frontend/packages/suse-theme/assets/core/custom/kubescore.md @@ -0,0 +1,7 @@ +## Kube-Score + +Kube-score is a tool that performs static code analysis of your Kubernetes object definitions. + +The output is a list of recommendations of what you can improve to make your application more secure and resilient. + +[https://github.com/zegl/kube-score](https://github.com/zegl/kube-score) \ No newline at end of file diff --git a/src/frontend/packages/suse-theme/assets/core/custom/kubescore.png b/src/frontend/packages/suse-theme/assets/core/custom/kubescore.png new file mode 100644 index 0000000000000000000000000000000000000000..ee6b8f82f0d5e47468499fbc2b0a517e13c78776 GIT binary patch literal 36127 zcmeEu1zS~J*DkSvO{X-%rhAjp9n#$*ND0yG~Gpp+opA>Ex)lF}h94QKJh`+g^W z!tv4z*vvI!%n|pv$5@7`D7{8UAx43LfkBs*kyL|$fe(a%f#n7x0-v={qx6AN{YXRI9mx(>L{vENZ2`=QE;nUDAd)}mDQD#)y~lz%Ff5f2W8`ca&WK!BUqf= zZJmwWSZtlB{+{IDc_hu8OdLPhJAbgVrFfp#$k^_qvk)ca^Fsgm^UpqQ?EkZntmw+{cle_|K{(<{yNp)iwOdv;g@hUGjg_b)UdO&7Jfd_ z2U{yA6C-=G=W_`{|IZQs-KpU7l>Ew$AIyNWKA%XKT@dTwW0ap6=qJ&Whhc$IzU_@bLCB-z{U=P0|r)q4~9Y3_qQVHQrj3j`EC=@wS!Qr1N zDX+I~vNP;+?mV}f3>_WY>9fJa+9Xa`2`lFZV<99`PTIx~EG)6-|%!W4(ii?=7xJbys=^FW{h9FtGps z?|&rFjV#m6Ee-+u!hm~3eTM|6110XOTOuf5#@A$Vvpc_5e;&z}2$SSti$%1qe%73K z8O#{W@%(9M0t{&vZ)Y9}*w?oWR9tHoIv{h$Zne>LMHB`UNhd0a zv3_=fkaW*yMLrvYFzUbiL3QgV<=l|Hh)pFC2pHo`C)#2WYhN$Y5uUx~Hk|~M)Ue7-I>dB{HWn5y^Wy50${6W=KmF1d zqGu6UNc%Dhxz<$9U9Zlvb|%WWY1~p2?kG-eB#C^sHcI$g!nn<+-)my1y0K2WV~Y;$rhc%#1oG32*Zl zp4ZxyqsT^uzD4)92S(pv<;ExQQ8HbruE)kybMn%6#v#SlBv6Ps1CupU!j_z&;f8z0 zQ{n#Janf3RL2}ii?t-jlfa5+!ut~jl&_Lt@%;@VY23o@+1`%5Vdu64u7oM@?w99ha z%MNUZn9~^rVgVAKNPtOF2_886_A5hMxr&Onai{HCWSaf=9Vn!=ounP@0t9T{uLrpU zqL2xp{#z@4mYoM~s7RFAM&`W{RE(>^#pJ$#1-2Ck(-;tJmJ4S$<^9+g;vFY&4_`!hZ6JbcjGn52_(cz@Ac*!jb zl15+GP1 zWEHbJ$MXK7V7<{P^%&S{KJ$>5@lpXk=Dw`O&-U*Jv)UTpwS21B=5Wc8(@@UQ2}0MH z@-}ET56Db!pTGg1c|Vzl`2{ddB_nK!#aoJe ziSO=pGbOM3EasH3wO+>7gpfv8k*5QK_U)hn7DsVS4p?nJgJH@ECc>DkU?UczZC^JxWkiI_#S_dk&inz(BuBVOy@H_Mz0L5gB5 zVoH#Rf&2Frg#udS0o$n!Tv8jdK3bicHzY7~!nHJU=~HsIOpe%?($FXqqA5Jw=4XXG zx8EiqgCpE3QIuZs{WpoJaaYNSYV2|h7l1WGj!yFe&#C6lZ-ZJ6+4UoFK%%0k zz^3YQ!9*0XpFwJZGmP1~OAJ$8BC&JmWidsZx4>$<^1eet(|q*KxK>T-hKk9_O4iG- z#e-ia2O+#>v0;dW2gC{jHk6!-NC*q}gK~muW}m2@c~$R6nspSOK^y#2y3CbGNG4gr z4&1)jnjoxW-3$9qYs!2*9%OcMNU64PStGy9;&wd6l;VZi0IP=adj$ODVGLS*fUhF5 zbBLboDWiZ;f^|4Wvyr@mKeYZOZqV2juleimR55@m`gVeLIpqkb1lopuBF3xs*AYvV zaXvanqI-!0j@34v5RFp|aGbP&<9xTKf&`~U0~pBMsW=y@r4&<{n=p0cGln+|`-ps;6$&1cv4H1|9RYNN_WZEZ1#7~$ zBS?iw?CM;-lM7vPQ5AG5wRh)Pby>_jVpT~66G;S$q!j&K&mbPRkK1WSRKOW@LGM|9 zD;l`j?daldf)a%)0i0L{Fz+tVS{@UaniG~W4~@O}mjJkK${9bd<_m)o7~U@e^y!=5 zt;tIIM+Z#dUa{PyyH*O*M_OHac!!JE1kYAa3HZ4nT3iWOKMUm8mR~N|TCX58jC#iG z4FUsJ{z9~<=OU3!bo{`5>Wll%iGBp9?7TF%-QH<6nV}IlrBQ{e^He{9=yF}VhM|c2 z05fjV05iJpU?c~Kj)1T%UvO4S3fxQ4XV!2sRZyym?P9{YPFatA-*b1Z!7zlYJ((dvT@8a-)uR4lj@CURgj-Gd!E zQ|SrO7=hYT*9ICuTZpCmIfB8 z#_~IGyui*Ce(77fbfjkt(WZpoV(?{V!#ZRES<$@T{7zG#;XDOf1;8UjTwe(bHv3|D zSQcO^Ir((_SSiOG`-xAwyZ0Fg-~5m%a{2szbE~|N8nwyJYqAG(#f!2FoRgmY{%Mx4 zwhIS~H4y^r#;Z}B5pZ2Fd0NdIb^a$=UD%JW`Al}PnF!%YL@89^G`7=dNjFkjeS6-9 z4T<{oc>(Heg73IxYQi0^o0)_hrs>qiAKEB_UstE#A2?AXv|d|}8;Aj$)2 z0jsm3gp$CP2xS08@~g~40c`+US?X3?V4Cy2)f=)yfU%Mh|D`)uEf_|#9=el3xX*!8 zqxN2fP=*;KjN5gZo0+DKFHcvC0K~Z%Ks@L35}aWDI2iG=^3eWrffr+)x4v`M9+LQ5 zS@ley$fa~p@n?vGVb;yp4fod`YduubFdD(S(AMO)9awsz8O`TN)BlOdoX{nuy8 zzJvqU$z?amiOXm3aUJ{%@o^S06If(?^%EKJ#=SftPiPq_c4D|jYG>HI!#gGB_T_SI zVFA5gUjwq)A7zj>bpU3mz6KRUyK837^%krmJ+Xfb4vb3``wP(oK zup_b3E^`@Y>`C!X69~X%{G63cThCU$Rmi1_SgTGy^oh?(=4~}0nm`%w_+rH`Irxn0 zzAiZ{9T1aRX2-v%Md{SR*p1>HA20_C4-Fgr?DHJ6@3|QIao1GZ=cL}R(K!yWDXT@p zNRvqW>f0b>z8rA(cNWpcQsh)%$Mf^WGZ5Z3z}q>e$cdtLH8=0}eEU z67Q=p_IPjY{XO;?w+dO;R?})0nkE*5-taM!%0)sz#V4+Oj(YDBlm0bZP@a}nHqCr81#D1`n-7Kf znGv5rJG+?ft}O>F;{h>rN@Co}0mGYPfHQ&soR0yUS*m8v%8rAJycW_ad99MA513(+ z{yBmbH)LW+%>awTCxJN;R*_mf)Cg3(X&OB+b_dJk>HoAvR7e;u+TjMrt4%acN~|6V z_!hi$1vDuUI8w{HSf}Zf%O3#% z^}}7bf(?Ep5-l5Sxr<dEX6`eF-^feWVf`6;V7m@5-I*_yRy3Y%Hq$| zi@BdA*K%~>6L9s!Rz+z7A1$lW`lXB>1ps@|S3fMx!ca**X{a!Vv&??i<|V6Rvgz5z ze}DWs=4HCaWn!%reWQw8oE7@8wGG0UW_`9dnlSP6>Un{$>M2CalbigfW>iWutl8g1 zV8#bpEPFKP9r7Syu3)Jpj?`X6h7-vj)2ofTpzymRxnmNjxP9#nF@irYt`&%MmVP97 zMfj~CSmt;1(#_X3E9;q$$s8lWeOO^*UW!q7t0F2MLaStaYHrgd3{8?n+A7*dGI_;2 z41#ss4~-p7Ll+Fbu*m=poTyfZ?*@RZX10+&+qNu5b90Up!gS;ms?Y-tc-CXl^|-hs zbMhq^BcgVH4@mkM#g(TMLhL1bK(|)_RW)t z7HbYuCd>q-j~|Z~^;Ct+Sg;7oiT7ipo4d?PXG)E zrf;AGJd9T6i+NMS26vk7^Np#u`;qV}(C4r|&To>5k%QxVAT! zMNp<4$EAJGQB~2(%1D{BzFC$jy<@ zitc~k1t#QNDd6oK))1sc$vSrMhP-~-NCNwzTII^!a0vWt+=~M%px**M_BQ0pCDN1I z#XOF1BeW%Y!{l*y#LV^WY)XlbxL>dCJ zz6)hdZWJ3bh>NOYJx@KTD^H7B%Ze7y9|tm_yEvnBW@~d~Jcir)8J8zkf{@*~SY=;Z zZH|FXwSCMipJnAEoyv%h-N6>~j_opcK`~s3wc!}Sw~CV!EL)35QY4Jpx+C{)O}=q* zjGarX=tR2kOBxq;ac<d zIc%x&Ic}HweW1MpS3vf_&ex?to{(|%>CYcxUNz&Wd?6CXNqm&hO>|PwR>Z=9!>0oD z-xwFpsg}kbG9x#TqT{F$9a4`ks%Hn+rZRA;RhMC@r)U0W#kJtLia)S~ne~7mef%cK ziJmP$*@XaQMqd{5&z^K{Lbr?|{@-NTw4!`GYGHM-{yO1BXsp`kixH)>b?WaoMrs&7 zDsQIsf6jA>?03pIG+Qj}t4`MMIN_eNGiC#%qxk*!}|p0 zk*5jL%O5j~-w{h%er5P48rp-_eUAEKS>&oEc)0>fuq+2qnjzE(Vyt zXMMB$)`2V8TeoM2VbUvG{G~JVnzNPszIqqk=1Ax9ej%K{SO?{Em@=h&7wiHm_LulX znzsYoCE0Wj^6BgZu}4MUxh3N;`j&xDtrCI34G%D-dUkQZK_R+!36EtK2+yGMN! zV>$n?%ePe>;#_)l_F$?{v=I|+Tj6tq5lRLm&u#}CsB<7dNs$wKewLw7)gf8%7v0kh zt)z0p%^2s9)js1&L zoYCM2^z%gkuOSVS zkcn#|?GPaHwcC%A(x?nT3=kgndR-*IwUx;PK_jcwsAe5lQM8PCZ@FZ6m&o7YzTEM- zp-qRIRq*XzAmPn?P-$I1OcA4A&~+Yu>+o9QBC#mDsGRO8rG_E5P?!Ri+DH={ZcIPECMtJWLMXjfVrupgjHXXX9+b;Dm zkvUI*o5R^qjd}ZR4)-joXWn)9!d=JquUJ(@RJc?3lC*L4Boj#s266$?pS~%B0HBch z@fV;_sE6Md6U7C5EO#duB8^Mox1t$}=oJT! zS!-mV-I^J-G5(nH$E$F2A2-4Nh*w zpUz88!qKay!EnpNGS=nv87nWr#9E0377K47ZA9;6lpI+JdYdHnN?t;^EcaEv*oHs5_O^1|Ff#;->T z2-e=>>V`yikg-V2GIa}!K7wRrI)8$fgmx>7Y)U4Z%~xuNpk&d^RF~0(v3E)af>a4IHTz@Ii#h(yF5#7DzL`Rs z_9)Tl5_0T-rmI_vs~hoN5EVqkk*#iJW|;Ho+etD;GA#5@V1o~rwq5vQ>KY8Ke&~!i zgQ-lk&Rvp`pDbhtmcrQBciy_3-Wb)ywwar)4`4W1Hx?(!y$KGDtR+8BM!z3dp z;u;Uq5rzG+f~wU zHj7L1Zr>NSbvhv$MD~JOzUQb{cxv{nIierG7F)hL$e)DvB9AS&E8y|3CU{<2)h;i~ zmo^Hv7PsE>9lSKF_1-;@@`bhyk7q7i*(#0-`tS-C&lyOx}lw>5+h5svibr!tBgLDw5CwB-p6Iyimw zJR3WS?#ES9!A9PE`ee5aqZc7;{(>pR4hSD-Bk{tU5ozzj+|p#$n8u*`;|B!4*@qAr z?b>yDX9UIg`suSKrbS@u7EepJ%uL4Iynfx;nf=T@NM4yt&(3D=;f?aL8Y0kW4{wgf z$oqZY)kb1e8V3pwqWm$eJGLy%7{rQ2_?%DqfsNYBLB-!7+PsKDU4vJajY%}5m~WA+ zz1Eiz2M5`&4heT*{%AS7s+lB_5~a_trSF#|W;o;ji)!*Ks5 ze|YdBL^BQH`35F3G);c?*C!s_vsriJ*;yY7925-pGJF0;W|a&^DM4 zKz0TN+^rTnZ_T*6p+SLY6r}|qwfb?a&We;k_Xly=WH)i_s^Q?{EK=#)>H(KW1*JIe zvOeW!f>!JImo5monfb(wy<}Nm;-;g*R~J&2ngiR$GIk+O57XMCD2sjq&%CDaC%bdA zceF@4Z}x+8uzxk=Mmz14{FlZvr2?$QhHF8Kh*7it?YJ_?a4VvO+nN%x*8=_@B5Z92MqZi@W@PC~mNmp3ADmJ;VDbD#b$sj8&t?8nW|lygvTRMj6Tk0az_RkVs0 zMyHtC2IWbE=6akW7CmncP66|8^=frVS`>-hrFh3_`gy4aTnbBZG-b8ppwrtRpj0R; z07sZz3$o9PuT@VG44!5srp`RVtn7_B7nv?8r@>@jW+|Ypa2@|KG!$m;Wgu@@c|jeg zfML}0o1JpgKvsuz#}*OUP*0a7Sg>Ak@0N91b>qpJ)}+k(QnUNr>5+lK3nY*T0O_#0(TDf@BbUXe)% z{Rh&Mh)wl-Rc4AVTcR67i2nUBv- z*(+F)n7m1E8-?cRg$rUznkn22Koz*olkh5^U_obE1BWwcwpP7x1Ok^i^R@|ANdLSr zd0Y7ssnDIgG$`2)$3U8rfj~Q_dabbeP=Ckt z9NbsL_hw?@#io;kbGMoxSxih`;rIMvY!nCXsm}Ik4c0(EpspzEZZvO&c5MlP)A%8* zmOoizJtcA?(zX|x@)b13GShl6r#TzppTC)ADepg&undVrTm4Fp?uv3;1pxvD1^fBK zDu_YgI&AHWXU^s;PISNDPM~|Eg4?W;65p0BYc=RE$D>vfOpGD&%vwn57Y2>*YNrU* z)XCFT1FPFUXL_X~n{`^4Iz2B_zSk03yF;M{swOPt>US_5rYEYehj!*wD0x+z?GF^warpCI!JCjNaJZtH2gPAnk4<5 zjdv4U70H6Ugm|+(>#~k6%s_xwOOyA&wgA-4u=Ly<<9oRrU4OFTNtN}4puRy>SZoqF zv~BBN$((VW_xPf~9il=;p5vRux4HV=7f$`Urr-zj!6^Bvm?#|_fcADu$u!H;j{M#Y zZI&x$J4t-}z2?8i&(5h~-Hg?m8C~JVh_G|+&iIu|5e=azr@AH+^&zs@(KgP?Oh<$} z)|w7bi6gEh;+UioH`w#aYlMimU#cuZBVt6)cj%NgygiF;8FtVdI~!*{6?_J4JP%DQ zYq+Zo2M8x4OVyN1rx_Nv>6f*1(|o>tq52@fbeEAM>xAEOj)<8tx%z1OGkVMU z+bb)LPu{Xw&~NXF4rPUbMHB4C6dF?h3F>QeTcmcpnH+!s zOcc#R*f@UViSt#*H!Jan*@jwo3xM;9{n!{@`*>WRgaN#S%)&3vsc*eW?h+xR3Uy8D z{nKQ>m4t_=S#u*(UVsnrjJ9C;JO+%OYc%oCeK(t#JnG+QDMRetf3J%+q|CzvHl%T& zrZ66XL4RxjdcTFkAaZhC6G+gW~Wv^Lv|={}yQGCX-KQ2q46CLdmS#g|oLvtU5SI8V3-a_9wUc)QD-8Xo;nu zMjXSt-(;FeoQ*iEYyy;3rGScAjX}Td+;^!DKqIj_TY8c6o#RUV=9M#rudH^8tIm-u zQ!MZ3fK2zzKt_nYtNaM_sm=YByX@|3Gb4il8fJg&e|SHWXroLz`XGQJAa%^$MpokL zN3l4P?ZCZAX;a-B6Ih%SF4%Et@v3hD*0j6yKiICEOM^%aX}*t)#6Ff)?7){QM@-K{ zp594DBjR@H6g^UN5HZvV-e!DkA0_ake6$`WNyX2YQ)(;p(B0%Sj7`WNH?Hp_jd-Tr z9$$@t9}?XnJ-wvEiO3#+PLi`7Q%;@>W5k$ zp?@-{4`&%yYGa#nuH(^iWTzFG!QayOZp3YA)9vcxrWvYblji9W2~?ct^l_wZ@m)q7 zXj4^5@hR*%U{lqJYdZpKr;?uMmnLogCZ$R|k=8mBdhDM|cT1D?&6#rfEi$d~%#&@z zetxGLbDjVja&q=19@?m<9b4gv7LZJKTtdGEE|+TJRt+ zQMrbUp~X8ZEz)GBEOUQ<@q58%&k{N;LTr=OL_3U-$|nX^imj8kn?eax_MNjLG>4OZ zq5!m@vNgDbbs4GSyh0=Aad04Sv2F?LUcO@1Ztb21buF{lW6=ZEVn6U;$kyS#H0nG& zuEKMZ#3nMEybzGCzhBO{Ugdtj0P~#y`C1$@_eRrJb$U_H&4Eeyk{TwYk}j%Er1U1p zvy(d&+IZr+%tL8C8w_(gTKaa0k*Tou_CY719<$J-oaLvG=g(Q5i>l!t8N48d##GN1 zR4IE5Xt*!Q-LqGMqncB3>$5KMx@YipzQKUk7k^|g{jv}4*-m)7n%pLzhe4Kax={Q5!+ zoUuWb3y!Oje-B-vt4`MjuM#}-7p#pNCSe8y$XH?1KKu^fT+F>5)W)2o%WtH(9z2bW z-7e)jm^O=_Vgsb3KC8?!uA`oODy_M*i6pzlrC?W#y(iPG^&09$m>cf$Yy!hX zNOsB9z$wbF?t%v*0u*Y zf##OL2AUAOrLr)5a`uaTsay}DFtXo6O>HW??Q_DOMrA3c6n>`FXe`-iFpfsY> z42=iU&yecFeL>5~CEs!v;R5L%7i_aCLKCdPZ)1*5$G6qBzAr|6XN0W%488hnoX2uZGw>C5J?RH(yqITT(1M z=OcPX_+0gV`ZjZCU!Hb!4;#4f(H$JHR6><}K5g)dmu|4GK+Upu=jtSZ?seOsn{*R!ri$)BtoQ+NxT9<7Wq}0el^yRW7Y?g1*W& zDl2mhvxDrPB(~^M+6wR;2L}ygXplIhQz;Jr(XZ9k;yd?<-wQneAs5pqCYYYaG~%(2 zH{3`sMp$=QB#ihjz69yPKW>a}nSCMo-i9pn>X!)|ASGIuik$NQfVR>7BIH*~0na>_ z4!mZ|;LBIGQv@azS+F5&4*5g+XU7Jjehi+S;WSD(J87vPhE`A9(|=R_>{PakdYtF- zOL`Qqb>2|@!A;m&={-{TW%R~c(Z`hB&of^3xrJK?eYAKd6NId!;!%Eou!^<`SaBaG z<6{ly;UidtZ~wyatX;&#i%PDY^IM7^fYs{y|fk~-lRobt-GPwO3 zY_}!j{fbQf62+&aXfNa*T~hYnilcA&fBkkra&p36u#+@{#$POVw||@T>mCNmsJbf_rDg`^Xk6+ZgCFsy6@LJF*a+6J0|D}U``Ikz82zm%%lSbBDhIr*M zi;E$d3?396>DxNp6iZiJF+S9aR?EHHEq3rB6Y&~j$;~5&ZF}<~Q~6QoxpV=2AF@}3 z9RG!SV$dl6-)K8}B{kXyJ^6%^!yI;1zYGyBc*#I4lnh=@fE2A{>a1 zN*Vq`CpRk~K+Qt|Ru{v;Cc-B^=!X2c9kyQ-#)p@^gQJ9wfwnp0?LINyNh)KUOSXqriQY5!DKhBq^DmYO&Sw^x5&pIOi2NQenj4 zdeI-~lbL5Wt69+FN=?q1E;yn zLrgq#q!uy3Fxgnr5`$~6{^3QBE3HjuCD61mKc?WSJeM?A)C`12anaD)J$dR0#bA`d zQMsOGWIAlOL!QjF1Ut+LbS5V!sy^RK>^oU#GmPXfEoV0YA2|d8=K+Bc7DZ&@TJ(Ag zbF1~9_I%V2;Q_k?x9f2rwKfZ2Jy8oDUa*qtS;Ql&0Ji*^t?>!HfvITamny3D8UkEB z?F3{iHAZqQwTfd?FYmhXkHEU1Srt_mIY!Hha|;Mc<>!lU7a zsxsBn@R^Ylxe3uL&0|AcA7w#3 zC+V+9mcv8No?12Z?u3CRUZZk`i4_9C7kR0&J1mLCsa`Op`Dg}FeF-G5X-Km`(QwLZ zd~#j@vhKo-d^cFkH9v4Q1TThrLR9Bqxy+(I|aU833Is zEi95C+~ZWBzbp-9GLPec0xU%#w#6ghXovoCw4~U)d$$S_vE8fnNMsAjzpo#|nS^P4 zx}_rK$vl%rb@F2vVu_jjWbqcqQKffG!G0gbq>G!ge7oY8~0q=mRiDE#t+|s>MdnOS(s}Su3yWF8xgTI^)Ve(bF0W&uT6uQ9%q5OWW|+ubQ9uJ_PWv5hhRe zY9-f*pYAB~m?OUkG+QW8D99}%)p3vZMk%6x3Qvx&w{5%n?Q__Ko-_K~W1;_;@}WIm zqWIM|RwzY?=ppdMUC`Y<1-S!J0@-=>lChLk!<}15 z{j8L`{;M0K>?3ullNUS_s5juma}A=EiNvhxA&nrgu(+XoE1NbJ-$FXBBpuHH!l7v$EjI= zh7@{a;71phTsM~6hKV~bgd=)BG<%(US^e0vMZlAMZgQF_j{Lw2G!Il!Dys}1g*RB{ zgG&kY=rQnMP5N5!R!X+F-`Z(wvTVd3+fSKf1mKUQdpY!+lq-_VmCxqmT^`t`clDBH z|BCRp>wS-+cOdnSKID5ve!035Wgv7PfsemQyHzGaDe&lxCXrV0qVrzQxeA@;X z^qSDv6f8gEDS=f4frq`YUv&Zp2Prrq?CGY`)y3$i39YWC9jBlWy*;%Tr=DXN6?+Vo zTG>+3;j)w(&6%z4d(>EM78{wFsJ}|K*vk9lyBZa^7>G1@oZ?E0LU=Z9mKz$_ z3<*~+{-r(>ZYrXb;XZwk{fqH2*b9Fp_%Owj6DeG}hiY+5+C^id*=3#zxcHZ=rw=XTyJmD} zs9)&bOgj*j1@J!eX?u%Q1|cfkjOwvh zw7m_-#BD1Z6${P8DC_Xpxqs2d;b&py58k|cI~HW7{fF$2*w^+HZhFT)!%_%FA=eBC z?Kx&&_`m(|{S})+Q+$bkmZX>;ODr8OSi(zaIaBbO;o81UnnPV2&5+_D?a9XNsEs>G z8XbHS^pLPE*qQw1hS_X!b5V(s5a2iMC0 z{RM>S$}^Q}ZkR?wm0)c3)fVN{*4zGU8J(y?z z*~1siq$tFgdk^QAmp@a1zd~!Eh2(k%RbhXG-#N>EoDMSqyNiT8!mJ8@`Wcy_9hdgn z+bB@dHJjoy3hPE?7ajlXY@OBLEh|2zf`|?n>lJ(M}%x?~^t1(z}#)0V$N*j<|Bcv;C**z7I9?R|Qv> zjLjJ-5-M}h%$YfFYtC$#Grvgll@#gr&l=y4T!Kmt3D1;1C!B)}>$^rv2Y${AwW|n0 zPnruYS59ZZxUCtleNlIsR=xo(P4ZU8ADLv@TFjRBpKMoTa+`QyTl7=^ylgOQx7x!h z3D`uYHNdncs6cuZ1@(=SFgqJ@b)339V0BgUYB5hAEZi;4?lF){IB4+pxis*4(F=Vr ze4Dty*z4t4bViiyuhMj_YWXAK95o=*u+cmkhkmoD5g{EpI9CUFqodW%NmKu0-vJ4M z)eg0_-9m;oqrAX2!Do)Dy2tSALKpbe8R3i9ZC^**)57jB)LPrOR=8K|f6yP>JU8K0q)1NX4GQB*{ zujX3mt^;iKxbW^>xTDa6_}W!axI4*ajkrxoD(JbjL+9{H2_+zxVYg;hI=xH;55_bU zEpi!31nt@$?~GKCF~jRz<7=Pf;!Pg5Ke`@OHGg**fGQBo^d*Hx(5dQ-;`V&1eovm* z`b}$Vp~mo`x%m^U2-(Q&80qZ}%hmG3lLZ}SJkIkQ6{`NkS93sLLaHuB;ulxOl^S>P zwC6SxCvYs@e}HQ*L`wD#c^j)Y;e%T6+}%cD$d6B>ko43aWJ%6KQ2fuV>UsPSH(lQj zwhc@F@CLrujHt}-qHPq{ir|Z`_e8VaDDu4nKn_zS4-;NA7ph8f*s28JVqLkUQ;eu0X#dA^f%g)Xm1=j+qSMCY#-WuBiF(Fn*NXuPZcUdJ&E>2h zXK-!o<#AVl*6?ZTc2K8wfC^PAt%+_>r$CA@Sm$78!~nC3mgR-Z?0QoS_u~!w&CzxH zxtgWlhQkOI@mEQVH$lJXYmYH{Q{o`V^4z*>_7@=%+{YT4cgrlJH5VKqSusA#o~!9k zB`Kc&bxhs#0MQTX?_-DX{<#fZJmuWolY~~$T z-};c-+spn~Tjm(LER1+P&wh`cVLP{9nVine-EPl*<1&ud$;WG$#?QSYzQV-2DHLlB zIj-e=oBy!$NWSxdc&3x|KT`tZ(OT zZJt0kT?-Rl_5SB&x*J}1OE;6jHzYi^8n;F?&swCm6UMbiGYxh4 zqyv*TJk3_}sdWeLgfS$3R=O{zLr19!E4EFCpqql7V;S9T@m8dHLH9hFB~4#%l^W}( zTIMLdrZxHcNiayx?R|UtTfYtaOu7)pCIhK%VA#ZTUIh_`ktyL9$?E~R-la|g{h;Sq z6eYT;w2afHZl3g+7#8Q^lWpNy&-l|i$x&e(yVxnoY^^^1@=Z{4&_pWRw|p%=z2Fk< zU@vcjHE$O+OzOcXZHW~hr^@;db|zo+Do50yZ5@0P@!3Le*Ot%L7we4ViHZ`~@yDT? zsS|%UAn^K3W6WB8OgB5~DT$MP!^DJ0etg&}E$~%UCjM;I-RSm7AUxT^7z@}kqF=OD zR2opPl;$?vdEYDY(+}4L6!zg5mPX^jRx_&T+s&c)bmXpo4cZnZUp$?@EZT+Hn&EI|lu!F4O7m*)~)Ys_Vk_Md0F)r9mFuo!lD7SNVmMVCB&x zw(E23>#oXAjMQ1sznt zp&^Yq_f8G)1kf#`Tb@#&n-I%glEoE#+_{dYbxgVL7Us*oBV?ajW;2fA+6&|mlOB-E z=LE(D1$dcMu0eCU)>-O4-sjzMcpY@{K!W1mhKeDzAHEUFUTm+a>VhKFH3(HRDUH!Y zu)nPv7KhZYcw8L=UdB7xmx!n8y3lb>?d*ke!J6mK{HmTfLft1f;tC(ZpK$mO**RF{ z^}b|m>w>WQYi|x!D$^whJG&7`EwvciRA|posbmy9wEv<4-Hyg?Z)sW;{C`OKHcOQG zM|;ek!~Xk!0UXk~diqFZdtxtJGS!)I8f9HO}o{)QXpK&7c z-D?No+Lx$I2Nv#@VOkO^JSm!ywih2iG)0?+0x#xJ!AH~aDpxYph3*NYpYv3C59)D^ z?vV8I82{Gr^kWVaSH2y4=RDO!u{;QLk&4_8PMTQu1lAd-J(9CB)=cS1FDwxY0y=yD z2I!!0Faz)F0U1UGHiSEpYuFfk^d94{hDyz3!;gJCF{PFy;$^i0ru7<=m1vOcO@WiE z8jZ4?6-)EtC~Ai=4~)0x1j?HJsBU78n-Up3SIdy&*8DMm63cv-P4_^fFch#f=k?`h z>pFGAdzN!lBxL?k`OZe^h>1}hls=q(ssGtKp*c7CkSHOot>cd$x%*thFh&yR0QA*V zLWXZNV(29iGhPDIJ89DDI$G$)a0As<7S{mH0!qcX0*?uJ8<T7Jjnx2t3H3PU5k1+u+x ztq08w9;=TC=c2Ghkr1p8{h@d1B3ZfZ2O?hUI7vD>vhP3RvaY%++G~CmedmrPwDMlR z5$sgWq_eQ!vKrAe1bG@2>>n{|)0eatwSfrRk1BvZ=%!Q4yebj6hqr8h=3Xv+q>YWl z$O{iV#$!B}Tq|wb%;_TN=yvV{ox)?S#UJ1Caxr}$`_+_RjQG^~lty~i$+BwrmfaV8 z_#+f%hhSSJ`Wm*z$6reN?m~;|2x}S>_77r!2R_qdn5UT4sleVtBn9`Y9FNbcx_qbK z#+T*`Ge-VXIVkUuu{UU zCJj;as^D3aL){m4gFuqh;h@=GzBHiCo-KavPNJRNH!KOaY*xtUX)5=IVr^Zev>$lc zY{j}&SNc~H+Nt0hd(5Zqra8d5=QatD^B*#`NeAPfN|$ag>R0jviRdSvQwK3?(jAi0brd?__n&JDCr0s%mK8+w9 zU<5e*G|m`ltX4Lyu=x>nX!r#Yl>Wi{Nq)-Zw^ZFCF6Pn+`+t-;0oF0n`K5QoG&OTA8dW_DRgMsb~~YivN2&3lUOI z-%r*#d|?cxeXMVobnTUZsGEWChuz1W8`h8m6<1mI!EUdlXBfMu??bCx;?$dwW%r&> zXR#$=>BMucipJ=^B(-+%MsQf$RD~ZRpYn?SRe2<2DRgO_1;z-ap}UImBmXOm{2^cT z8Kl~WkR0L;>6vo|y741m^G}>m$Sk_MQ(RSM#809f!SU|n3bHKX*T*CQ!U!oE%11H` zg6_dZ$M4!_#Fd@^mho9DNf%pUdE<>EcW_YYrZn`%fWqmiTb4v=rdFZOeB?a&V?lU> zAyZS!s@Wl9N>mw(!?6sGDF_+95B`_-p*o?VX*bupWchjS|6;4A`R%6v$sfp8J74Wb z(*$E>>vVRbjoRTi!a^4WT#)@YOVMLS==x(*PgG^V6ji? zh?Vpo9=)l&wWqc!SL0xP>@#X>9kF0SW{cK1-IJXo{beipt#@o<+&VcvSStcLJ<28o zes>TUcMRPJgYBkcuKaQfiZ%*K_1a6l#o|UI#R)#w%Pyfm~_7sCAL#xzUy{C}D)?>bea; z%1to30)$RkD#fB3*URDe@!rvItTA+hPuA$Hmus=gw5GUU#8!8Xnj*7)wtrETx{=?c zUOUA8R3gV&QA7HHXF2FeIDC|Qe+?eiVb^#+3A1|8(X_}a8$M}&#jE35jNP_w_kCTC zkY1PJ+{&DqMSXes4T8viUW|x1z`qQR^5hHx6hd?8Ej&TA@A#=LFrmY+VtrYB=XZ<4 zOJFOh;Uw?b)`Jb0%(!0(O`_v%O)ZNO(AT!(+m97kJU=T+({Fx>A+xCgCjd;mJu}Ie z5@=$2VGlu8*RZ57Jxdyo{TJi>3;26TS~&@{0$LlX+r81m%pvXM?vo$F$r^X4URF_w z;3V!)-jwY!+C{ZWe8T0kH77p_qj_-<4Z=r{yrarYtPv9Jr`%8TP$uG#Qvq)AxDqH> zIZTxfg$NO3RkxW&nI8R;`Y(gq3fm+KLv@}_KR+bXj8Io-wjnBn>-=$+wqOK&uXi*-T*$&-+T123XvX#?WAUP6DxC4)xz{zK z$=?&n!~U_!YOkMzPQ_WX`BCY>D_Uf24o|zm{r@tQ1|^ir#!zSE<@C=oZ7RY_0d90r ziI1%`(m9&&WAL!{mi@^J7?|HUeYBy&?|zvwU=ZGIq*?p@iKODN1LFo;bc0jkhClNN zOXp2wDpIar?^cvBO-tarLgPsJRf=mZ`#vNU68s)*cvCIxRyInp>h2Pv%z5n@V;`$x z{~F=;plrbTV%0t1G|H}qtgM2HbILi;(C<7X;pl5g3FtJjw#nk_;f4Ko0^y)VgW=v; zJA1HPpKr4xXHXLBVRLX#O}yC7<1b05!pn?_F238%S{kUE%P&){l#3f}Kg}1UX#Skx zQn3gN$kZs7Mv26~TpP^5Fu><|WXns4pL_wh+3SvdJlGsHU(5oN5yQ5EmL9KXxqB#aSrFO;hz1K`~p z^(A)!Lm`Fpj@BUPRARwRqUAP=r2$&eez`KjeK)kd>_+K!-1m%>>!&HCoFNk$<7 zKwisu`g>yXuTQ`?i#a6}wXabaIp+#meYbZ+_#X(gBxtzZfVS`UpV1L@M7P1S2v4^H zxboDO;|5y}0t-PLjYyCPzZ(+bdU@=#mV`R(+B|QLq5q7&wd~z;7nV+kcJDtAIe{o&Kq`qu&k3(F3m-BoUgyYZ~>MmDG&giH+D;{y_lIM*4tQh*aCuMw`N(Xp#pJ7I;uhUr6+N3f z4^7XrM>I`y$4pjm>`k^BQ$x{k&xhpQI9>dGA3WZ23y1IsZ{M%R>w~+Pa(XOe&CwP% z%4MjmL zqKRe%Nyffm1VTC(lv_hZ4>HTyi5}7hv4i=!m99{v_rbyF=_3Mwk;@E{OY zLw~axW73iP2n$o%YHQ8=TRLpilH0noH6=B!JU(Q6*I?JhIRXC};2{5ABczFk^~=}`}t@`~yZp|$e%Wxk?w^(^Nb zh4IfI7FVNg;AxF{k(GMnzh|oHLOa&Hz+P%krK1w)O^ACn%%5&|Ub^2?GvF<&Dt}|& z>K_gRjK(M@voC1Vy-1sby`tHTi9oCI&(sJIo%t=w^u9-V@(A4`>iMh%RygSE532mB zYpnz*ng!@yC_=PXO}dlIitSAhz3zB<`hyRyy+YmgramJYy}ROQ4M*h1kOd}Tqz z(Tb(nCZU+D>*OeHNN6pHln^6jd$~_-R1NSA7vy z??O0rn9%L%hV8PC)xS*er=B8vr4JK@3F_qXcMp~mlhfTQ*k(*#&Vm=GLSX2m(;N6T+5rEnNQq+BJ{LUqTY9bT zJ?a7_IXI)9*!~++KqdxYN2U#YzBlVJ{1Qeo{~s-jW+9xF=am2n2XPwJbD9-WE&mF)>9p*uok zz450LmJS{e%SYPrblvIHoaKif(@wSiUz^nlT%Fo{~7AU_~?jMZ+peiUGHJwM^n2Uu8N|}p1;kQUf$&6uA27V zWmNX489MXjLbHLH5k~Qm@A$wDs-I*>&86}MWCT5T_ zJiT*YoqRAySNYdGM&#G5uFy`1_&bJS_AumYgQ1haT^5lsrrx+>>gL1W5pLyk|B2-f z%Mi96fzirc=GK2sJmd%giZtcAab`C1S(Af6L77D6KGV0$35*S#3X1Sw2RNEh zi48P|;7n7~j(&Lyqx+INvEmjO$(Ka4Y4s;D79E@RX?R4wAKUIo5E}aO6@J-0ouFqx zEa!@khC)E|=vAzMK}1x1Il37-O!qo^Xp-+uNN%;eYn0lmk8Z~j-+Epv@*S_KpQR(0 zoij+I*XT(e;C!*3l{=sY58JA>-diZkp0<5hN2DYM)NSSNWw(8pY7G}Ny2anLKl{6E z|0H)##P@yn_850&lG5eH8F&s$ZBm>s<_T`B@asNW2l>oYO3Ex`cmg7-aVGrM#>PYh zv5FwC(AykADLX+*uMgDH*F1A-kPAz-gKQlB0;TV3M81EgcUrtH#t7|TC-52XkND4 zP18Q|2(agkND1_>`{WBKId=y_juIWmi4&J_z9;%T_M9O9JO+A8qqk|8`WNoTb$yX- z{LdGx>P`SI^K>xkNqoT-z}rZ-wme138@TCRlS;k9Im7MK{HW^rCUr0eegC{}M=WxN4UQC-ZN z1bd&HqaPbI6?&9}+b3~?62w(@X{C;%JKlOAUDJl{#n|Vh(wc_2K+L z-p#bAH}A(&+gjn3Vya2WW3VoZ3Ec*5l7_>`=^uFv93Ul^D(;gkkPC)Vz^~OXJdip?MJ#< zd&^e(H!Ce@sse$yHOHy?&mnG^SE%iC26)ySQ4kupR?Q-AACL;qLGxi96x)*`aY9f4 z$bQM8Tk-VZI+3rbDjmYhYr9;=Dk2dzka4fsdAOLUpg4>Va9fEvJAU+n3Dkr>fs@4q zxqwmq+qC$T0<)habgk*8a$DBrSl4yhS=)&Bp{Z9bE~IX+wvBjh)xI3oYDxMC6Ma}N zSL%VRU+Lpq8TheVE7tU&41PPeo#a|vJyFRc2Nvot`6LefpZzoqt`I&OH~0#w18S2iv@YCs zxtGa#&$Acrc)iI{V<5}QW%{|z**!%}4@6+D?-mA6)V%-wClV|F+q0>P;j8c_QAo=f zu5d852plq;ox{xG<*Uoa##1)xd%Ryk|N*+ITX$;^X`{ zaPY{(KoB1$6~Jkdf7z6L-c*Pp6v-@O+baq7mC(ok*lIP?UvKqpo*7PER0Lr7e2S<4 zZK}hCC^v$PxAakWz|_(O(rgn&XQfV7Xe|wWz1_+f1|-&YzFr>@0P+5ugiCRO;Q7V1b$T4FLJP@HIyGnf0-w3KWpz?JgzlY%nVu$VdA zy0!X14^RDBb(+lr)1zp&lkI_+nuW;cxjiA0uVLFL_D!lmj>h&QWu?SIJC$^{&rSnz z7W46%ia98LhHl8yxJnW8s1gxZkk|**Zr^mn(8yB`2nc^v$%l}&d2~lKL+)3_XcqCj zSj^N>F;$g}qWM<>+7ucuC1zQbO=K9*GV1?BX?dw$4*OQ;2DV4Znn0&${rvdLE`DH! z$h%AkxUOt_LacQ9mANfk#!4>l5u2N4M$gePdfmC%8fn*@D^5f1C8xOL=RXHy6dXoV zMG-vj3{Ks*_C{{@42}*nW`5fL_AzCNYa3AmDcBoz5Oo$n3r)~y^1sF8avNpES547_ z+N+eWIB(kB@(t>t2Z$fiLORHkLoJ{wD1FFoQN=^>u!MX*W&dDefQNg}a))f~b>L zET89$UH&*5e~-vFt_wIHqpwv-q$ilvr~5wmw$^)a>7#q>-Kpn-%}yz{Le9N4{3V}U z<_De-t(BE2fU?RY_>aX{@oi+>dfl&?@x01*GrvY6iQww1RFJG+O0fE}G^^ zDZAc#qJZ(utyX&GME37{a(F!vS7Y-i_<4NeDGxU_I(%=HWVFF~Ta0$Ff3e4_k4f($5gMZvDk!LQv|d?$lgOUYJTC2e-4L9G`l}+f z+VAokQreOyQG1$*AJG8Rew^={4F@#^DgI~*^rM9h;$1jLQc|St-v`P?yT6I}4xu6_ z#|ACFV~jG|uz9G!Jc{{q&=4@@_t~_vzL@*x>D*#aBX;Uf&`3gqInt{Z>@LRm`g0FU zq1&)8U%j6}NG;NdS9~V>yJxAYb@tL8rZzsDC4iSQ14X2qB{||-WFPW9Q$Vz%cemQ^j|{ut>jbrd$w#0A^tT%`bjO)SbMPF6X{vF1oeFc)fgW)CbF1+QoWj>pcs2gNjK)!q2K3U3K zBkYsOif#qU01vlvE;$cRphUlH`{_=VgN6~L3j#s13bkfK#1phd0P?h1j=Pkwitu&j z%_`6Dgy272#9Y2Mx*`51ZYH-0 zKUy9%Nte(F^}I8@<4WTk&NW>gE;|;j`ck|_)PEmLa}htF9%puL$i-Gw+EkpgWq^dQ z3D|w9eyE2>944wsBk1Xlh>+!#W4_ZoRv=jKwA_2@mfpd-kpQoFqW-6l%gW;PFMQ$a zO1^E@lJxL!6Iq*hbpWqp@A1_X*p*$2#-=hw!sdsAu&3+uj_X?TD=B@VqNzCDDn zD2x&{=d8an$<(c0>6k%7$DDerlp=WkP;EuLw7fz6+k0}MQiSW<7oWaHt9s>-+X7`F zF(?p8&lm&=^0y#CNlC|jr<)c#2qiF%?YQ&A{aSsE;~CfflBYnH_i3}4!2Z&NzJ_$q zlkEkR0@f{XWJi&REIaH`xrCt2!O5bQsy=@)1G?k#OiEl{ z`&XAGjD>n+{0y?qEx=rPo5Gg z8a^cLwNaNow;9(Gq-s`e7=czgE0oT@L97kG@=H%Whi0@H37o_}-C{C@BPWY){`vZS zLsM#ysqVYXD7y&X(Ur{@-3sf4Dz$MjZjou=M;U>2$*1tw8V$3biD@S9{%VIq*fN&y zevr{p(G-UW*(}x>@xTJ(%{I;GDJuW1EegL9ygOXYs`g^-S%-|e)hvIb3{P8b6zbEQ* z_nK(VYNN6}Z%2s0)=cDcL#pK6h_-xuMQKGUj%vKnGv-DFOwQ+|h^Na`JiC-l#}=jT zK2m-nac67(`w}LsBVKkmI%=I>CXcinc3*dGJ)=*`kG5My&KM(>WT`sI^3qpuK9{4{$dwyC$CbHJi7 zXrKE-#k_K(ai$yhMXxGc=H{llN^>3#{APWSiD?c9#9a8as*IJ41eP}yLr??-512zf z)U}AsebOr8jg0S&hM`6H11rD5lEdzh5|+14;_}d0AQX@QzUz_xqcf+ku{)G_TxP7% zy4bA%*QP-=5%Hdg3kLFga5}qK(AROb03*HFb~BP%!!Q^qd6+;RrxED^_Q!YU$L*hyv}?1RoFxC9uifhs%G>jq5xOH) za#PC-jb)V7v?~G|4hp0GNUoAY)#0*N?qirBs|_4}Vr=Hnb$QX8WnT|1rpST~c|k2Kje%uk3XCH(l{u zldBjqHQ&nmZuQjEUaFI};Jqh8qhrsS#o5UOS-AP^r6RHe4> zgv60v(*=6Be$?hc&iH@dcoJUcL#M`LBCTCyx{DzM%LXntz<^AwXz9d>4o~pSen?A{ z#3&QCd3E>x`lz8VkmUPx{%96c>xZzc!I8e|kqkHNT7 zphgRn9!eb4r|Aqq9cD#sMg5PL-z(85^qmV=v7qZ@VgUxFWg$mQ&MOJuHQU#P=sN&HAj*=&)-^!+0gamJ&Nv-V5Mb4f5S>=!+ z!BCik?R12Yn;gu?n zuTFMjp_RDNW4M0UpXQ=E;TXv-T7Jg7v|b+`hPi$r87hWCM&_Qo;=WD0LWt{Cyda1D zrcgsm&sWZRw$|&E@naC5O|hHqNp90fiToFb;lrQk6C0b=XROMv(w^k$`B}Ju}P z9nvX0uXIT~Q$04QkH3*%kE9Kurg9#GDo>S_?aaiQG^P@6`hu@;Ax-Si&+qiH8;1_#eBLt(%W;Q7oO}}LN7h{rX?J? zBlk+jUULTS&5Cr+A+8e4|BdS z$*D>`r7$TM^VM}R2EyqUi?`l_J3N0Nb6}S(A0Ey2WMmlZCY?3s@S%$%s#>KZ;x*SD z)%=bKPMBwgRbLE+u$}y~BZqJsY}atQlUA8vO?tGe)u~_8ivMn+mkA}N4YeYFA+abnA)k% z9-{x|mIW_Fs?&mj| z=~Hi9>G)mOsBKG#|L|oH#%Cc`oO`sWf^^Dq1KvmV`y2z?4PW8RLjwm#;gCdYFC&xz1brK2H40C;`FEZkmambq+!I$0Nq?Q`Z*U|!<#t+B{tPokjN4;dr<^P~}@W179aT+(`v*@(H<#{~%$|j~?5>a7Cl%bW*&x$I5b*HQIdic%669eBc^~B!a`4Dq;t+c}J=(H-yr%C*GG4XUacs|8|b2n;0uv^KUY)a%64W zE1u^=vY-V;8!+pt$#yU3TYr%u6SO{@ z8$Pf7S2etIg&kV7;eTmrvbNX)8zAhhS=7L}Z@ zeK^i_(#r`LZ^981J3rtUPQ6qzL*x{0>4kXEgMZ4?m z)HOzi6BCaKTu?O|Yx&#dcWA@T`T{5p3B=R1bmbd=lv?4^U~+!M_4)8nff1wkwfF_4 z74^q<{51NXdHPc=5S+8fHc&|&Spnrq#W!WQ$xa8Ab&)XbXVU}@#Wlz7 zi+Aq(v$2F3A43lJ1itPy4d1sj1>4^(3X2AmnuhxWRtpHF=7};Wbneq@t!{q;GfrMA6p_X?o-?EZV*gB6{>a6DTCZm# z;7Ls@Tr5y*oxqwl77{ga1ZDJ0JtUToO<5H^-xoST2)z5 z$wx{=BE;qaeZRd}xj?z%8G}~z`mHS>S zX5mrmdcZ;tkb}ck3N7%R(;r%^tB=~+9!+WTw`l4p_28D8GlL)LKg$b54w5)V zUWdWn53qZb3tyc1T+STZzc_~MD;ge0lJfFp7Pg0A)W3Ax&avBBS=Jn@D748#w(T^4 zrI;dY@iVjHoHRt4D1IP-w1F2=#HKXJ8V|9+B#SE27YE=*=#OFv&CtR7MO}I-qMX4W za9yX~l{bpK^@q(rTdf^EsUwyh=g*_d#TOB7CQWP`-NKx5l34ZQ?W_B_`a?6F0V5zZ z0xHt0EsG^#2 zgm0q+x;Jqy;K?a1;XhjD4s0p#{bR0dTFsT%K3*r$Z6db2n?zu4HxPS*$@z5= zN+-GsOLdC8kyd}Z(>{mIz9)>@KPG>k0sSRTYQEB-i17NpVeiip>!B)V5N=T-r}rV+ zrlZNeJ7&TgQ~?S4h+OqIQv3b05LyVD=5_(mxoUO-?{v6H1A{$d1mI5a0mIkud;Sn6 z^S%z{dHiI*@9Z3#uj9_(tlghZ*jli2WL3z&-EJa^_#s0@z9$#8?%0EMoTn8!58t(H z?)l9M4d*>v5iCHFjhlcIXWTcUtWM1!8$wZJq}_`~V& zdma&CQRU;EVGl3A#yLwGM5z2YnwQ!)G$5Ia1e&J3dnV3?2Cl8PV$gdTR5vFqJzPTi z%srw>Yj*o8ib&@lN}&P5jY@_Ki0CP{u>uF?W@b`KCig*T{@L4MsycsFOLv9R%rf}9 z^;PBa+}WlLSi>n!sB{3h^Wc6VPpb#2{v3W-ElI|N{jndaLvp=h@8_Sh+1YWVV)!HN zn*#cTDsVNFX_<*J>6O@7kWZM# z^n{p%lU%wTP0eQKM)w^~{0O0l{_SWy_1+6pvc zoQ+a4Cz7fm%nF)Uk>?GEDAumOn)tXc%~65;oAm0JtUD}<4d9#g5{_z~BI?SuhlAi+eX~5mO-K93igje|K~?-;*$S#_n=ZSu&!}Y(kbi(=IGagBuASV;;ppnPI4T3o zv&dw(bqP{Qgeh3(HlW(EEx00CcYqGy8q2Qlub^m`^JlAcx!1kN;*;MIb;?%BMmBW0 z6fR9g<9R@x5XC@XYh#sRq?e2Q(u{s*Ng8Ztx^BLgbg(hkPZm5aM9 z5&z|SQgpXK;N{Za^$m?ip3DF)@G6RkKL%W@z7QEk(j9}NYuP421@vA=8pyL>WyiF^ zTo9q{txUj*Pm0{9(fG+^XoUF7REGpFUp1=->}lMjF%@O^>vZGKY7`@;nBekTnVw;LyW2+n28L_c@;Dg`m z>__9f5xeK73ct-|o1axh0j3g_YEOB51EzZ6$EUhNC}$tUh*`cDtuK_#E82UWW=>ZR zJVA~mG<~-!?6v0oLwYzoKA~C1Wr|`B202JWKz>f=Ml!XM*^nI{r%mJ*dUFv+@DC-` zbq&F7jj-~+PA%=3Db?m6;8%UTeZH=Bb9dF9`2tyCgNLfznnZkfp_W+}xE$u%P**8) z1ip}V6i0b+jKXbAt<-8`1mz*mU2Mz^5e%f}v67y%Wc~je$2VLUnK=`} zHZ#HuDH4<7d;uPJmR5|?Eb=uiw#_`p0cvcy0yan*V>z;*7E8cZVL+9_I(vVjS7 zCWt4$EmNo?gRf-qu8i)23RC?w)WRLxQ#Wp=qg{Vg$nbek{9p0 z;U7>8O4aQT{GYVYp6%O@N=5Un#k1RX%P;vf2#!vY?O%Mn*n9IBE*PNatWk` zI&*=1r&!z>_p{PYoAe^(yhLsm*7gtP17%v^IJAEz;8FwyQ`hQw zOInQqbs{2|$tc?5qetVt0aSpT%wA!h9KHN}q6&i99Z=&Hy-8sScMsw61KUogu_%}Z zqZBS{B2okdLbocP_Tj&5v}KKc;@3?GSVNr29zdwY#Glb*@Adby0dEMK;A!NNi6aj( z<5-iaun7}6d!_lEsgCS0`6;*aL_ zDPgqH%vL-Eb68Lj#0mn7U8+#CC|0LymBI_9nTRg(Dq^{(tP;={ZhnJ2uZabFKNOv_ zq;`^I{=TxVFq>N0`b$rgcP&B$?j?XM%f|l77}%vwypclBK_G|8+9cZNdmf4TGY5MI z9phL7SMm|bROirGzsF6{oJY>7rWD3-yqoVLM~3-@hsRBGn7w( ze(X?9>>6wzoOu(oVs8A|QAzq_L5DOfy$>1=QW@9O#YRE-Q)Y1jGC{9 zrT_hFj_jX=2#2mdMNc@c-KQ5mKhz^50Sd7iZm7rSsh_cR?z>-ji2ucOuwc*>=17&5vXA-^ z9T>*tvo%f*PTX!(NL-(dZZ2s?B)2(EkF`1YXT;IoY@XMPhd9TdCXB9Z+X#|HA>awK zLgV5ZktFBm=*`vUuyRRKq~V1z{1e(tAH`Yg`~>Nt4*sj}`lfMhw&FUMV!6G_9}2N) zGj|{%<7cOO5@;*0!;%uAeqlB{2BZ0mT|>p7l~QO8I(~wD-^jE4e9*cEb=mrfR_Xit zx#S+|d_s03f6K^i2KSH&RlDF4oDF_k(!_|_WsUNJ6&aH9Rtf~r$Zod zuiaBDxWF)o{FIl!0R)GUo7`Ch3o0vuW1{MjPe1M zahtA|OgO`s(#(59Ch&m>1V_$;eXuI>0jvL(goAM*xIToBx9u$6J z6&_+i?*s(}K*=i=L^_f%{`@FzcjC<%Rsmzj!%HZ+)e4dJ$Y5u{MNI3;LwP6ucf@QV z%6r!ja5_e0$PC3~USGJQ$<1nm8W!_8%MyCyrwI87^>(BuuLwkP-2*f)nk3X0;XvG| zf=D@0_9VDa57Ha5a#b$J(uc2it6OlhP{(v!__v549yKhJJjpAL?;GWNP`tIq$-xy{ zvD|0b`SNFO3E#sUMOKZr*yaxif^`)zjSIdI*LjLB!h^m6mR7<&iXtfBzw#SqGscBf zVtZh*-`L*MGoA=$1VRC4BWrjnfz+b=XHhWtXmx-JTK|m{%%>l|RX3=)z@V0?o z9jgbapFgH~SOq>uks)tEG*4p4-?3HpejRpblL%oDBGy(8L2Ry_l(C5Yb%{Ww+07h| z-LHylrzrY`)=cf z-}z{$FdjPU(0z)<2=aT^*VQg{BREl+Bj+e&8n1GXRJKOE($(%|bY2qUvpe^gQjJ&` znm8xc>G{unMhIDz%wyG1%+JHs6%qs6k%C=>zefLHDqJ8gI5v1Iwk}l$x3#O3>)iZ# zcyeVh?kj%Jw9>}Q#CpBWSx;oj;|GL*bSKB_1_fLZ$k#>Ts}2 z2zPzFIP8MdWazWizjV{`=_lPuLV_Sm4e(Qf`XOSDZgBt!83{I)OPr&(j3KiM;IaeL zjfNw%;cRVP_GQ*><7NmWzXN?k{+>8XlI#8kHN70QcS~p7<&Ro$1!1?c?5sdj5N9(0 z_095S&?jW(2*El^%_*c*Mqmmhz~GgMVelo{k(wo-Q(SrWVdF$CxV#eU7L#fP2*2TGQ#ldVgfD~B?<@_9DV zn?mO#Lqsz7TmF-p&t5L#&kRMy_aJT*x3+)^4c)CJzdqHm;6V5J5r9=>G?Z5eLhLHwiE|L6BSpKY`E4-IodV>WuQ*6b063-bD*kjBK z0O1qw0-}ixH9%Ih2kJC^v@g>Wp$q&ED+=BXjm9H37nTA7kcT@S0hO-rab7NU1Ul%D z8=w6)iL=T4g_#PZQ0X-LT)0a<3OoC~>n0JWo>m$l88Wf6-kGe1fPkNq5*1Q)+qDcNbRj5ZhwP0(_=Wj2nN! z-)L1cy3b8tVYnec9%u(YDHmtTW-9154kK$7E(?CFs1-AgOh~RQ7s7CYfkn-3rbEm| zfHe)(YeI;MqnqGv(i2xjSZkb&_|{B9oj>{lnR_%nH*(fZk0r2(_64;we(D9a=YrUf z40NH6MnCC(RJ%;zz-scB`9q07V6i&;Lw7daPZZ7Lf;2VMDHe{6vmMkD zYRDEc9(_ToL8LjvxCjE)a56&fAg7KHEx#>PZCnF^f2I>qOB(Wf?cMkO z1jL2}AU9!i0)tLe zprEU^lksYM7-}x> zZzp3@R%zee4(+$(D~vSzYW?4caonbl2N}4CO+SE16haE%uMI3zhvH;T9||ek5)i?1 zgLBP%Xf+*3bZ<*8ygtR$k9`iFitM>^ay$()XTZM%Ry+PB`=^*1{RR|M=+AY|Oj=5$ zYd|!YKlQ21)cGeeHRD(u|D6Rb0Y|$bG0cd-e`FR@5bV5x;%ux6dbJgZGjQwtNf32j zSNjFWRW51|jugADIBAMO2hGhb>^HU77Z~3pzTZ>Z-&_+V+0?TR&;v5huwy@=0L|1+ z&U>h>OrTEaZ$p>@qDvBBmO!Z#>lV*MIZ;i~b6wZJzVvdxMv&&>D?I3ZkJY?$$vctD z!uR;@?KJ&wc}Sd=@j@q4{8vyt`(201^?c7|=)1LBRf@~f0E*YVE(mW$ zNZxz~pMk^l_GKrd{HVliM@Uk6=IID`Xu!V4L+f+WzmUi@^<-*VejlNg zVhR|hJeX)-1mFV^E*x!8GV$!SK7ijgfiw;)^PYf+Yimp5*7w80^9&jBdk4hX?$3d< z`!9DRu*;8j7}`LA4=ig3TD8Bzly5(=Y!t|Hs{m-OkSg(7eBpbb(>MvB2zRxb)%y;t zWdE)LvQwSHKncD=C~*7Ul0CGj!il}8f4$pbJB0*igKVpv+IZ))nrUfiMn?|BfnGGA zQn}b+0IFICX~EJ|Lm`8KeYO<=f`eR^kos8NyZY)=H#9S060wi`LKHBChqJ8v?u9{k*}0Gl62q3klP(K86Lc2I=UF$}zs&NxXSnZtRTL>AV*z~S@KTt~&3i#}?7e+L#$6TE}WRITo7+{MP~Yq))`|GPp6><_*vW2FAU>`K2q}I-o1IzjPnCZhFuq+;@Zc zj*sQPR2}BTO|q5u-t?Vcx)0bG0rneZP96?(TBCi#`Tg#@k2L#!O#A%Y@h+#rtB;(D zSqsY8d2a)YI3L9e?TqEYZ<>ODrn3uctXfiNvu)mCe{T6Co_POU{vy7_v#gdaTfBvI z&!64(KbT$F`MM)en?gW<>*PDDDteg@w?1DD98=g?d4HY$JBxbj_}kk8n_{NDkKFRM zsrCjiBp8^~1c1FZ;RVjBz*s%cViEsbL;hjp>!nGLW+kW`eR-=nzP&B>t^bEfZ}w+H zI}cxU1gnA8I5<=@MNUpN^^Oez_G*g)c68nMw_M%Y?Kj6h^-Brw6`m^&wx>D%168hZ zd~kbv__Yn3VW3>=AP#O$Jkoqrwr_3MH5TgvHt!klH;1*yG_OB>+9SF(K|mc?A2i%# zk(;Mv&NY9=xy$P|-JG^D{~casNHwKLOAD>|`GUz9BeBYC6Ar$MEgH$9oU3 zfE(~6oQss47~IZBbZij%C z>H(-Y@7|HVlM)qD@mM}?_l(nldage`bvQp>cZ_$~qfBb>EDr;Sde2+WMhx>pN+e#8U(Ceubd@W|_g1q)r}--?A*Z0GX1cqBLzRh# z$Kt}m0+fV^=tfmVC76bmRy!DtSn&OA{h1j}GClzzVHPzbqe7W(tDE0ajp>ih4FUJ( zdq?k+<@&!B6*2AJck4>fH3&q6CB3kU+`%9ELkYj|^$w58%|kI`RKMNQ31 z7BVXbNqX?nlct_?{rW}t_fbps=w2b(hhm5&UTFMFvHxSSthzdm(g;B-N9w4UoE!@9 z20eI*_guJvS5OQFheSjmYH4dnau|QfC5#2*_B`(HJZ-%3_xAQ`85U3NJ9k5`$a}UN+RDmo z2AHD4RcKtT_C}IFTF;l^rDbOmB`E!KJ6Sf}@PuX`wiv!JG4b(@A0N}Tx3|}{wrV3I zBmbJ7{!#6HXD8w8T+4_ao+KwLn}k5dB&RH|pzstH7ABLd1WJ_ZTsYD_aNukchNljm zs`ygeB%VVBKDO>uZ?J{3q>qULr|jIU<0%KR0b?pjXUP|9>gp2F0%zW&Q_%)FVCw~5 zVPT<(oq>VDR_9ZZ$^5+9-qpjOKYys!|A_fq2cHwSAASw+5&jZ8 zcyM}(J;`^MUJ^`ES&eucei>Kkh=kVrpn67m&Z%B1535$(E$PP zeXstF=OiQ~3`l}zIGLG$0H3@jI}eXGd9cNlYx`=0Z7iBFHQ0nb^E-NYur~aVc`Yhs ziC|Z$wGwu2d`!?)1{KM z47{NXwR4f>#Bu|lhMpe3u9g=H@cwi`{d0+)oAB@OAkF1oU%;O|qn^9Wm^9 zS9UITKLiM}p~36ucKAH>XJllki%UpU0`J+}X{Et7vP4DtWVIPX*WW!}8I0-c>r2S* zlNJ+$dV9J?t5lM7cYOfK1NxwGv?!iT@oyjGJh zTmGmheF1g|y))?1=`(TI*JEeJAKPGFi%n)2uwPKZZ#X#&iyE{B7W&MGezy1!WKS8g ziid9t>QT{MYH{PV+m?xKmd3PVbn&KNGkWWgKy0;fH#|Ratp3j<(K>pP50Wx20FIo%p_Z$V>r54y` znK+`QkrIspV)ewHL0n1EXfU!PlcUS|YTfN3yyK%Ebs?GHf~tfEkuQ=nMUo?PRfgPH zSXv&)`ue_Ta1x$orKfW>`aE35^PDyr7v|?1ol*)sNL6%n=mF)U{GFY>#%k{GcC@}| zykNxr`1$21BP%cO@k2kOU$wQ5&-3(XD&tvfzfsKNvGW_87NK)O3L)i z%x~!7Xt9I(5>&$Qz*tq5lauQuCnp~q86AzS)T%#JWWps>m)?6D?|k5Rw}7#>y+2*B zyttTF`}gnPTcE5n;XZwuKWOPN^z-ZBMh}NEu1?!%poS~$xaGsn^pJF z+&C_3=@T2GMeyu8nq_7qKtYnj;3UGA4s23$1s{JkwqpQdj5L zyL*yI95#tvIU!!DG88$x+#9XreS5yDl`n>@)?266TS6#G>*`eM>*_FZME*Ai)^=7l zUK`MalU!X~d|vM_cHS0tcXwxO*pGbZ9qa1qrqMSuvQ|Noo?u>CiwNxZd zTFZ_j%InuX^nZ!WlicdCput$J)ioaV%FeI~sL<3%39(%?1q2h1yTcDvR4L>u;}=k~jGg70$5#QZ*sM{cbmz^%1b zqFj7CHZf5#X~Htj%E~Iln(LhW$TqEb0A6K8c+yD$5<7&lH6Do(G*WqEn&nK^+VHt+TF#Q9xAmv*sQzQ#CGUb#IKqPgfu3$C&y7_AxF zbCOk1fIuLK_pQnlM0tWyT)rm2~nB1<_#83JJ@lNiCpld z&I+Ojamjp(XGzx_W97GJWyYXPQPatmh45vVaGNd1YYa4i4Y{)`>Uy_u%m4nx*KM#_ z!ipVq_B1isJv?dL;BWDLx=qi0y)OP2L&US#XixI}`}h73<^THnr@&LK0LHXMovDGr z=I4nluB*ND^z@pee?vz%HNH=7{BIAFwVqdIDUA9tz&ZUAMixk?K^xpkr0C=}X2tdV z=J?E=h0m6XaJx>iR4-Ost=D_>rE-*j3altyT~*c9)%C8RpdgFiHv^jE9aE}anIKD1 z$#F~#K&>Ln6g1(*2KoHtRJ8_0AGDLug!gt1q!hF5I_ZQhr7dx z`t9<>EfuxEM@sB-F2sM=Jo!UYGrV`JtHF@j4kBRUe4fl(>9QEbc-hdGr~fU;-Amr~ zom*M5k6Bm{xm?0e{Y$f#pa-8E4Z4AJR&$T%i$N~0YbriIzDgw9?@6r>Cu{Y*Z3S7T zs@yG9=z~0A9f$&pg6kotVnFJO=WVh%FfV7`tBnF{1MYEC#xn zAeIWjajTbcvz^zhRq8!UwXhcg0gwSe9PzzjpRaZk6?V?Zh9oX6W6QH(01r;8oTq+w({v$wY9vBMpx zRIAYO^y>{pF*cvf89&?T)CaIkj3B9E+aCbzp$}2Qe*5;#=dN{Q?$f7FZrPlU+nrI- z(e(fhrNZ2PeL8~@Y}|i3TBwv*$R1-{)H$g3IK@g#%M-O4cBO=-ZE)d?AYsh~HaClfMNr{uK`-HaF z$I=|dy#i%MgC>52Az|nV-u?Y9y%flhnKKdUqMu=$UF(7}m=^U^c^gEA|8ch2d?`?mkOn|-GtO^Gc z7LyqzzVTv>!L^^R(&jW_C~QK(^+&WLxpKHoIq}~T!&D(Aj6VwVrCZF zX35N(_;2L=>vXW(;#nTeu^`5waSN4xE~fv@dyd*-9jrr?soV+;2xD&d=k@ z6i|uv=D&yKo38Mut7V2-=Y2BR@w?sv;`20U!|9{!=}ks^O5rQy>s2ev$UzRAV}Dgk zEv&4B7hH>qi={y5bRfAFiR~x}=cX)qE1B#je(}Q#*n~I}Ss~t1$z9mtHq!Le!YlR^ z`l&*?>v9Y9?s23!-Lp_;7x*RvspRe=Q=xgbFpKhACEe|mCE0;FK2A+`KGs$%fRGM8 zKRN8X&3dY1rc7_*AU%6U^T0D(-zdw<*6=xcbi)&;E}Kv9whP zxa`_mF7q78tj&tjXC^7NzLDEQ$$uR47?s>FgbN=J=So*n7<9eJ@uOcaf%YhNJ6@cA z$v*Qj2gr2&?Ks*n-S$pMKHMd9SE2&{aCZ<~5P*_4xcy(B-$U4<8yeXC-tPQ!fh%?E z!h=b!AE|PeYqezE**`c>NT$RNKeO0D8Xc{=Jmxu$dac_Cbtu|>n^9FrV`D= z|LeTET`XbtkO`&qygfz^&-z{TH)-5jYq6#woE#;&ua>(QE}?BTebZy%Yi})H@GQa; zA?MBKFw)AfrXFG;<>u}~FAi<>V2x>9^bAavY>jJF)L4$_D7kfjSDxY$66!F3>#iPr zu0wG2hIE^q`Wf8XaQNcB+8F{UXVieHsiL?TTA{-Lb!%NaUapUPdAi%VdUz)Z!upnI ze|!MnYG9rT!dK7QSKigXa@Xw{q3Pyxx2S8hoL!K9?hwA2byYCDV~68u;M9;^jEY*y zYPGo-`w_X07jlD7U@p1XAd7a3q}O4C;Tw%(vdqL=0{rH!IFG?91ptwH-yY}H)Fc4+ z+{LW20x(yG=Poe)A4COqoa=o&)yZkCT|o2n%7?T+v&tQhty11R9fBe4j_hTB{&oAh zu6DFUEF*H*iTsuc7dF&lFM=Je%QtrkfGVI~rB2E^8;GPmvBOe3c5;(nm?i!KV;X$9 z6kIHYDB&5XYsX!^>}4Ix{c>*q@dnPzX$S~-o!{EpS~E2@rJS$Se~ScKpD-rn(Z1?_sn*laahW0oHWf8@gXZDze&Zc`W3W5wJx<1UUsa78Q)Y1MH z*P}n><)#mpd%U-c#=K9*wa61D_gmd$X8&q^u1>k0#B=qyh-^o~H5e!`{Od>-B zvUjZSD)w@CGBh;LdZ%W;+cGqLL2grn>THK*%s9>VEbwUV@m65Orgp z>5QoE{(vkp>fv!AwaM^BT1vTH+`6DocG+a+eDZYKb6+oLnFd7{gKhvz9ga>uXOxwo zx}F|XkFaQt%F`_UD04C^~l2aTx1`L)E#jS#tzJTXnv~`7;$m2^s zZ>h#E3cC+D`Z|-nPbpP}GvOkC++|6nz~{}NaaSSa)WktO;ygNoDWyG8pWth%q@Eo` zwj>^nwS90qc~{22-wyaK*xCB3q-ADiuCDFud;_F3C2ZtR{ROf7BY@20$eqp677BsN`FY+( z69qydnOIB;3N)7U9aprnbqSC*KKe;96gL6jsesby`Gu82A-hiV2d#xZIOu0D<<;|G zu0~d?2`iW7musXRp@0oa&6z)rgI>`mnJ$Dan<2fPueVSNsQ7x5^+Z~gW4OT*`imR^ z@&-epDefmg%_`HV{-C=6FbhL&mcetCK#c)_f6KT^N+gm*=VAQ}8Hhi{e0%DrCT?=Z z)90#6lJ0z6M;AxTYWYobY-+A{QAMsqN+Er8PP_x?^l?JgviEzT8j_OxeiDW=T>ppFc?I)PU9p zG(zkeD>wI}_L9xKs;1^Ja8%OP)<@sPbcgmZ72aqj!SNhgc-r0fyjce|({lX--?_0& z(RmR1EG*NsoCQ;@fUG z!rBce0$#j>cR6*qS>D!}ngqe)M~Eg@dU;3OfV@$r)0|+yG<1E<_H8E-Sr(Kgv#64E z{|A+4O5g)}yi!%1+ViDN(o*4Oy!W!kk2Ba6Kaw{Whi2D&sZ7e)7)2a(x>Cn>%9-|N z(GM+(!K+gmrv?xu0o!rIkq@CmP-k)V4UfH9S(EYm`+GGq(SyD|(L+FpSPBmhmsM6y zbZXY=4|zfO8fS#Mi!IJeJP7cMj{#i?fCl@C3edI7%8p%K@56)hoh92(dh~|`C*!`R zDtqw3eyorZnqs8-+~t5theU{9Y5nNj41F$OwzR^br?3WjGDPlyBOn*JN{>nxyI`Hp@FLH_tyC& zheGEcyxwL5&XyJiH-L1K;K2FZe|vk21C&ILG+4=;HGS+E00DEaFHez3&lo>}*6C?t z*DjEyuAi-;`}n)11wVK4j(fT_+%j|cp^<~}J>~I6(bk=m4eUc(k0pGAjfQT})+EX3Rz;k#OgspoJ1Kqk zXhTi1?Raypv$c|GR|#$xP2f`U4A}52^&0+JgV16yh{$2<%5bWKv7KJ~Z**)6GXfea$H!@lq>hdbgRk*TKU#Mk zIQ5FvjaQgBjcV^vTi=N>OOC3u>V(BJQi31D%+Er~!yUhg@d*Dj; z=j2pr_6GRV<%(L{M0qAfrdMTFb^zTkRo6R2-6rE(6UfHgwAU}`jbZa2Wh9?xMs%bk zB~<|kl5X^C?xgeX&=*wQw7i-=sVVirOX=|Yxa->Bcd;u%s*7UlG{`i~{KSX8c&20; zl4jw>HT%+rOA#CUKS}W8HIh7G$;&^LtE!gSUS9N7OF6b#Pe3*{I(gBVo)07bSFhqnftC4$KQ50z^HohgD&o?>sm!fK$K`F3A*%baSX;N>$#(VGZX6Gj;SW&YL(+Qi``3}G%S z*uj1~zanJRt3n8aX>Gt$QIni+kd{Z$urImItflgqZ|m869$$I)G2k5>Dxsis_~6dg z12h*JUa;el#oc+APNOy2AI9E0e}q08N?z+MRbz?KDA93tr=~Q+foE=F6_0iF!Gka` zHZq~m*EY(b`7`n2aY1`^NbzLweSZnHW0CtbU>nri0j#BeY;3Gzety35Y3sB9y$LYV z(pFB`Njx^+Kl}MRZs8yl{`&l3$tI<_nM<7|y)E8z40^>;IQf1Xp?`mN<#Wg-r$Ik@ zcrTeG7>j6)J<;+0!Z6c=W$@c)+&VT(psGDfJX|FZa8aq~shf~eP<(?Z{O=S1D6&pr zFCx)6o`QlHt6k+2-nUb=K10A_i<_y|he3iI@D3(O2ypg(E*$l5Ih>ENPM&}6#P4>H z?6^pmJ`~wEcMy8GEg%rao~<@tE&4wyyjebXJe}5Z z*eojQJpbpln31A~$BphON|ObXa#!L;wzYdY9xCc;k_NbFO@BApIET;SHpWmeeNgX& z7FM)O6~qtI3{WCXmXxP7WreWJR3k)oJ{!jL@>Qa%E&P__Rwj*GmJXMJ+V)x&)JE*L zOqtV?1AjzgYFkceJ#^RQ9%%?ARxNh$YidIx*am42JN1 zQDgtaw63ryg(z-5lXZ{<+6^c=d?z0!O8sQ)UimjrMmSu%+BhT?{GE6?^W}MG3n4}) z1LNxI$~5dVNk?Z8gr7A9|z-J+_C54e0y(RDH)P z*Mg7KNDtVJ%8CcdbcJNLJ=elpX|N&;cCsu!v{93^Mdr3C4HiCey=QHDi~w3U1Q z?|ZehJ{)W@XV4(umfz$%J71A~h*Z6?Yhbu@-oIu#k&#)d0{tBDR?Nc;tV5vWHs^A2 z3-&!BQ~T}_C+u4W?Zn}4m2(wz*s%3WQDNLF{?6~`=Mt5-l;!N=QfKJqcAIx`ad87o zx3hM=GlAD$f#=>uz!EwEB-eb{{~V&T6{Z)hiEr8RiPHaxwqTG*0efV#JBwW^En_&&Oq^D=t_3|z{2E++id20cKl64yKlA!Or1}r1uT%Ryk07gpZ|W8- zIlhw^ttetCuC_KOz1=H_Ja2~6KEFcMs#~ak%Ft3BchJX63Qb^Vo#jF$CT})pWZlZC z^Jw53UWON=^zFrUHvas;Dg&3SZdjv7|9=)>crr%Lxy)J*aH|#opGljS_)T3Vg-+9W z<<>n}35SQ9d&d6)$NvW#Oe0l9bZ{e6XjmAR^FLW%n&cK9{OFYTxS`$a?cSEQw&a# zw}e$wl0rI@lciJYEo?LjYVDH6HJjoLa$~7;1k9@A{Jga_4?|Zt_Bpdj=mOx$&OLZ% z=jV+yu~REuW>_)FHeG9K*26Yjoo~Eh6~C~+Kj`v$+9btvi@AgaR9M5b!DchfR-(kC z|7IH=Gs^jvYWA>hoZ7U?Gpv61-RaC$s4Ygp03;zag)(~7#zC_R&#hw^ z1!6oS8;W70DQLNinH#-KhoL z+q87QV^rf2Hl#(q6{xtDpIBMCVw0r=#QnO*>%4@Iw3Og|m*r@4nPA#|s?Wi!)$pPhVU#<}H^} zc#gor)jv1w$qfg!D0jQsd(vw1cSiV5#*ayT7n>pKVpv#17IM`Z#{>s1UM~scGOrT4x@+A960sBZYb~m}sTb7S1!4flHv~$0W`3UC^?b8? zG}Q2TV$aELv<8=m$kfX#BOspi;F(~DirAfO7BiBT+gp~~%Ld;XwzGb)3yLM0&+h*c z>&XyXN^sa-Zu%i?XYPFDrmyKKqbwXkjhm)I*rsydu3W33Nq@i4N%@B+6_gNBoIA

|#sfVx7M12aSwhjIE)M_AG}H#Oll0mkvf@bGX73ITf^3BS+1Kj11N zTV$9CTn>E``}Us?v5rU_8#YMd&ugiv=1+wwJw7(plt=35bTC6+<8+Y8FhoL9(=?N7 zX!j-bAEqWoiWH(kl?@YY>I%oVA&kH%>%fX$VGhFsMaIDXY za8_kaCtQ;Z#;!5bfm4F__~h}1bO~cue@9~~ zOLND!oQwdh=)eq`=${HR8sE}#sw}B6d|rP7s>3fzOaZ^IlUsI~A#6=aMr$-gsWO`` zh@aqWXg;1#lCWzu6mE9u9kV?EGT1+*Gar7x0;Vymg_9V9;k!%(YVs|IG#`EicXy8L z_5v7RhWYyY^LB**VKPL$6>q!|Ki`?$LdEf-;U4BBrQCa+LGSfBX)#sFV(@ZU_`tbJ zf=R<>%HEadH~i@N$?@^EPn5T_(nf}7St*wmY;BWKe8`hBx| zDeKx$v_J@s-0K2_;=-oeIXzps><`ZDf>l!%+IiP24`G>x7B2F4UaO0s+l8lTBnJ_j$M=xo^10ENpC|gJ4>^0Z1Jqbzb$1V5|72N?&+tw2aUsZY3*8 zIT2};JS<{QnS>GQ!r5t}<$9;z8C*#f~v=5T;rB3PDQoUu;ge9MgF#f?f>QxgTr&;K1bc|oxCe^$3=zW z-vo&dMpIE&cLw5R4v*KCkFDGuw>n9W5n96XQWT6DvMal?1lrWwC2Wx;yeSE79Ulbq z(C8?zkirSca)r9Sed+lGgDR)E&Edd9gxdnzjfYu7j8!HD!JBU2vZ%UnuXh_4kXm=P zX zFiw$MJR1+F(-yOd08Am~cG=gDiH$zhS2cwb@K%WAUBcQDRmU7v6D{~5pUvG5c%m8| zMp*hUZ9z*KF;@lloo=$Nzx|U(cJD`CP?uAV0Q%YKxf;mmKVh&z#9r-T7~|f>_=hgl zV|5`+QD{9lhd=S{MKlM>fGLW#fpByLtKYmKX z2I(tcmwkeRb6;xEWdJ`5iHP(nbiAC{OT3n@zzuK=usE8N!LMe!{I=Llym8%wuENhGhQ8O~|sTt{2E?itz z`Zk1?`_z~?3-(WHE%hnvJz!cW;9)?3o;!YnKaPU`eGWhGr|5!y_9!2d&Be(X)B_AX zQTKc7@Ng%#@xAyRVH#DiPxOf)d38ipt+GV3hXv4Lac zg0`0MB+98lV0bFa!0QV-9sv>xIZPd@;{yxiOiW(z|sviY*{ySt;R3{*L3wRj{Qc?&j>|FQDS=*IeIVuS9rtez=CN)l3smpQCn>VUs@Tke)5YA+K&Ie>$(wXrGV&zB;lo)WIiFd09iCGhdcOONzWCDFpPLS1^{(gBY<=Tka*{W}R zGtMdEqqw%-g7d<{RG4WT<%<1zKLvCO$82ij!eo_5dHQ5S7~boiCd!q-9%RGt2JwF+}@!^);INkoJ&Ln_AH}Q zvMJq5yj(Kq*W3=AAoQE~;1|38w3H3i-I@2nh-K2*e#;-WYUSuJKZ( zL;T$JgPsR;zq2J_*32zi$Sds9@CdMCt5}m05JIlh}e(>nxSA6^rbr8!6Ze)LoXZyP2O2gpO z($V7K{c3cj5N4XPXT|g0d6Ie0qi%h?c4}ZW zvVE50<<3X=ZV7X>LrqCHZ+gw#`GJ1KePIEq2JRfmvUlk5k|yVX?Y_k4eT#{r2o%t|#{oFCkG8UHo5%0@9tVhUzPcFv?xa8d zGQoIS2F-Mzo6^d=F!REe4p@onV|6eEO^gYnt~3Cf2W)I?Q}gqoMV6B9$;pHk;j-0F zI9&dIT;x(L+V01|rhHs~>B{%`#1a{i{%d^H+|~@k-bdaTzT;-)6TyO?i`13-rSP+# zK)hry5)RLhd4dWIjMAzOO1$v5yl9cy3m&9|f8=^XVuIg}llPSZX?8ViV7ZjP$fY66 znR9M5b`6(C%1&s5X0Y*OdXL;T-QZzzdr;kA^@@L2_e2}w>YQd9SF$gXKM#m)@jAXfdpcfh_t!vk{a*=yNa6hs z0JY{>DqVQnigWz^+)>K0&}X$v`=v3#?{V;yXINV#d5tk*2N7UC{O2M8;fi21i;Ukb zO538-)8-p!a&0k7FjL8X0M&Ck%mi>+`)U4^-$1t%(@)O;E$>d%rs3@G7LyUmDmvl{GU^#3>) zaCRi2yzLlx1Nokp(!J0bM|^ZSpAjc=#8p%=nflDCjq#Egp~uhxODn7Va$lqwS!Pa7 z&|opNr&*`sJ1#QCcex|qhrQHDkP^@xII@u4*%IY#Kr97M@;@CH{1?jW1x&-xP$gYI zPX)e>rqa-q%SOuBqtHQXLBIx*TrG0;h|jY864?*TEHhyLgEJd88a0gJPkG8NMg(oI zz3ku2+b5&|Di7ruN2~L5yJ*H$W3=;O?Z>wtq?*w{&y1{*bSyx zFhaQ9D^wl%kEtR?oG_Ql zyU8BNLb~Y~xr4(ONI`&pTR5L-x^aoAClRw=9ZZ-RlTrvd0EZgx0x@SeKyb=tpr;2@ zA?btSwEYVTF#(fe2e9O}fQ;nn(O@jeTLIwSLUas&NeDoO3cP78siy6c5;_Pz5?@O{ z+flCVSPp!{GpfctaLK@;Ra4Z}^))tZ$_i|u5i*%6azFf+k<_|OuK$!TWcr{@Iqr|K zMtSkxz7nNZ5Wag@xmfd;?2j*TXSf6C3x)D%+(q{5pP=Fm~1h_;aN~aJIA|@p&yAKZg!xVMQSAdQtx|+UI7%>9FUL&stkDj|vIcA`*h0f4xFT z1j1||0^R|G_1>q$`f9fOEKwyDNj%hM0LN~E%gzqn=gJVE%9RQkD-F?>8EHksfHtdM zsRwRsOg%ld`%lpVNFb121k(GExj8X&b4uWc>zf;97M7m-drm+gOe+enIO2$TII7*= z_*|F-fWLoGD3|jgCb?e75xw{jBAK9tiEMO8-Ne{eS;n5JG9#8x&IsMYT5&@N33wrK z_@R2Z|8D#Qz)Fa^%N{6Vi?mdC*7tnN^}NH@J=%yy)RB25Aj;N!m)PR;tVXFlSaLr~ zJIW!27Mj!#sUI^+`GP+(___L}bf+Va9zL&Dvjj81_9`QAakb)(?Om0!q(_wknVdk# zfh-3*yAiJVwBipNP#gJwxrwHkgKIt@*0_Ud(wqDDR3~BjBCd$Y{;!g4z2N_oay|==PdlP z3LIW1e<6blZcjjWRw}^+ymc2>S7vthfyG5!;Lr8#I0Bh_z?0w*mReA?q#qStxFJ9> zvTG!W9TYJ&CC9?TN=ivlQdS0XkpzD#Dh5v6cA6^sFZmS#^WC;_ZGhFa9K@oI&`d6^ zS?SE>ltP-l5Z;F*7w9=IBPB);jy7d`^Z|Ca0Bn+$l~rhOgGwY8zEH$MRf3AwE6Al}F&*XD{aX zk^yJ$)}D!PNG;+osn1pn%hyOQzw6R3k55kz!wP=FA|fAw^_|{5r}vvvhNI$e^>Yc~ z)C7>{pLV1~R)0aR-|>|+#>VNsHYOU*$8W52eb#`V2ln+7D^Bm*ljH2n~<&lWw(tRFJtWm${K~m6goA_H4 zAnyKHYf2*G;h0%pmseKC0D|)OKKxG2gg`UM%E>tbOD=jITd)9YOOMu$61Vx=oA8KE zlneP_+Nr++PX&Vwke-Dqsw*p%5SM@35zWCy?1qV*DYVS=na(ecUW18*yd2}9V6cy! zHSAHwTG9iIyjqVui&b!^pINZLixQIlvSz^O)I%k?RrPWPM9(Viu`?=S*w-pOj(H|RC zFOvD@*pmI2j8&7%HIx`UP4rQwgNHFB2DyChnSgo>7A6C>itKS!!%)XjmZOJ*NXCj3 zfYrw7eL6rL-vU-j+Ac?Eby{4)fwdlB84ix+tf#>isC^)>S5iU^&_Pe&`YHjURe-f@ zvs4pud+VlFrXl3%`Or#%P6h)c(91MyLn|PR35^9!Y*o|0K;%KSh%3sYIDSxu)7CS7 zAQtwgq@?@ou9G3SDCukJXtn#99{*MPq{I295O5uEiHnP;<+6o?zo)2JBzcLEF9LXp?%Stz^|L5%b3YDu|pUfSj1=Ay=}NI=q}JlN-s=^t8&YjZ|xUN{!kQmlyu(WcOx{EMu;bLSr@ESg(> zRIdm!?Qlmy8pfQUFU$A8o=J=bFuXVB;C ze{$RIL)eEP;zC8{`MgT%9_P(j~Zam09$$t)D}MT=7(oz(8X%wy88Mrs9_gg z?HY6`lV$N^k{fJh-t4g|f-3?FT|Rp8xRdI$zv;`1Lz}y=k72kFo&5L9;Vd zNlQx_*oUkwjgZ?+1b4Y3Se&A7rPXXIbRDOXc~BAtw~ zLRy1haJLj}X@As8M!|2K;7We{FTtz&)M6$y?*qc=20uE~afnO?cWKCyCaa;5BV_ZU zFhScWo$^)oLa@b)G1qxm0*k_DQ!_38G#Ies<7G(BaNt9L<%H%7k5w?_fIDH>g$S55%B(ek?NPx?FRbn9v=z8gGPBd>C$ z!TTpCd%*4n@RVV-1{D=mmkEpSb~RGq&%b~FOZ3O05*4>}1f0DGOcIfdi0UjAX zNL7Ek9jL~K*O|s&nDZ?WBBf#>U-J?>xVveEFn@u?GT>&~=5US839iFjf-%r0e<6qD zl?e3b-bGEJOOg$(&q4L3Mgohe6G!B+ie?AMx=8C?=) zOIS})b+v&mcQylix@>XH(y@nxM3|bgGAj~k?YqmpMFqc03}E2d0Ko3e<8pWSEVQ@? z5&9s<+ujjjUskunze{>hD>j4_;-t4$k+lj57~Ye?@_E3g;k2T*1e&E2?u3A^j7#Tz|Dy5@=!#!*6wfMs;nS7CzQ-fzc??g^qa za+a3V?3pKRkz*10Ko0kQRR6UH5Yz_&o-Pb*!AIol+*$(#3@mm48qPalEnnb^px6)5 zADFktOT9oCLQqw8PK>Yz0HXt&CCwG(bc^HYsEN1W z-6$BgsG86fn(eCHpP+26ObT*bdFLAQ2xZ?dp}m`~8ID?vwpuc1Fv}9EIo|GBkIO8? zoduyzvG3dAdp2rE$9&57*wm!kC`KZ)+x$&}EApsrOtEc>oZ(z4vIR>PL|JI7uRP)$ zgu$!k{bwm@JFMHT+ITsV)68h|u`w}f6p2!~OE&z#|9JS~(yXJ8w4C^VG@W%+*89`7 zkpqg9h;)lL-Q6Haceiwdbcd8kcb7=FNGJ_TNZh1|fRuoA35bXi@9FuAPz5gO_X#`6oOn={*s=M(eZF`*StC<{xT}wVa zn!-l>y#UwwUn4Ba) zr_mUjG;(=IMqxCs@1o{lX0sx6D7R}KvzFqKBtFQ~jG{~5)|JH=(2R>nmDZxwx*IR8 zD4F}}@vF&~c<66QJpOL^bH&C)v10yNpzpEIwqhOvQrzCraSiHD^Tjr=w6rv{dVSaS zMg3Y`(1svtVRb#zbmQ`Dx!Vczhv^sil=}Xjv%QZN%1{uPvbwt3__)uaUL63E-~00% z3Nc{9ben(Hd^FR2l@9m@uZksOiRMlU*LIhvSK`8LlYSa&#)g<#Y+9;n`2qP1*JIBh zEAFzNf0rbMZ$DaS{yOlN5d90W+KJ=SCMwV5vMw~0Ag1TP?mfq({Oh4e8u;yFmX*Hd zT2axL{)93G-7nuNmHU2>&XML(bhWJJWv`PU)~ zx;F7xxY*nK8S6}^4O#uQ=g*}yX>~8%a22$nTxJakbQ1wbZ@vCV7W8p@dwYS1e8q22 z8~yyFLdROiOOrg+GgeyYPN$&rK%NOtEjI4OXKWQ45?p&v9M$_V*7DZqt4t9e^wT~L zVIQRl2Nm3vUL%#NAK6SmLzDbqpFFvXpzmSQ)>#9w3-pIZm{`F<{noG+ER9b5ya3O5#)R z453?ZAdk%=aozUa$J{uP^W_z+URnlLGHrN7<}#(jj-_uFhJrhIl+WyPgd*tC(h@r} zZ(~#`K6#g1eo%!-QJ=e*#M2SyQ8PaLWTW2d)0(gRK4x?2U`2i)t-7{$UnGP*cl63f zM<>#j@2l;tA6Q1LL~Es)CiqVZC#on#yf01wUYqN|%>A<{!rWpRjZ<8*fv+i*P|ugA zO`5Aok`tzN$(7?@h7s4FjXe;B;FEnqYOy_q`@=%mS!F6K&L(8UU|Eeml|tB)@g6si zVo;^_4wgoZ@xj;0$&b}_T`|jtn`0t}eNOzFlpWI!G8xt&zd=DtoL@QZ@UFpmQTV0- zfcASbWF<+e)kw0o)=$O4u~iWm48yH{ z$?>QxLPP6V7lUCaD{0cfoHB)VD2K zbt(AzvQgEQ=rQ{Oyit*iG1d@9u?#&B7M4@PuODo5HQk>QCTTeD6Ov+DlVZ`?Q>(f+ zaD1wNgVEV*PU{w-Zzwa_ks@E;g92#${xE(rX{#3mUMUD4w! zU0A{^P<nN#lfiH3HNA%-#Js{+`4Ir{Cr2%sA>GD}iwqM0R8w85$n! z9v*g!Wh;}td8$9w;3w`MPP(*nZ;C2viW{%|orLnZe3(9SK&XHLJNMf;r`dgP?A!mP zAl3~W_Ps@5kY$T=cg&S;R1isOGY<%_a6WzRC^xv~K^v^skAwe0n@p3fjE@_6!i&i9 zK4n&5jOh_M?C1r<=t=MMFq-e)?b!k@OX#D>yhDFs3S79evoH`znv$eWv|{9RG!@!z zgRzCxk)ceD?EOp`xwAq0Ps@3<)_Db;OOi^a_wPN`>w9;?rtkT!)e*NKltyb3k(!K{ zgn2VPR#@Oi65&SU?2C8*wr#bvK3aaAZE6d7H7QsZBp5Z#xYk)0Cu9YEK zw@REV63vj}i7k5+o@lmRbu<{Zp^B_T0HWYyMnuUL1Ct4&HB>ur|G0#Ohv8yENeCwc=qO;2 z*PAv}RZ%%Am$-}u$Giue9Ok@aPy?-gyhGi$?`_Im_z+4UKR=EbsZuzAbF2r9MhV!~M7g(mO!TfcPZfYhIU^M}j#S)V|uqB{(V!J(OTrU3fqJX~fa7VE(1l#WYyC*`pIB~+H#gg=#uW0bQKa2_7 zqP-ij^bzmK>UJG6#)P3j0Pk+tr_YLFiOYNC!K_2O0jOzy@uF|PhU(}Vs53*9k0N}< z=N3f^sljX-0C>6!8<0We9NibkTb7Ap{LldY9`XjKF}QH)Vv6isoSck7nLqsrb?~7| zspbZcM^(XXN*;}Hc*eFpH9<0lNor+PRZ&e1cHK;FB7dB$<#W4jO`!%g&0ne3*+Bkc zVquM=rpqHza$hG5kdl%X*4HQBC_B^B>34FaX(fg8yNWbTVTjTha0Z3yefag0+n$*O zqBdhk$}{uWA&fCEt1vT5onMzR&EhcgMR3dJEAN_R$SdX^FnK z0G*Tmsj4bwMMXs@G`3Fv20ne#G9viSR(u>=r4_l3`uhJT_dp^IeBc3KEUl|c1aTRj zE~uNqsdN)D6;%`nlM*P7%tmwgpsxvJ+MKrIUG)^+-rYqaKUd|vWOX%&5w*{GX)>aD zkiog*>4|Mi8eMUooqUwl6TC-ZCYrJm;FDhO|LXpl?K?Wh@4WT|!dA5Ph+H491_$$p zr_Zr^&M{o>zNY=mmG=GJnTXvs>3YGzo06_CuJ5aU_)$`~u#%DM1(1-a9j~?rb^N@- zlYs$IEZs=Mr+p*&C@O|pFI zp#pvp<8y3I8{raOl}D*VdFz363d1u(HqU=oqf!`TxCv6gSps?li{2Y)9UV}NUU9yy z<0wy<{qjZH(()1fKQo`Tu>kW8@?t=M2vy>yl2G04@K7E;hK88Oy6BhIBN>JEWZVXd zd-$^XanBi*u7B9$MVIPa`J274GF%t&`P1qaik^FwDk+V=C!4;`hg*e-vnNLNeO#p7 z*WXf1hJX8)-Gxxd6Fjsc`}nVqUl3n?9WGdQDKt-r&&?F9&*g2x8hmMACEIFb)?0j+%C)SIm^;~2SFB}sBZFnW~Gj{4ltt$=2nMkTqQUchU0eH1 z2Z8Sv__?10$qi>k_p(}0kQh0?lNscs`F1XKI4o{@eG<;c%~#DNell#+2pX#l^++@W{6f0xqsM zKYVyRcz6bN;>^%{mt|ESpXSo)YDYgcwQ(MJ7zLoVCS;6pS7j2Zo>qj6LvYj-!dDS; zXBx~thIRCIBvD2o6aXxRi<0fqI{MDn;e0cuC~4FN+!PBfi_QF7wOC9v)m1ra&83=5Fi!-CH?M`1>1#U0k_k%hW|lR7e$+fDh}|r_gHfkE62Cl3rg#gHyjmm`D1&a?`1Yk2&gq{Hs$r#|ty5vudx7@n zXdtt>LU&L%z<^2)YS5^c;fcexNWs(<1 zMq`Y&@{0o5YI<`2-`V;>sh60|FGA!8Dij&H#9T`3_rytQlXqy+-&piwGN`TzJ(m72 zQo(vk@lOAkP=}XiO8&rr(31G@N3Cz9xp#3i@ZRPkzOplZAG>|8nbrGkg>oW-kvLOM zdAj0v%tCWpNJbaa`#owKgLmlx8Jk5EkqYZ$*4}>y6#cdr#m(*PDxf^P9<<}V-F=dO z*l`_BeL*unF~NyNqgM^{)|>FC`ntM08|b>Sp;3subLUPhpUOXdlL4DX4I?8YWQ~p8 z%<+q4VvoaZOl7)GMtIX$k$&HfNX@d{qTmkuimJi~A42}3r6F{rq`SKS!nmNsU}Iy0 z=_$G-9wQr@H2_c}**uZM!;0{x^!N8ev;tPc*Mn3MXBVp z8uZ!dJE({~SOQ0CP5O)e8e)p6?&qudz7l9@achaf*L0$MM-CS;+rKD%Eh2xzwhDaU_MYQlkmx@X>3eYXH5Hdnd$nJ zCmS2w(!gVFpQ7bzAlE(KJf7m6P+%scx?gNkW~pzE$WfxJ%=uK9fU)zQ%BnIZKu^#; z;_W3KpO%4je#z17%k*3_WUcv>=G|MhX_ zA4FQ~puDwAZA827-#^W4`5nhA7=$wP8R^IA2k(1|=laVzUptNE>8-FDRql+yF%ge|Y^*c6=eEHHDI)=9=J_~mmSQ}keq@26X zZEk{ip#KPgV7KzAr|$>&*Zv@&n7OH&PwZ2zxD{aHJ(U?37xyF7s?1{t(m5WbD{?{8 z0z*J1c6Rz?dB{#vP*b}H@HkY6XdjHe7c~tc^Q^tJb<*msK8iZ9ToMrYbLPbBpZa4r zyx_%o|1C}`<1Z7fqOG7yGHId7++z-w4n1K9B7)l{QP`Jt;)2O!~C}(MwR6{ zTR`C1g6~>&9V@5wg`4RP*rc7T-{fvbpW(sr68aS)*y(` zW=`+kMwc|(kD*)WK2mkqOq){I3tu#U@^xrS+RZT65LrFo_NnO~aa*y_qWL2f_*W?~ zgwuTY_*Ri?B*S0LDK0*=Ql^_gB{DQwMx@&0iAO?q`K1DpI+P@jU~e@({PKkW?&JB% z3THHl^?D3?B9G5LDkeG+UW_$b1GaLd-)N3lxs+7{v$G?c00*bJVB~NiKV^K#OG(u5 zycviY!`yGe9V_M6nXX_b@LS!`{2Q+Do|2@bYozCiV?K!t`mJ>I=g=Q&OAVN%Wqe3u z!^3c5Wn&XF(bjI_gYgx|y3G%kGbU38nI7oJxW&bt1J4$HT%I3xo?RV+*aLH!jhbmb zviO@=zQdryfD5qv-h?54TyZr{chBIdEOg>Wf~Ei@7loLATKJuNpFe+2ap57HVEomh zKle7vQIDnkC(XzX4@(L9jYS!8joM*tAlRh@4dV^}fhYY3E zIoqNvs=R%lW;w@;@`qUxIdVL69{5`q4YO$|)MBSM3zQ7D(fp2oL zq4-W9_c>TNUxw6ay}UVVx)@rBq!zvS*?aLbQsT!~BbQQ0?@%gy|A+ZAPE{4?S2ECo zfQ)YeQHULXR%1IZj*$>JjLnH`UjI${ z=KU9$@PO=IvIc?i)cwJA*Y{{R(ki`9+}}BVbNi^GE5YwQZHP$AAW%EB;-gP&uQ8pgW+d94$%zBC>vnWnL5B)O|xVuvGL zV#c`@JFvC^4@o#QAIap-Eh|-MNp&)qyMQ>r_lpG7Q=i@dc#@XCL4GLey?oVy96m?i zAIWkpyKmaz(CJ*A(kVcr7_v$^I&y_w>Wyh* zp3Es90g?Y(veT{ct>GN49m~lczdn#V{r}qU?pa!ShM}KSft#8}X?5LgqUv^4m`2J^ z;uc14kt|b*$biF^SC3TKI?g`I?TG~Dy;-`&t5OUwJk)Ry(Tx1|&e7h^?xw>AwlTIS znJTtl(Dx@zp*jXi`z;E39F0Hj(-_@p`68GRy6UlX+_&O~whwBgWv!8NPxP@-ueX=D z9fCI10{`eNi4daWlh61HO;fLMP~$XOCFF|`3M8iUMW5xT?ckxjZZ0miT+jYqII}zV*8IQa z>#MVyBwCZYvnnM$`y5$dHb6Fjq~yq(xX&4rfcWMm|5$)^@ZzQ48jjQ$c17J0SQuNo zw}^ZM@88_2FyBAHnPJVGo?rhrJ(a%sa!@~^)+YK$jNdi+V66}{)s?V5W zq#Pap_caC3UJB*HIa)I_ij`4s{gR=`od1XL?I&Dp9IELWy#6mOtsuoZoqz8WQev1G zJ)Sl*R36`(iuzHWx`C#3@3Un0e7lC19=UD`mB>v zmaJF`xys-utUy|QQ@Nj~R4plN6T+qoZv^b-yNw`TQE(HttEOjK%o$}3&+(xJ% zr+LK1v_c=xrg8os3s7OdC4H+IDI1CmRj5PW8nbo^D|;A=>YN$w_fBV|z!aGm!>zfE zUHs9FJDU|+H)!Pme-iTAA;?y~4M@bOO*;)91P)#d1Yg{7={l!}4x~}_b(?yhrN(57 zzlgZZts2?82KaYnn?(C{Xp8S-(;W%cemQ5`Dn)k38*n(ZP|~*zH0%j+!CzrGh1t<$ ze93}fau=IoACren5$p@Im5eb@HPW7ZG;?)=EPH5pOJ`x|81b0qyCaxp%b~1P0osQ9 zB-pz>-z9L3{Kn7(V%Oi43B8Ic|F68~BPeqtma@9uHp77%=h<~LS8oEg15un`#?^zP z;pSfgdn%?J!Nn>Msw!xWQ`^uz4146|AHJJ_ zQ9kw6&pQcJVwiSmg3=oVn1id+)e;hx4Hf2JVoMtERP$aU)kT$flS|uXWlQyfWxj;& z(ciBg8ccqJ5mXE=cFMH2^+2nGh9N8M=uZhut4uP=l4Hb6+nqwJu*Wdz zAi%s~5cyq}@@DqbbqEbIvLt+PcXip;zV7NZI-E;k8sq*d^8JlAlCA2tan4)0r~`uE zYdgVFSzWeDTh)jHy4an$zs+ZUyPb>DYpp^lHzq#l^j!b^G^YyK393 zl*C*5n+XXWd(#bX^yorvj7x{tSBDlxMn;PVi|yTf5N~ezO=ibanBQsk!<3rDD6o57 zKR;xZ*+WYRk=ZJ0YHB7}mS$!sG8#z;SjdpR*_=~&El;0nJBk=lmKf*U!3;}Elt+^s ztR`+-bcRCuW;Fz8uwh|gq7Uj=ks#AIKAY$FK3KTX=^=GqgCJXLWitWJ+BFe})8%nG8}v_$dAqc)SZna5zuY;-#UNx(U*o)-#0z z8m|+fu7g;6w-HlkNXr)1d{g_(;qA)TVoM5kTrzB_t%2voJFon)q**j#Wpz zJA(rm3SyMVUJXG}(Tn~1`ua9fe|$KIq3Rv*>HhbWh|SP*vMk=#Q`81w=LneLWEeuh z4=>BUKul}*$yC$D38@2tj?T%8XdX`lL@mO(j)xhswYO(=xYX&_hOaUOL%=;i z1wXF3KX~38QsqE`NZ;KQ5p1c63O#zhx?Qs{Iaxopdn!(k(W3_0-zl>#ZfmR|#?Jhs zX9&ppx=a9KK0aB#Pkb$Jrq*a+Em2|&I>v6-Z#$kZ4BXZL5BltM>TU!T}3k|RV+o_=Nq|TAJNH! zDCYv0!+6D)+2em$X!Y>hsVtv`?EhE8YeoB2V-quYGDjU*ezp!owpV-ErsIrwWZ1Xk zWFP5P6SglVOl|kA9ho)HgP=$u>YD`9#P!OLbP5*RJ$|C@%l=)bTl5EAm$%tQb3F!`-)f_x8&H zI|t`-3C=jAeR)$~q^fxPq5%OKs99+lM2**W4@7*Z`^=lQwoYHSYZ?{v;l!E@jlqVvsw&VrZ>rY1=cg?2>`Aeb74|*U)T7lh z)17Fl)7aTDPLTV;xhq4UW5!*k+L-@w%L=no)#`aRPWk%rMlztKrFLS#ZT68I| z?#!H9PQ=A^z&`iKtjCseQQEqMkkq(nQ9oOwy9*Q{cbDr*4{|4~>-STAD9;5Ti+1+_ zo^FQTmA4PzNj|5(MYBl+sar27Gnw#IUzFJMZ6^_6pAZn?6!)(hc z_>o$mJATxrfAhqGcdJ#?ga3BEvYXres}$O@p8IY7|1m1!N`tKentX7s?|F+rc$pLU zmP{{q^g19r5Q;BmQT&q_+Yf6ydFOvR%2Ijb{G)xziiW0QURpv>RctiCn?#rEi z-`|qiQz}CgUf5?TkrU*|pana+?ZjPuWq;LQ{oCY9uNh+~a3ecY5U1Ir8xPnsS$0p( z-{_;XR%z;UbpFDJx|5BHoO~X>(Z*=4Z38!}Cf=I&+7gLy_&}oz;_3}M4O4Xw(betvdSlo%hzgD<&{d{abR3}UC8+PEd9r#X@Mj&%lch#0FzOqaviC(QXu zJkD?hp=k!GO2~5yZ@$^Z*2~t-;JWhxzP#tqUJZiZ zPeOxcTk)-@unG)S$0e>sIXZr#0#E*Aig!ah&Ha#4;Ao_oYPGA~x$p4g)l&~VnqmhE zp`{+R;PZg1!pglXiK-!|sYom>!LfY+s(`yqtC_YHG@=|BeX$)<#)ADyvgU-FYzl z^o{`m&=;?3nzB|VX%;*GtH_ZR&If)w8P4&LCwJ~iF-z4yu`;QY&erNJ(faPFe4Si%ps!$1VK;Gq#G7puGesoL-uWhV7r6@sZ%pJUtUf)7y%k7-vJ1nF?Yq zl3%~jr<(4`PV)AFOCME(Al-?-9SB_p2ynI4(l;%=OuRmlc(Js!6ay{jWhTrb`>U#A zbgR?lsDsiVf5^2}3nMTGuI}%{rI@5Q z-{sXaq&p(eTZ~mv|LgsA`(=o+fQfK=(FFb5*UDv8k3X1e<7KND1XVr2=T-I()z%%X zCPjTgYhPpY->J2(QzqARG)>4{l2ZS^kWh3{6>CM)ipStNW1o5Y-G08DHYw>8=9nV+ zs;X}=*h+*s^(oklWZNnDTTGQuvh(t+K;StBp0pR79lpHy(X%M#{V{|$H6RSMw^GzS zf=P)mBW7DY7`iXkIp#Y2ctBn#UmX5xRJZw0O?*VG*;LNH@J_Tx)7Kc(;zCA_uC{c& zExV6AIG!WJmU;%T}_Mp4_3m4mKv0cf$kEUnzyFez2RE63MXeIYIMK z+8<%#D=cu9`-(K<-`S>6{VYx*H_iJnTiLL2uBNM~XjPq;EI&sg_zDKsgx)}2kJvOi z0wyyKUNMv6sw%6-IXI>NyC?PwPq+FsMuc8R2YM!_rH<^Iudvf{S3V4#z5MRp3$_%y z-mV+~IT59O()D0PT#_bV$M>-J@?zs&-PF=z;*`zGii*$iuV$)}_n!-LH~-|(W1=3Q z3gAZOKQ_xMRw?Hsjb}CGrj8s99McX*D71*G=BU1F3|PJoFFjZDd(3m>Ytj2?1R9{s zrpP0r88i_oO!z~d2gN%-ros~o26%B&CDjz%9`+Wm@iu$^m=9p{4@Ip`T9eD^e|cg| zkJHKex`Orr7moGcJ{9T4lFtS^EPH8wo|#@`6Xwa;aaa^i`7{JtEjDkk>U2=VL%JaA@I zA+X;*Sj+4VNI&zTevB`}zvRVpeIAg=)BG**5Be4@LgV$snyiM7YTnN4=HQUO-y9sO zSdzw8qR}7olVoLOt>)(kBzti?W^#P8d1ovfNfT2-kZvZ}^YIab-~OnNugAYTPS<7} zhe0g`XJ-^YejX9MlpC1b^(0}V_bdpXcepLhs*B~F%U9=HF)HogeZNsP7d>e_eHb;| z1d$%mQW*r0Lz zv-jSbtKgf8v1(HMz8eJ%%s<_Qot>188squR#sgt}JLo zF&5eXym}Q5EI*A;`(pLLod`AuMTR*W<)4qQm8!O@_Fz>`r} zImm)53Uap}n8p74G4j$IpFcB2r<6!G-&osP?uRlfd6;lk+t0GmAGGZYC-A=exhb&xu-f4lx(?syFooVf#6AM7?udJssAAftEGATh~1X?DP$si2}pd zg}_T;#aPT}Im-%RQjL`R@4h~AH}))?G{W2s0_jepGsydCx*7l<|1v40Vj5yf3}MeX-Oe5Iehhqp=Gy>FKe6! z|9)tzjv26 z7gu^Csi%2spAz&BmZ{`|u?pD$s+Jai?mJX4n8Gy$30x^r+$Q8wQJXwze37{erlq6vfg(D{c^}-F|IptR4xXJ2Mbo;dh`rdk1?arH+n9g+-av zfr(VpKtokUN63Rk$<;8vJoZD_V^)02 z=fk|4sL}6I@wVK60)6VpZR>^_rlf1{g;^o)3Tw>II)9$vuU&l+KZ;1Dusc=Pvsv%5 zw&7j<&7!GJFyTnx5F77Q<9+=6Qt-}vr}oCyU{gtmx|EGyee&T_vckAs(p}R$bw(=+ zGP1Vj_wT*Smk%ZKg~to<_`e_T8Slk*1Dwv0oFhyK)?a~ z1Q0(+k^&Z&3#f3|qz8f&pj0VX&DXJC_EQk|F(XLG$Y_)0OFU^=>0bpL-{*HRjzDgM zgR@ApQe6RG;rx*#bFJ(X{~Yvu*;B43%x)Wj0&KQp52E`k7}-|eyz|=P-7(q;GH&X1 zNRO$C!RN0v__*CjyhTRx5Se~8#F<_6ddA+Zjhiz!4q?HS5{>2%|M!I9GkTxp-8TO_ za}92Uj=EnhxVAK{0*`H3m`xJc4HcvuxffcdXpjUytm@tNDJdL z))Kp~Z7O{#BPI~m`O}N}(bwT>4145c^8p+$B>F#e3kQ#%{W-uy-*(_K0yg-31e;?NB#URi`lPiS5YqkpuSS(3mS$Y@%yCf~ z`rlS0P6^(NN0ZtOE!D)uCIiFkd@aV?%}f7UMqD?nget2T&6ktuMPJ#cL|avnDV>)T z&X9Xr*$7VTc*;9W*H-puYNnf$&#(?SaF+-#N{pW^rI8hf$>8SjZ!MTs^&n|5Kf4iwSr?le$dUmIk5!BqSte5W)?xiV~`(6b5JtK%7C* zw%{zlo{0q%y|*_%kn9i^|9Wbhy?zh#;Z1%HkcVcR^5!P|%fUZPUR_@GYN!1uCEAdU zf30ZY>Y38G*_L&RUct&@(OYFr;$`Y|*Vhx*#Fb=r9t)N z_qX$J=ckgitBf`XD|PTm`A<+QhE1n!`qqcPk9k!KdR`zP9oE%YCdOdH2)BlOzzw}N zt%T<3sMBK$+N7Lb``|nYD)RES!I5v8`_Hx)hZ>|Rje5(-7*z6k_8eYWoqXo~%Gyy^ z6e3CIp%NwvNuT%DOs<4x7=OZ5OZ|QAuI+!?w!#eeY{H)xgZ} zaaX2)%?-FmxL^PvjlB{>!tz|$NNnTnongnT2&;Py3=C>uWg936V9By+Bn3f9flj@6 zGB9l8(n1wNo82}>NN?;zAbu%nnDCEFG^R69b!O&`U~3CF?pa6FyWM*l6prz*C7_pi z7>TJB*J}D&qccl-3^`^s!t;a^U**=|^j9VPEERh9M&stL2bcaEDi|${E7hzazu3_9 zY87E$EUAzIJ3T{r6q32?R6=}NW~?Ti$-NRoTC4TR86jh{x$IMx zp&E7pO#`#_ALtx%<9GB8xB5!D7!hUaKeSK2quyULW`tR(_SxCljJ1so5tWd;l?`a9 z&8HjZ-~N+VRmGJp=6W;jV2L7I3>JmpCwDy!GLHUT-bj9sBMONiz|n!|PV)j*>O!!A z44|PS7T!-hCLYoptEZmHI+T_kHYe z48H2S`~hlZJP$^@QkO;=0bp0?rrH>!^CPRNGVRCBpHiMxh&g*^QSp!3obSYg9^8kr zRM9%&)hVGrTC<)`Mp1}K>09UIh|*GT!!}#NFVE{aepvj~ETmQH^~o%s`Yfi@%^-!q z=X%%NylkqWapVoNHOLw~dKPf@Cl)0M)Y?%Xklh}lK_La#Fo@n2&{<&-k#p#upb71B zZf*pxLRED&XjDCBoW{d?6)tJ3t7P9NXJ-fhr$)OhCk(w_UuqY4+wndo!DBH`XgOkj z8!?a{J?bP~r}Jnrinx?F_%}jmH&;IN4q)Q>08#tJ+oK z?b-}4uXkH6GN;q!D00e>6mo9wEH?foNY37Vmxdx*Q&%$tnf$UhS(cHaw7xWd@y_m$ zrx3jO4(}IQUj{axY*_meqNmC}k#Q5x9`jtibx&cvp)A#9Kn0gHS2Af$or_o%&-6R) zv%Ne^+Y{D)184WoXw60*QDIWLIelex-U&@=Qxn_k2c_N_#3jFj6+IOT-z*HraFd5? z-M2Exu(7dWhSc+++Fp`r4N4LGbePzm=(MUcaZX_hQt-^9RGk_GJ z`axKMW&sfiOk+qJ4f}_&F<67{!s?Z!9X-QM8X9m(r!t zy)G(A>bf%LHN??-mlYEx(i=MTTJCRHi!b5BmHd0|JIb&j(l_gG8Btgv#=V8c(044E z8YYI1%X$-<@Q!nj|B{dwl%5U$=kzd+me4;|f%38=kTUS|W3Q^w@ zw#}i~wqM;kTKz&f;c0K0X@3?+v=npNsYx$mHL-ykJ5O=CGsh8=%&lBWSGme;89y5` z5gYqmpL)jmQ1gexMT3|!?1a@UQ@n6sq_F(3RdRSbaNllJAy zgyP71^$qW1`ePO6I?M^H4=Wx%8qIO|`Q+Sqy@s2UOGssR4ma*L<#a5|;P|K%Vx8l} zGgThp+Rn|n?KQLHT4fZBL7;$_&@c47A-?(g;-ScI%EBhbf&#HKf_bWMQg7y?Rr3{% zO}NH037M@xUpJ_K!P*>4Y#NZ6xqJ~4=P444bbb?VS~vauZFV#=NuC{Lqxl<6^9K@S zNv@8S0o`p|Ek`C6%*Zj{iV>dF>&Ha=m(MmL1~Con9Wsb=hg4=&q0_#2D?DmIPaN>Ce@h8(G9%~S=k@d8A#H&DjvQHv+U^UO;#XyXOISLXh6vyT$$Sh zJ=bM@s_dt5m3d&O_+j<9hk$e`^Ov##9hb0BZ#(tg+6+gx3T^g}b868~YxkC)__>J- zr+fAa(WJ9#JvA6Ed*?GTvGA$P%PVc&b~#H@li-&j4u33qt!!^Ip?1${+vC2 zI8UW<6lxwN#>DKlrC;*>YSpg&jwFlB96A~Ago504t4-|D&g_L|UL7&-BkXh=`&Qf5 z*v2O(u#N&2U5vh|sp)WpkyX5K{#ZL~gy@dL``Udu-wkJQ*27p*N+LEne`JtNDth)s_q~(J6X#DoXGj4(nbMhx*%k*O zku$1~B0P8e4#=-?!gXV(9l&T6hTs0J;UON=rj|TLA>6zFWE5x#NYU5l2WND~UKy~Y zKFiEHMrQgp1g;<$*#ETHW`~PlPB*6yl%vSj_Pp%3-xD-9n|QD>+iJkiKK!ZZU(^y} zk=4yMKL5!xT~+UY@(BFDZ{EE5ha4VW0_NXAA6Csjld*TzLV?78wAYZVdT+4sh~d?h zv$=_veGcai!Xg^+F>2)dtZ2{^s>Ovs(EuxV{s3QEZ4WDpKu7rMY*y)1UroRQF$SVo zm6G!E0jlSJR>m(MfS-Pkkz7BhW}lzrE}Ox~1lUj6;-d^-+94}^ z*yRVC!37~$@5Xip#tFECa9?kH-%li8E%;M+|CvlB*t88U3#_3v=En=_b_9!3$>Y4b-0DX`_De?TGl`djuk1!oqBd?YrTbszRoURI! zt%Bd`LHz%t>8!%C?7FUvh)4<#At~K0-QC?C(ka~%3ew%(-Q7~s-QC^Y{V(7D_%__} zaO0NyVy$`2F~&K%#)Tu(^)t$IToyf>4JYfo&rU^>PL~6Kzb^#f-k)A?^xm9aiwe>= zuPE&5>g)e8gmf5_w881cNRRypI%={f%)n0^2v=AoGmlODAZRo(HPr)T`0So~G-cgN8cDuSPNWr%K8 zfp_9-mO{Lmnu2z0iot%|8zf>F1eU7Rm%|x2i(5;WiFx$Z>#!|v;RuhyL29iN_CU)o24@mQq z;Yvj-6Qs)XR%uhcgLXn`RgDBQk*t#8Q~~y(V()GWB1v#|rrOYUUP;n*54{`V>NlUe zlzjE}dD-u6*o~fa$8c~aI?;oW)E}4f&GBJutGPNRAu&uw#G1(uOw<)ja=82Wm-r*i zn2XO&PF6scPt6&$_r!zebjjqx29y8r=!lYnf&m|dUI%~}z4*A0=su#zM)v~`x{M%R zejhv4(26Q_s=O;rKa*rm+N~{VW=8f@Bc{TFchByOee%Z|*c8KEUS5U(;=?MSOrD!D zU`q1>wj0~?e}EhTfI}c-MlB0!!*kcpq+B$aNuuE#HlP559^_y_G`|TfdTg7t*$}|5 zR5YO6f)C_&HNpn=83lj-qT~M+&wJ-{Z=YoImUzqHM}>{7%NWFV4i8@ zxHcioG9jx)6H+giszI^#bnpg?R>EMEO-}9%Vk_PJ^)N`R-?`;p>O?cfFSEubk?+}# z+@`H1WRW!FekUTsU}aGA`mp3@J~D+MGK~Mh7rdO-)|jX&SpH5UW<>>6>5)zQWvp8{ z|7Wxh8mfx8CTyk~^3jYE8YX6(0u;r6@#B+{kN!u<~1GD?tLm4^3W6!617R-*)4CRh+Ny{Be zAD8;!6*>z4D=n(L*d;_nb@-ax?BQXmfvvJoEJb<;>DR0XFBNj)h!7A+jt+h*A)u4p zZSDNrb_rN4wm?4Q!5vauODn~3z2jFLFm56JXw|JjK3{#MqY9bS(6$m(&9odJvRUQz z)dltM+%b9RSjy;2lp{4&wKQRyxbP~Z2g!W+`x^GeH76OM(nJl)xAV=OGYt{=v?Y&$ zv8Xl6k&zWUe9@d4!>1(0mAIVEddDgH7YxCu0EXzwZMD)ImztV- zRNSqI<_CJ?fSvANE%ZNsikDY zZfs_hYF-ghKEMuW`m%+%E!Z`z4`HScGlkKOQ`4biu#gkeL=VIgpX$P|`oE}JaU?H6 zn}OA*B89_IAzeMw1mi#v9X*d>Qf%{ZVUOUel&Y;J+N|mV$K=Pt|EG#XN{Y<*hfqYaq*NdkZemH#v>?2oR#!59^=M zEK@xI_}C+myu$Gkg5A>QQ+XBV;ZY z@m(%oPkonv+_`&Qc73Ozq<%IEDo6f}3cJYlG+%cIgE(Sp_Wi8exXNy9(wK&dA~QP^ zqP4SQwO91@{MK0#?y7&#-auQPFjnE*{RKR_8>5bY!zPnq1W3{ekYP|ldftiGBsUoz z7!;Qb7HL!misVtw{$g%Hl*rK#FQ(5mHu5uKAKbpCc~6X5Tbs5g{o`ZVUh_T{=H{}x z&qAhflv3DKsxWEv+PrWJKj8}KdQi5rX0|KpsIuYizknBzT>ivNVuRzYt$d%UYwB zMIJ#%f~?M}$CZqesp;MqfT?c+V-ptL>xu_S=SKcJ3u^6Bz1?norK^4&Qt{|+>a&Ik zzD+|{`*bD@GWH49!517U4Yd>rul!%+r?gpr2u99NUsvl!$Wvb3B%Hz?^IEdZk-TeEFs=Dr_x%?v#xwoeOi&_73wtx`;5Hj)@)Vc9Yt*yfW zDpNs8$yYQ7G>?FwmF!j#t3~nPc+t$Xs@i2q!YUCPSBy+aLQd0P2FgMbSiNs}Ysa_) z+bqIKbWDm_W}#Udu>NA?_oq5X{UgJTlj9O7jO$5hISnp>#9_2x_trDskLl0`F_0UI z44ESkz%?ZzVosRCR%)~`NmV^LrKnh{S7jXSm+9^3qqer#d30uFdeuAge813FSWY2$ zQ$^C4t+;VB=CIxmtLuKngPpQ;{s=i8mvD7erlkSa(d8@Qx+SE9P|$?z^OJuc%6aj` ztt8*UP#&iy!OLqYB}MPGb}Fv_&K_Ujlb$*NgapPT)!EB)kfpmR)}#u)w3{!Z4E7cg zpimny5&zi}IAW1qOA_HkZMlz+2Oc!?vs3PlUxwtEw;;YWpuAf+Ad6ZkvWWvr6dau` z7*nuuCR&MQAC zRl3C8%7d{W$ECPG-UJ`8&%hukGyr05V{k=9pp&=q^!ee6@6MH5!}7DnLi4Odl#*Yx zGQ$B>-U1m|r3j@H)cj%xw-NAObK@aV#lZoq@_>;b%rBZDplDwLKGS)L-b*mZaa22> zXnMU~WnAs&L{z(8Y*Ozpy;G(Fc%u{`^@aAve)3l>={ZW(0jHCipNS z`6FqN`5Sjw!Q#W#j~@>aYptEH#9^*`9x#OZjMxm=j+ob+(-thCUiVk0f^4dVsk`&1 zGFho+9@@OqJC!f25(bWgL)13k_jcIUa;z6nydGjq)ipJm3v9 zex0+#wXq;Y3y(Bmyg*v2zqrqr*=EPx|IM$6hDNxb0#54zGW#os`P$P@2=HskvBtYX z3UJj-Rmli!2(F&QsDG{(Gv=GlD74PjhUvYYneWzWeg$uO3`4JwuHVyy(4&2jP#7qdngtQk=@ zI*PK+NzIh}5S1&uCmvWnL9ihnnr%fb1%FdW4^Ln=11X_6F^Tcbh||8RN}i90$J5ck zz`zJ(kQ<&?+9E~&uC%x~fcH61L&L2t(EYK57SYG|pPu9(T!7rC=p<{h??(pqI_}4x zS&*!0I!q?6ddshv>%)=5h7jbSTu*#i^G$@fpUw^9W7WYVwR7;1s~D^d>a)NRR-p;9_TP(r>rod0@j{}jNq%t@2K2$ylLu! z8zWs^sb5n2fSVgErN&oRM?pZ$Z9gl3f|}aU!z1+{+86x!k~7i&4@e7QIx;||2dW-6 zj^GU}qB9}X!otFDRG-^ZFGpLlnM7&P z=BEHyQ6hE6WVzOXe_Fugtbl|*9knG!0q2tO2Mrie@_bj*y?3wos{zCAp0Q}V_G|Ho zG!;c8OFP}xHJQ=fdF!R66!(w;eT z&B;^P$~SG&S42m$H|2hc(NIp!P7L9%t_)bnq+Uw072-Cvbd4B3$q~;VJ-wYLA>N?SxH~jVYo^S|@rfmdHz`Tp z0=l{HZ|W(0W!9iz{?4t$8OThQ3&|uc1-PmAI3B}WnX zFL|0}c=(u@`ws!2M^A3`+9>Je=}sz3zHk%leq6{=U`Mh&h%JjJ?KDWYhyH$&&>-wW z7_)JXHlDi(5X!;j;u2yeoC42LclSRaKicQd|B}!keg*b*|E|epx4kqrkxruD0MZh( zF>GXO`&*tNRHMSsP6Z2TGZG94kh(B{ocQxb3 zo+La@QNe3Dr~~YXO_~{Ps!(~fN}NopF@D+Ra4=zkQ0Ma8TmvO7?a5A!=@>L)pDLn+ZV_ML`H3j*KL=XDm~f#%8wYw@aoNs4-;5#JstO z`MpUJ&FE{$yDMY|=YZRY>&`e?o4X`)I9u~x19i3k{t}&fF`iZ{{{G$pP%*IZ@aoyv z*xoJyFD_+ASi7}~)3<=lSbt8hRdTgZg~Az!Vx<1tq~-ar$aw$?lrJIJivS2Nu%EmK zN6f#hu96%Nz-Kp_uhN8x3c1K3otSEBR5zv~v>>Kw%DDTdRRvKxasd8A;UrxfJ(a=th&S{1F`P&edci!Q*Ls~2M-FA*LEtP1Q;#cmkJyJFvKfQVT zkuqEp#qj5c7tvXans70vEtE4R(a0gtGe3WuKB`O%)5AP@ELhp>x`1lA#ozOOJ~Y0x zUw&QzZ|gVJq-G=saOa5;y&SHkNpG+C^!DrOsGYXk!5Ny0>iu_TZ7GY{*~&91$Rt0* zqyF=1a3DAJe|#7~YN#?lfkV(7+52H5VPHbn0R&#bGKH&}z4Xj%5_%Qm?B!?e+-;W+ z;`l5AaA=`q#0%CKSHF-=NA3qkl2zA*-)V^tXRA2 zI}1G(8nY%(*q54X+20CANkk2Pi$q)KS>kn?1n{&xTl#dNqd648BtzBp&CQRX5!dmo z+3Codl4{iq=7{NCZd;pfo%h=-7l>25E=^CzUb%ZQV@gc2F^`1ug|Aq2o6qGaP|uff z247iiH=T&1+`rd{>|`fK_1c6@+T`Z?R018K5)etEqt&2ybn^lG=K&y(QU8emNj#0i zlatn(tgMZ8a26OhmSSHF?`qG|GiEmT2DPe-CZv?>OJpTw-Lt^>Qy=*H=h#$xbrcr?1;50yw#_76z!h|!W+nX`_jpY4uUk%5`RjYC6ShU{AO zed`Oc;|*eUX>&A!*dn<*m=_XLlXvpw)^ZvN63)EOT8!pPLuI8e9y45WCM?`e55&~l zav=j|Vj;tDZq_QYU~fcx#k3U)`4)P;Ur(J%O_<)!8^)@=W~!+_6#gbale0Z{WqP|k zQsbDN7rdkz?kd5c-TZ;W8fjib_%ktk{43{RNQ-k-$qq>bpFO=Cx4R9M!mHaBPPFjR z1KuZGj!vcszW&Q(5z0GVz_pAd4RTc^Lx$PRK-#!?OiF_%7-Hm1`y7W1QhlZ7ti9y< z`v>^F`0-y4NUTH;P}(Qi%whQR;?JQ8F{NZYxQjAXC9Jfq`b>};W`-mMHOadfv{-p@;?qwZD@(QVz`WksSH3Q-dD2DE=-RHlN zLVCo_)4zlW{56=C!g%a0i{`D7E0{{tsWBNLr8fd91u*DMf!g~w6InG1m@MwPp%Xz;36IJrWN6>?p18dI zIo#^&ta)b?`4Y1IpBI3qKz@4sM|o_*Ima*h&vN6_li@Q4yPaoN9feJ> zqVpeQP}e*H9nw6l)?8xmoblH?s>qUMGjdv7Ab_kGzT_uLniUO2r^dM<9$T~5a{jm{ zK|;_)H0~|bid-j|8F?v4XZ->YPbyhy_6@g0PxEEXXSvKS$ZEpA^oJ`R@p1B&Bp&)f zs9%`wyCfVfz~)9iwWuZAMWh5;Pgq3j4Hr)@RhWnn3&H;$9N*z0ITHr}*Rh1e|967{ zushOCTwPs$Io>*VwCpl@*OvL3}rp z3x=&r9c-)U!?1`NGH~#nZ?3{FK6-F_s(p(1ZmXzcMob59;_;QJiXG@CpycFVX&=z8 zfF@&Br&+5>iygMx{A(M4M~0vNlO;guN6ZgZ?nvWkA%2uRA{XgwPD3s z3H!4isVfO`t;_ZKQNn6=i%nz(&l?F?;dSS>P9hHpj`J}0+IYQ*tu8tJbR}{Nlk)i{ zq41w&amj^Pj)&_dNxR#dr>vvP3l&Kal>?4jI~qbFPr$rh<)${ zb60Q-BFc3TzAB|LulMo@tyfXPr^$_OB1PB`4$B;Rmaf{%`db_nj6|d|$+JRdE`D<# zQ7R6?I1hHVH=`hXkwBShjuO0bv1N>YF%-cxF2|Xn6{{c(osw3ps7m^OH^w?;(GX># zG7fT)=~c+V*)UZp%EzbIrhT_x4-whhD+>HAX+Vd1(*w8p5vW;^A%#Ty zHjA<0Q4z5EAD@JV-$HI@E(mo?qBV}tP)VCgG{r2_z%#vKf^{#ZlaH`s)aadSt#OFj z7iLVvt`I6Jw4VSqnFw0F(*IZSJYb}#w8?rI^P{H7){>V;nV4S{YOinePo-(}=>);p z_ko=OMA_Ncl)_QAn%a3uh59&GK|++E{}B4HzTo7Pm8F5t`1te$8_)&x%HPQQj?HK* zJt`f-Fu+K%-s7DLW`2!uXhRIaxZq@;kRTYA4=ZhTN5yKb-vtLb(k}Gw|2abB<&n; z3{YG1$RI{6xZjA0IA3>R)n~)0IKEP+(D4Gkl|PTOPxp|Wa)|iFDpUpoBu`qJuKG{T zMA5@F5q!g7PzlWDzAKxO`VUeAI;BV2Ie+WWtwipj;VO#aB(3$&&1t~%lg%6d(*vDk z@a268Io@ti->Ko%BPW#Y;u#ZHE@zo+a*t;qyMh^XG`DA-; z_CCri81gxc@65o!BDp&ISvg{`pPlEWi`)Ip1aGJVx%skrb;J4iFmd06mhYe;qsN^7Z1% zDYl@VCsNU;7KHHT4<&2=W~wNqW;hv=eiU3!L#le}uv>L?VGAo5mDg45AIta=9fqT} z;XKcPb6w=t=wINM4NM({MKoP;QKQ?n7&hYioQ08=Pea?nPoa502&ha^!L0LnFr&&C zXJVCeXvkRfahzsTKcs=`7e|ooTAkIv=JXsEfhzgDcX8E6J>ZfeUxH2oL=NN8Yj=)G z*3Nb>hQVG*_=pM@g?Kk7*7qT@@>)j1#IV^Cepg)rCeN_Tvk}Q~=pU|&NpPsX|M650 z@TLi+89D^JhDE^Pnt8r1;QR7Y{Q0pM!X7lYflfZDmgZ)_&rNUZA9a!Hjb;1W^7O-u zmrFoHpR*}Rl?raEQj3Lsa(LTV<3A#x&d0L#*0SZ|VI9-3V16XkhzD+DRcF>q|Xp z1`bs|tUMbc!9Mw)S3(ZQYb^mlOj1x({1@IA5fK4Gu;TwW@sh4?n8yw1|=I6D>oVE!FZoq)aW83iudDDW%cC*g&3^+sP z->2AdS!GfrhS*27`9~L3lYC{~-PmE{`_|V+%zZQep4;P1$7q2@&0Rqd0ERf0U6T{Q z5fonjdc=P1#AhS7cRy`5XsZ@-U~3OBX7mp2w8wmIj>YSbPA{_7U7rbD%PF z1f!}$2=$#m8KfBt%7H2(XBl>mKm_p!e>xXpjDp8y=9$r(+)uTlM4JT!qIOFD)>mg*X8i}Z zz}jEV5~W-^JSx;qKG?4un^nZ{vcg2o5{IBB%%LxJT9 z$elsG2CgQ5io$+0Ap;`|sR&`0^-k5tQ5T1__SH6AtMn^(g{gO3aklY55*9IKAl ze$(I2>?`F(LIM zef15l7JOX?iMr#Bs{rlE2F)hPbwriwOCr1QJV z#d))KC@0I?OP$H^qxtKtE_wJ+b~Rj)trl3W8|OJg7yOleGE6;8H#E${OL?_#7bINC zHu!Fg%_S?15>5_a9=!(#2XP-i;o2TLHg|yD_%?S44(H=U5V|N&PD}HgKDt?K+s6p( zG43_Z=0}6cH%K%U{ZBAboC?&=dV?rJ#fXViI;WE)7X!n2VBb~|@KRRqlYGWVq!>}o z=?}8B_E|ta=EKR&?;lRK|HA5Pzp~683`v5nOk$hVX*?mXR%*&?)r&7doUDu*Mj1NV zi=uvquuPKA%ReVj`wDZ1m*)QLHmQ@3JR+m#3dvrk(N2=3VFfjAA6jKok}H-d=}Dl#we#nq zAnji+NiiNafY|Zw6R^MrZ$jd#P9g48J6f=W3`tE`O#hDg>Otryt__?~yM9%QAJuw| zRhSq{))5PKfzZc4=9)N@=N_6Nz(@eq9gQN!_4iM$OJX!68=t!o?2u$B=8Q-Ei#$Iy z+N>H^(!Di0{qqC!5f!bKdTx7lqd?Svw27G+6&0|t1^f*EU-xQo1c1s2))u^mj*izKzkK;Jgh8{CfY&8WQ&=Mr z9Sce~Fs3bT%7HUPm>Z9NsoneSbHkL6= zxOA*#wBxqdpFT~_yNB~L1j^)}OyH5~*8O2NU99X@p(&GyxdHVXm6B0LAlin&=Np@#Npf(LI8`Vc7k)vTVLWY}i6o

rRX@96-p;^Ttb>>zVi}R1w@^#=x0#ap+GQEfC#WZ_)=~<4!!~J31=Wt? z0HT6V4LV)@(y^-w_&GHam&a=9&Jq;cTu##}?CgB8!RjE#ZX6Dv94q7FmEHgX(56MZ z;<*BW0t01cWi>rKI%>_~{rXHiVW#}1V)L$M;aCUw>$t$3DFn=ihik954R^9~a%w}> zrIRJ3s%p*Zx>oAr;ji1`*(Uu3UD*-xDfIf|%gwH~!SH7})?BdT^y@)MB-O|y*Jfx$)v0`p)7dh*CUGV_A#Ma$MhW(r67B?R6w<%L~fQ4u@jrSCj zvwhpW5oa^6EJw+Bl0-0-R=~u>+6!}WwlA+q(0kseWG&V8@5y}X{?3)f4{G1(+ip#H z@6VButQPv$UiKwG-bE?)W>D<1Vz96M%(+|;)kmTt!hhEpcrwR$UU=$xJdmzBk;RTu z7ChJ4!I8{2KF8NBm+`~rP=pkdx7@|PEJSUSII1YOlmRtzpc<|gWlcO^H;6=omE|Q} z*#|o_udgqYTtL71G1+)jke{89-JMrls1IRmwLLZt{lEZw+7z4}SaF*gBrGD*gC!At3x?qU@V+)^ zYHE6R!X#G(6m6K!amUp6rY7>FLi58Y z*ecPsB$5P}L_9oU8hStZPPqF+Dqe!(>#w>uF@$Wr{=Hm3|FKGcE9o=0vLI!^Ct_ox zM!#!Mk5ESrmn4gU68g^|2r4>q+#Fb-ICDc7%k|<_qW<|XWlk_$A8uSGESR4!$>o$9 zYA=f~TVM(A-xhw1QM43ujXX7saj@+*$2rIjauCQv{OK%s{StVq_0?NB!JCng`O zbT&qfSZ+_bDM>*_QDvQ}vJe6L$Duvfi<`T^-k9fs2t325e3dy|=~%^RNz>16Gn6`= zu|5?gf;&zmQg)gM+|B}==}%7|$x(0t=%#2i{K>+c1~w{p{o4~lp5MFEHBG-hy0&@G zsP#%1$;(osJW1vu%!oJ_!&_a4 z7DOrrTd`^Zs{UMkT^-^-cya^t@-#sKdAWIVS!?UdhCD^>DnsZJFbh1r)bM7U^^Byj z>47TVIS||12?KNWQC=)IzmQ)-GZDH<(`9BjC3qz4mZB*V8;&1g_}M-bJVyWdVISJ- z5kwi6+S`4N(1ofLs~T^A-L2vET7tpu@#myko|748x2^#x+Za=Tm?$M=>)uCb!&-Q+s0M(0P~k4=_C z3_$n(^VQa*WnnXgqMwH0pYcwY>ijnUa(lhBsJXuOu<<9eAG5NSeeWsO{_+u_%ZL?4 zhdItB@mxl=75%3SD%vBO;6*B;_z_uObAD12HPW4F<)e)72T#MVhg9%IWwv8Z99|dn zml{HSld-Wo=F^{B>@*7kO-bIP!V*dR>T1p0r|Zm?OdiykPG~;B8nbH$B%Zx2p#bMn z?WW5awpG`zd61MV`+%%7*Aqo7Hj9LS;9+BL&xqexHUX+A2AEk`TD|9VTv|bJGwvBc z9rUn+gxdh=QUzkYzUWv-jep#-=}<|<7&;DtgYrr1heg4bPlN2}H#NuO&k{5-PoMnt zGjX5~N=Mh%VZ`K8TMh7LnMv{Sxq24D>0a*vtYu~~P(_80i-j1f*?f|RpS*kS{WEheq{y1l+#ZH;6UB7Z?K=9r3dgIiMNU7fRD3%N#AE5VM^#k{Rj zldx7Bm*bWIwF0LqGi*7LfuEXJ#wVM!=GOBPD>%-FM~X7GUim9KnV`KKSn|3p_z z|EzbVmghBDoxXjq8j^+$ee;Qm8{K{+0bKe0cQDF?{{^}U$1D0EQv`#0tLqXpx4hU= zy_LT&cc+B&MKLyUk57m9{qwY;aNGXK zN-o>ruzAgq?zKs%iBoKox*ndLnG#)O`+Mqp+Vjznjgp4ya0br{yQM0#fl@iL$nb?$ z+R5qO-ql^YXfjDZn?qe&muTp&>k6GKjOyc` zY@hzP+K_}Waz5HgY^7e~<9*d?vr(_Zah9DzMr^pX5AVK!PS}1yko|45o^qSxLr&Ck zo6M*YyBlF|x{CGj;EsJqK62<7nM7{2S@C1uAM-4j{4Ffr16(n5viOqqNFtf!!56gq z6NIsDOt`##Oxc>O-g=nrT&Rk!Zz6RWL~k(fm9TPS4}lam)#>}O;q@Wo&8;PN60Kps z^PdBna|rIVo@Hqs{M6l6#d7(iAu-XX1Jow3g@+X_BYQBoLe zxI5858Qis;<&`GfKg8KgT5J7VJPYl7wk#=Q{vYd79_GsMp|n5o@eNI-q@>~BW!+kB7mX5v z4A^n~Q+%$!JF~v(x^MB!R+#HwCC5|qk`r4ZCnxo2ugeFCln=2n!$qOAK;yN1V~+mu zoz;AD6~ovuKXCDx8drjna=>BJUVWFNuWg_4=f(h9A17JikPEWyzM5xCFA98J9`EAg z7J{tt{+FQtq@FGoKhN2yyrVT{g?n*^&bJRm2P=O!&@hNIb#U!4Va9xX<8CwJ#%s6v zn;H#{2b6|ry0s{&3Bnv)JwSs_&-5rof9a{*+*}e{U_P!paMXNnR%Bb1)hU;53A>1% zW$R4QweSk_Ko=wvBTk9oWJySxOk`$WxG_q5bg+L&nlwn`juS94nBG!<+5f)fq9tuk zTBk<|OV@1+f;Oy<`*xgX`(e3>ko;xRDzq3T`05#L=Yf)XkbLZH3qRxG%h=1S6C^MF zFFtpl%fIV;yA#oZk)wy#h+%e*+8Cx|PE4E}J-c<(4-8GM?Kju1>R~3f!#5G zZeI1hZjWA4PmXsdY*xDd{fdRLpzCp+H{L%>!Cx)Z7&7T59393|DH_Zv*O%+{phlc- z74SVF_Q|H|>sSg|^I>395`vNseFJ-UUQ#M3_V1Z6F*YXeJhM7kDz%^-e9{sXCs|N- zj=%XNLy~(0KkAAHt$pt^sf!#=PSmS~75FbSpyt@SA|PwR+{@LJ2d{4?qIQhZtnB*Y zaYA7eD`q2!CQCgW%TS?SEqVI0RuTP`eKuRLzNGZjw4;vA12CWsg5+iwC|GJ?fiP%T z%A2uZ`Hg&d(yoQlL6>Lsudh9DXeT)_Eo~)PGA~(rN|{-tGQW4M*PShxAs$VsiHIQi zGwx|(o;SCwQkF0}S$-_Z6f~BO!Nfo08z!`|$OE_Io3&^!x!X?Jdy?pbCWOfKaM~lc z%_!g#+R>ZJloHv{p8N23f<1!Q6-glDY0yL60ecy;kcx79pbwuQX5f77Z3FA|W~ZUB zjBf8D;IwD2RuZjQ`>g4;Fu!ty(bA7cqDL?>&l_e*v=y)U;DHgHqu|Voho*LM4j~Y?P2>2a65<5mYl$xNB(f?T+bFgu#%<3GwHDUWqSh4tE ztRjOg$-L|w=h~a=!0=a|Lgf1|U%!|ul=B7BCCy9MlYWYtf#vp_>+9(StNLcZ@w;9& zE_AJd_!n+i06q}-(NylIKO9z z9hpGu840&i;JGWQN#IKhH4A_Lm)!n#))Gb-^H>#J#hoYs*C9Nq1l zv=tK5Q+Mx+oXI{TOGD#A7R(5L^XdXVFFY<(BDBRkp}_Rn3L+VZ#f= zGU(R`vR4XAP>9BL>HrWZ)4lvKDo!U&;`-3#<#qGO!Q8XWwG>j=uT*X^N191Fu6>$K zvNqsKBE9As93}5xsy|kRPrKh+UFJgt0BHH^}xf<5%v?@lHa;ZYYe`9{ZG6h9L86oM~;u1=p=tw6^Jw;4kQn{x( zEp5ETl{{JAC0sTC@%}(T52vWBnb-n}@g%wv*HnV|&5EeIuTS=>g1&vEv#W2R$rF|D z;oo&X6>W(68tfxhuCjS`^5&$c_|C;zBV_5ITfa_c^7gY&sQKl_gc2^7o4+(`pOL-- z6sZ^KTs~F%#rZEvQtN<@BV`y;x_1_Rw$V}Y@&1C}?Sduwvp!P6r==buUiEJ9)|XS{ z)-C!5Yj3ZM6Glpg6Y<6~?Bj*m=9?zFL?B%qimenTJ}-HySee^?Q`$DwH76w4|F2fr zonryVgfTkbhFgGUhj1M^x=Gg;Us+@MB?lJ^8#QtctE!1FAsUTOjnn4{49n^jVL2sJFEQj zDB$uc&#Z=TtEqCTp(kwI7OZVxpC$++Xg5%m4mBYB+2$9g_kaxLc%IKx*2KKJFZnuY z{HXoov74^z!*^ZJD~R`9Imat?VSHYXcFt$>fqf>evXguQ+;O5c>gP{985J;eq@P9f zm;7hIBq;gxpv!xIkBZmi&DIwGc$DFu5MoKI_C8#lab%!gR@ZCGbM@gICb<}Fcew`* zkyfP~bVxs|co=sQ$4fJD<7HQuLIljXZ-v#z-YY}Y7YnwhXt?3RS4ZX9%!N=y z_m^8)03j(}i>PIsi`V2ILlPl&eH2@Y1V0+yfXuq#@ceD|TIs2O@J=wxr4#qDG6AYpf&HkY5oOg$C;7B*Q;!$bP2$; z!P(ufQabiNkO46s-{NBK+mqJk`;QUT#yZq=Av?`jhJLZRD3p+1e8IOLB-Qf)Y{_`< zRtDVveW_g7H$OOz?U=y%K!D^V6YTL2E?c-n&+JaHgHJPaYNjODVujIA_SxV_Ar0+Y8KYul`^wk(>EFJq|GJ_y}kW`8tr|Y zZ?0C+!IMN@=V>2ZHzz!qXVmncQoHOYnJ1>qgW-G%2QiL*RZ_y?%+-#|tBtha<83Hw zeMD87Qcghk{z@ds!sf(>mz-Dg1ZmUiop{H`Df+g~AcI)bzFxm!Awr6MPvL z#`#&tJtd6xNbUT%y-if^jp6BZMZn3flW4RY=P@ey@A))y#UHPqrs>{61LMcf8Z#tl zb}`{Svi-_KCKIHBj&@&WWl3qY<}Wxq!)ZoU7qw@kV0pt5j-o099HmOwjtnT0D8WXU z5#k6;bA>hMrYt?V+-s7SaS@$Gun>S2egZ-hTIeDV0U#l*41Db)A0rDgyprFpGhV0m zLAg!_te@Z!@D-}2)ujsH2h9T`alQg zYrV>~+~w_79-&R8_uEH-cXz7v^$PvMwhJGD(q@5VZgfckJmZZ;Zg)C#FpzR@I(=ga=)iM+IVrRu+v}4(D3%l1VmPq=R|!@A7knCj@_3Kiyx|nNxpbeU!=O z!dMuX2iI-sk~J5!`;hQ|1fKC!Ck$@-&>vUre)OGBEAPo>&0s>g`tYKx5=%si5EjbB z%qgJq;8c7xG7*;uiLmm`(KU6;loOCG(p>nsCE0Pavg(7+JdUjEZ8W5mbkKPdm=eTZ zo6&~yay!m;wC+|1*4{GoF8E%@AR@eutY^!tJB{m>8_k4aq(!o9TvPXM+k3~nTQja& z-(Rgqov9X5D^_ZS?(A>D%`TYycaO_}o@O`)J$fsKl`%tkr$xuKOGP$9O6Bcg^DYIO z8}}5!p_uKj5j)3PNj6-iMrIyWNBPV{XIYMPsr;Jlu@CZ>8gZ?-NTF-Y3_OgUa9YGia zbH9T^-nW<1&tYjxte4Ywc$=#{OO)4Mu!s(nMO3u>Y{T@jS9GM#C4TW@hKsJ0EgX3CC#{Eqs@>_`ti@4$9v&iN&<8~9ehb+2`ct`iMPptnW1Leh%S^4?p z?f`O8sl=Xbu(Im<^E4?NDU^=9(l*JMp*~ggJLCh$=6#Tr)B=VH*%#1U#%#^`6XJNj z$`=$!(E_hux<-?I9#@*|ThINy>%fS6I{0dHO4KS|e-unLwF8db1902@Sc1+!}sJZqP(+RpGUUAKP|AN=#Al$YWfiu@{X zpCJa>F+A7xVV)U3H7VPYU6jK4``NMLqI~q=K7!lnIqs?Dy%O+vCTYwb%8%kkx7>N&>(iIlQrk_@4}! ziAn~|nHaAGlX26L$7Ep%PU%M<(9k{Q`Pf>glS(nCk;RQhhW27&rEL2WlXEzf!|SjW z7E|O^)d#oKTW1zk#RGECx6B`RkF~TT=VWllS&|mYnPQ z>6cQS8EFXxUc7)8#i!G{TcQ65?4^5iVw5~w@1fe=UyVQE+YmFOrhkkOj1V*o5{>HP ze@Jy-TPx%YjBG4LNf1Gn*mI^#V#P{E-_Q3c)=etckqm-tq(yN-?oi*>mazX2do-1Nuzo1)A2LtHQOx^61L_iqpI^r*zPHwNPpOT~v32|;!^?$W zCjBM7#V7Fx>u^7MShwBv^S)jmoK+Q9ktNt`68Ra*C;;r*{0VD**MBBm3MdE6pHlp? zq9pwL$izvMLS_8f*SPx6=b<*tVv~dIu`Q|QtMlWYvmq~iy?k_$=vokavUMbtJe6<_ zU_XWQrhmZ+5;b@}d3)vn%QvdCFWi57ol54x+a6<<&y-o%F!S6ZptMU7)2A0;)2lOXu5Zh?ZH*2K)wXMC9E zLOI!^9iyNteC`PRXR-_7X3+Com+7=+KU!D7gTL-F5SZFUfxvLe<}0bYv;3Yzf)^0> zZ#6To9N7K<76$9uItMFApQ?)UqQWdSizmBf6%R1NSY8S0C+cM%KxM3 z9OEi$yEvR}*W{@tO*SXnwwr9*t~1$oO?FMTU6Y$^o9});ynUQ6&F{3&z3;Wwe_amuPHFsrQxnmG!HHypO84d)+T|Weh0R65~>xCbjY-c#3)sER7RJgr1 zTa96*#QqUW?PHX%#JH*b`+2`x(P$_k`%OeKq(TaQkzD;ceB1+wffIcQJ5DJY{hH14 zh&O1S;EbnBjdQD-!vJj_Op1%(Ft~sC`3N}TZr@asQx(LU+sBLhA|cvBJfjRQE}$-@wM9@p!a+S++AkwnGh3@BGkMQV=ps+yz2D)vhZWo6B-X z<929Tv%TfsQYhVF6gpmyqu2RujR&6iGR8u1TJiLhsIss}*J_K<;2124Ae?VWRU%Bt z@A(kL{TS+f*+jGFjfKDm(T&OHrZ|%txEpQyBV@o9wgubV*-LgHQLChnrf8>=fi^Gm z!aps64-^^&!7Cq4S}ucODMbsZUfDV!TaK`Rt98ZQ6s z|J!gL|9s2+yuAbVN&NrYR8jzVFT{4%016v;)S1&|+mZWQFB(%yELVUP1`)tl-L0(V z>NH;>m%GZA2naDFfKU5=)YM5|xrM^QWNXOhLjU|9P~X-NpqY{Q2i$Zi zqn=C`?b$IhJFS7MO@rdaC73W_hFF9JFbmWG3J#J0Abf0r1?~pB{%;`Q96AGjR<5tF z!BfZYD~#|S0Vamm*22O<6hLOQXGw4LQ;!84hlRiv>Km{$^5F=eGqQn?JN5lP3?qP5 zX?dI6dYc?mq~6PsfmY7BG<6M4#%Pgu2+c-zrF4nKaoV;CaYuEznuY&XLC)o8moI@DQxWNUZcR3E?6QTG!8fz#N`5l-7l9asWv1o+Do(shOk7+_Sp~t-3MDI{%S5h^116`{Q$AqR=b}>@LrF2-5XZ`#43^L+Yr;5RF;;6v!|r59x{Fm zj6#fCv_@~e71>=aag%{-3&=U&AC9cYuEl7!ai485)s+FLxyH}UcrwTJ^=X5ovA_Y_ za0%% zQo@Pn%W-KgDink#knO_;=CESGt#(S2-W;+BEeI$#M}W?P_lDi$GXPVh{JdfOyotI5 z<`e%dw^}8Pnyw7qC6CjV(K;EaFe2|v4gx@2dNpXA8{Z|d%yPKiYF>`(OaC`0;1nI) zZQRIc4-NpdP<%MwlG}^U^DAg&w6WxWXfNXvWt$q)hV+{cUb_CP)k^*Pi?Oo_Beg$j zumD{KQI-Y=FeIno>xD(?Bofc{oYosEv6#jXS`T>qHDt3<&aR~X~)Pf&}0$j^;ve8;CT5Ktjp&C)R3Wr3AydSgdyG^p7+OqJoI~$sYo)ko9Ca84MWRNORu)pc@UxM@ z^G@66R#w`WaiTqeNJJs95-J1v1E+dvNga@k08H@dRHuBP?T}G=`Z5IQ=NJ!kw%wI= zNEHREb||6vRbUuNvWu!d6YQjoW~Td5&;I3P>)3O9koHzsa@jOzT|={IWn0ikvdMOp z(H>N3j*3R*e2`&@)z-J=V8=GW)!A5dy`}#ez@Sl1r}rAls&u87nb^EAlg6xsz)60F zSzbvKVlfF)ZC!Bp{6jB2wJ*SRwjT?^*s|B# zR%!Fi)JNc@)`FI|#eOIM)eZe_gLqU}Y`&KFoXe)o?XL&RpsEE7Yh{D#t!6t7w|`e^ zL-xjhi5a7F=^)RK8ivjnjHt7in5nxv`fE`qJ3N2<Q?CM7SyKVlUi+qb^^Y0GBCL#$j#HIC@-}%HEm!yty6-N0t5G` z>BCAO$5)}%>M2*uk+n1!7@HK7Q%B#;&om(VH0L90VvDUH;3WtIpr~64-&LhU$bork zX$7AI83$(E+@f#bbtIb1Y;kHu-<4>@;pY_NNk5Ww%s2^d)3P7z6SeQqKrnPN(8I8M8sGN#Rp)M8PWgL?7FNWWrl z@?z#Yld||@ciMde3Hp$%HUEBH&miD*VJ((&wxz@_7K0-L;z6uL8(9Kd(dE=Kr!eq= z)tQ8REhV z-G~KC8dF{>1tloW!v-gV4iZdOVh%~xOzL>2o|>XG(QZ!npRr!{B?c2!qBg*sy`P!+ z#l!BpJ1ms6_DMS^JbDTC0;D`FD!g+tD$?Hb3G}~$R zcX?kHFl~2s<}^$&U#P34{Bzn%3Y>ntn_XJ!wc$dLffJP_O-9OO$ITwEV#{kmY)0_JHotpo-*J!yn@+$*D9T!fhuO}*fuBQbQ{VsDkZ={%78Isi#5&cb=9Oe}2)SO^WmVZ@A zeb!Kz_M84=0`;3!&q=bJjdlxy>(oA<%NYf%AkUBupW|yGnwSv}#%^fp=FVZaCwf1d z$6jE^N|z)117l%u*PnwzkOTQ(AVAkV2m}d9`^X21B+@SnreOH&MPTESkBq2hIm`_( zrAB*TV#l?{-NFeOHZrcf?2AjmA3B)_yjb765`|(H!zFeTOC+& zsRHN2HK0S$ETeJ!=UNecLNePpCOSaD(h?Yj#|-OuiPe}|CHj*D&Ht*+F9Jb%xNUGbvoqP?eM zlpODCIbl}xEF7Ugjoyk;GaG$vM{;2XoUpMYv~g|bqIwE)M|caMuVXuSI#E6}Ip9KW ziKwyDkjx^{teIj@IqggniybTeAHb`e$8l$1nGu59>#oe{V!drhm6pAxQUjw_X>VC^ zQD(qgSm`KRa$W#$IPELuZEH3g=c1B^UYAQuZqncH1eNM+_1?oN&-r}{^4a^T(_JIm zM^HCzqGVCRTseF0{vOe}A7@&?BlWl4h+TpIgLLwKk-qe>o6p0vxTy0M;`5HNd-v*k zUJ{5W(LrrM^s;QcCj_C=%!ahp?qjP+&$SQeN=q_Q*maQFS2xgFOYR(3N;Fg~SXMbG zrCetDe+(@J17`Bo$rJCtE8s9GT~s84ku z&!2_A+x6KKyn=3gCPn*1OuAw$+S6i-LA`|y3{9p8h?WHZSE1qMB5$p#rc^L}k*WP$ zbbsFJ_J}L|wq{vYzBerBk;KbU(@Ui(ujeauH51s7-TCG(`1!r2>m9t@1*Q@~Od!yg z414-Un>-yNaJ-s619}!&YS|&gbV--zD{qNVzD6Ov;5VKW*;AzOom{HDPE#-0GPqSZ zFZx>|bKoj^aPM{cTirSjNGP5{k^<~xAWk}0eCicjeM+#G#uELG6ZL1T;4axHO?1by z?^=e1Tnt)B`@~u?b@ez2BYbh}z?JTAmnE{BPTZ=Qp-0x%pDTo#?t3!U$h}EebTTIF z(0`+r|2;w<7yj?867o6u<<(LDMbIA3R|F*1x@H( zKhzY9Cm8MgoriDQcj_%5#)CNMMWoxq$cyCKOu zWhr_GWaXj7)zoOmWq?6}b_VVO8};|8!(QJGOWx(da0z*1>Cy5BhL7uXT!+8sK){3! zyybSNbn8GD)JW6D#Mbv;xDJSz5*&KNXtva+llXt4=MXhEp8r1!;NwE#NxbZ|u`6@o z%$9txyZh7QpoQ`JWQ_z(W{-gHMh`*l8NqE*lSPkz{B(YWtgUREH5G`-odBAnHYs?! zj;P`4142ucgb9QqRkR(aa}23!v6cPW0$O8zUcg_-1su$}FUaQ5-Bn;t00&#STvd!J zF`a~T!nslvS9^x-*UO*-YijPq(a;~+s>K3jb?u1$Q~iv|?-=yL4#OpjeAh?sKuUL> zlPf4G0R+nFn?asSM{>X16qm{oEXBPsLykquqc(L7o-MbOZ*jeOld->EdL?0IX7Bgy zXt@o(hl7%#^B& zqlnW=y12Z_$WjtaUCl-#%B6bpk6UdqqG_2ZC|Dr+(KIGW$}5DWa)`pker0_HtYrP0 zz@V`QAnbMvfUz($3sEIr>n7D;{|MX?vyF!|!prT0hK?K)Qy0z@1E?RUYC3)U1~fp) z&F8WuFFfDyHtp|~a$RnTG!N(sIn2K*F(4s1fAYJ}%_+vN*-eT;<>E?&{GdfRwn_O2 zLICXav!}~?z4`fz0j5Xa@4#1lG*Ll-#_wf5mnU&_@9nYH8l%!m_d1?#Cm|}$ zC}SY8yk>w>O)l}Jg2nG{uU`^=`tu($%%RJC|11|{w`;&uj=-khrO8sYmHBvkvT6#} z&W)$yZ=jQ@7?Nhs2&_`bnWd$t7k5^I3cpRwY_7-wQJEJQ2__^=HKkVTH5lU7L;0@T z9%A5FsHJz;(Q_|ytcy#Ub?u8})7$U`Kn<{fqA^P4{19GldJ6q7Ji>aAakq3jNfwT; zme&IvF!Am+(>UzWpV}Cv_}aizQcISCJ>L59Vk_D=%)L9^; z{|4WCY3ioMRZFCzBdB<8$n^oeWFYvx?tCHS-V|p^9%7?B^nN*RV;bQlAuK2=3n&Qk zi9)!&ZqJ5-6!>F@8>vbFPN8fWCQgNiPhRwLd0ViY zwZ^tH@1Yg_xcDxskjQBKf?2(m`L2|3KE9V1jK}4!0+-7f*7>0~aKUI>2&Wx!M;wDd z5NZh;7D%nvhB>N134p8BI)KAIB-YVRl^uo-qp`F1I#7{d-A>}F;`UCJJ{@2kQ%kgg zsI@@Av32yf;>f&5K|)%enK@}bSE9xuPnP!7Wza~s%uol*q$;CE$X=N3yp%moGl@4oOYjsW)j5`x8c`ZHX z<1it%cvBB@(a#yY`F#BUPLP`k&$w)!ep3q=z2#XbD8isTiQdPp+jH9cI;5F=n|8YJLG3re1md&nACmA?M;d9yIKpUG zZRM5KMI{AE*j2G-o6UhGLj-?_aXHi`LtDm)ubXc0?odIu7qH>@_?blwrMb<{dfcn{ z{XgKSZ>XX8arii-X1>;zQ)Lu<9RYv3!SNRZWWiSG9z+P&+_q~m4C%2{@g?q~U~nZA z+s!+-t%{=ZLw!j*Cp%yeY-+}fIEeXF81TaYlnS}&xdr(Lu&532z{X{Yl|6C${nXI8 zsx;>HS<30^>)Tf+BQLaol^P%8sN-X%LFnG`mp1!{(Q!PO_d-n7l)YVNUICH-sYsYT z?+vJ>=9yL9;m|;%DVGN(0S+mROE6p*iHLz6ymle-WMgv<2<4r7FSsYZ1@)9VJ6FV5 zWOg>yR26mQhz+Z@g_Kr8SKY@3>ap88_hk+?ITId&-Ks~_TZL|)9M)|9zwDtYEQq0Y zm}IeSJV}4l{rhiTqQD}=ur-@xV7^t!P?Z)Qi8FK99@pL+%ssYg{c1#sFB)B>f-g?e zR81TUTEBbA^2G**1iU;vR`aezavjVipUvPSS655`{U{K?9Q=u{@#J?4lE)O&y;w{y zDO#wRRXrHER85aNjZu)B-<(%aQ8~AL7I}dUL!axJe}Zl+GOcD_F=?rQIU4f_j3FF3Rj$nnwONf_ zQ)M^5YoRy3nmV|@kdWUojNjdk%7d=7f)cpjqjZEm+;%Q-s+!;$2*apLhs2w}H0iKg zjYpN!F{UdB0j>0mwq`{&CB>w+74LZ{lk}oX@qm%RpWo^e`tovt^%h59*3s_uil4D` zy`pxOq05lqUzI09&V&;wO-7cJGBJQ0gpwkk*7hTWDVtrgXfEKr;$gG|A29b|m8*>IKA$GY zgM2W4*Vf0W9r7K=z(!2%+GB%|*EMb>{VH~MDn7u7)Ro8*PjX&VGO_}WO^mcilt3JX zD@dmvxl#D>`_d zCB%3ZSW1>Nbq$Oih9VYbi=1GBWZ;ZO=d8LBdqv>z$a0`HB-g*>90E z95tTr*mP(vEtMlv+sO{{QIO9+O;q{m`CbKy1dPSOMzy<7<(39rLoh`%FJYT z@DKo-H29&hwZ9#oIG~K*s-_=ILj(6jP4|6g``@l?XkQCXPs*~KtDJ4d*b93V-%Ag1 zmfM$k_{6|pQmcygkw0mxpZjjwnO}Xl^NlGinXuWlXl*qy6ldUDRe#?;b2uV{p>v(9 zLu?LOGW0Xi17_YzR8pX9(+0Y8LOj`Sx;HR;u7CZd#1!{ud825q4n_=LU?n^b+ETfk zt9fzZBXmqmf2$*YmvfDYk#{)JU9R7`U$#uA66*y+-E4{WcC*v@HHN`SkxYq0(d0~+ zh7eT{q{yr^nn9~2LRN)17bd)_IEsg$B{J&UrKp63Ev9s^WsPsrHNwsjUs6Z?XBm0q zq)j}BvaXZ`)^aJ2DodOh3c18Q8HbPG*Uov!4WJt)nZepwF2OkaTn0k%jHt<+1A!kJo|r ze0Lp*R*FvI7FUZ5Vo<}N3HDh1fq;z}`a8#ZUiPQZGIITkZk3EA6ht&7C+R^WJR3Hf zk+BLg9}2b{vl5e1612M|_qnH>grXq>L{wHF2eWLOOUdl0Tq+`2M5(N?6cG(u$U@Bv zVpPUFpB5Y`urG&+5Zd(u2Jp#MX^MUS!N5EE{$eaHYW}g`D;Jt|9_?gKDo!77{8?#_ zIdOY?ne&3OFDAq^k?HrOYlpkWQN7;{tTh_lwqvjRE}Q?3(yZ2?7t8+4M?`)wh(+|_?-VIg%r%l3nYrOlrt_VeDsj}T;Jj3I;=lxR+r<-p zc0hDun)z{PV7+VBy}h!%V!SMi?`DKNkm8pZK!-<#KW0@}-JRUDo&+V5{_rp}R&Da9 z7LH6h>zsJ>d+^m_a_3=R#QyVk&>@??K2h{?a?AVoDfLiR7;G+-{8Zqts1^lV35F+( zcM;a?uiWHuy8}aST##T(M$RmsUyGr36SsIyS(j7Lp61DD{9bgFEVz>xo-ECrXZY-I zVLAtyKp8j)Wo2{eb>*GM*6UW?VYHvbIh5+!iKB=3-?60Vib~#!$$LdjQ922nA(7MdX z`HM{Exfm2NAj0eZI~b?u{d`^c_^(;_C!fgICUYm1?;wHJtf>ouP0dGeTSycJ5vHcq zo`mA=uPSU2APe!iUt=qKQ5mB0Uqgx}*wS&XlrSC9QxfuqW4F$!cA6vdBB(RVmPKaH z1yFtrDcegkXEDwgS$jpJ1eardZR3+dhJ1!NKc>GfkQ;Iv!NFdcRW5RJP(-_h9}B|h z)=@sdh1xLbcYa|^x>Y0~xza*8HXcQ%=E?BMkJzOSIo*+@BFxDU)P{a_S(-~K=GgE5 zYNc(R4yMSHRz*;Urw`8VQG8eElVs33|H&0om>?~ z4cFsaf+E$5+kx0EO4!H>x)cTM#Pfz9>&Lp>{wS4OULbDPQm1~k+hJub|F#0$7p0!S zW(M`nJE~}$sqe*{(+7MOOvQ#5ccXl@J8nE3-Yd=4B)0D;=-ttpJ;rQ>+-%!3f+vP_ zLNiQh1P`o*eGfm(b4dK;k#T33&XLW@{wf7y8(vU&BxAKh6G{VZgt{OqXGM;YJKk$n z_BBi2zZzsIt$Uzx*gH$G<|wV}f~#cRw_?W57Dnc%Q(bs0izci&XzZvvr-6(0U!#1z zT$T3aKDcuFK>w@aLWLBONk(GNs=qd^mlrD)i0HdA^)CY7|-~F=ZrExuiJ3mjMqlqe1$8?aO;ZtT`aSp zFtXH$T4m2rR~a8?L7YN~#6uRkOi;%E&?5p!+*BK?xWpBEb>q$3m%fPP?voST8R7@6 zD}5wvU2+1|d@(X9z#nxT-o1|cIx>=uI}1`U(^7VxRAme`ljDlp={y(VlD^%~fqVmf zK4BI{Qh~dU|_;LZk{#pA)h*Kf%ex$gpKVbdAxr(a6V7>bA^(xr>v4heh-QON~V9OrFF+Kj(8?_Tvz zDG?SQpPU~qg9L}KEP_b*{v>G4YhM#My$OlZjP(hrueXKx+7j{I_gUj9U(J*FrnZmO zC*;i=}4D?f4eIh@Kqw_+{(yooxr;DF`)u*)#XM>r4jpI5!9FsLwtQA;{S1 zR&>H43<;e|Cb<1GDB$}JZ8A&^JY(O@(3?x^eb^HA-n)cWS-9uK;F3Sp7C@dq= z2i^!;%B2%(whnGf2=5$YISRSuXiFKIO?;8KvN$@a@tsJ z3h`#I_>ZPH+3V;h_H zyM_Av_&Z-Thwt}xC7p3v8J}hN7@AkcUMNy{#^(^h=zeZ8y%`)sr6Ni5t3Y&@7y&vW zRD_LffyLSCzuP>N4>8hYF6S?}K%5ytw!U%erP~STnz~O#SpUy{*tUx(PY=C=C$}9- zxfxr>wb*FDdf9Xd)doUrCQi zAQp-Qe0$EiGgsg(!~RpN3NF--#-*Zp%de^9TD$PX62TFovsjpXP9{n1EYY}vX9u0O z!FM1Wd;+PfJ;a&@+e2awyS6az3(dZ}1{(^_zOGV_dZ%A|nEA4{6Q^5lm)cQRx|~s8QQhX|Ij?{y9vM{1cu`w^iqz!2 zuH!SGoq-R#3Kb{jb&5IM$60_V!cAO3VMp#a_0BO?#!e}Jp8yiR#su#!XF9XlnSn+} z&|)X?A8#7pYazeS1sR(o5@UI;A8K+ja8t_IZ;3=|(d-_xCHONWdEH2`Aq2u^PSn^{()qgMYP=8P< zD*QrcXkv8oX&vXeU^0cs+w)?-#lv|6*2U%Cm;h$hBJH%gop7xgUyfqbw86B&v%Zb+ zST;rP6{X($lT2WxG1h(aHG(%qcNjQ<%~toQ{**lbgd|^Ok}PWQsrTfa{__N5|ED1* z=K~yPB4;X!H+2Ts{A~OdXJbGu<|{`j=y7C+5sBrm+m!K>_r>DQQxoT5XK6}IubQV% zpp=c-(6VO`!XF#aL`dBgwKm`Wm(p6k^Is$CVheiCGEjO5KT4QG7xu7hL=C3pu*6(Z zy;fr_LxwMbRH}9ze}?kpfy;`65_uVad;f$Zp$Py~C&!$Aia)%yVQQ<-tl zH`hiO8Lo+HPD^@56Tj%((&fi`D__*$a`LZ{Y?r=&BF{S>ZZ^Yn9mkP1n7t2F59F;W z(eC_{S9!uS$&;vX?i!p{Fha22UO<&kyR3}PWewpScHQxGxD)$^5}f`%CzP97pUe#< za^}aA%dF1QbgaNL2lp(q!yHGRILge#B`e`hlG$=@dYant*S|KgHy-MXz;hO(`-RDA z9RGh;!xY_)Uy41C=NE4Y2|>zdf7?JH4!iwh+^y~YSe@GQgnAS5Wb@@s4l65K%j#-A z$5n?cVwIk|Q8_-3Ef%lc;6JyU!+a|%3b7q=Vs?<8k+MFOb!8RW)E7ve=@(VIlMVrk z8;)*Nb45{68=V}%Qc~8*LS~BF?2PZL5oi$oOlUim=HRJH()b4Sz41#yK~+iwx(wXn zgU&a!?c$`xeYPhiY+FRb%s^bLzuDHH9c+gyu9kkhs_7w@@lM=Q*e<-X#IxtOk-vRP ziy(+ZsvH>!bkk>Ty>c~rIFZerT!}knGTYZSk;@zGz!f2Z5YrEPLJuZJE#}G90|`_j zB7hK}`<{Wsr!n&Lb$mm&&5%Kl-CY_@SyJmdAdc?#^6u6y8D=B7l zq;3p!K@$CGF#4Kn_YtJP>EFBU5cJc)FYy6bfC94mhrBI{I%;9pw$Y*?J>W1Bi8fCb ztd;tH(a5dS&Yww-2>4PH$8f8y85!pFq zBa9m^2l&!Pc~hy%F^CXIfsihb&3cP9>f&on7@_X)d7_{BMC%MvJ=PcsNppH(Mr96M zSux^;yEkS4u@HQRBL8lnI1xFBjQ!pzcZdf;+pe3)(u}J?!>P_?GLBJZ^Tw30fci zyyHZjRW`Uy?iEBSf-;nfG=qbvgTppcI+#kIisOg%us({Y=o-Awk``t~H++>?SYpnM32QTBFt~kz3t?5*toTpxW`YvQ0DkSn-)OnM&U}b)D z=rRYdsFZPPOTdW)@j!0aUJFwNb(SF4(%}?2x|S%k5X;_vRoc2|xG>qMpf6ofx;auB z9Ck+cl-a_z%us%27?Y^ehbM_tW#KHEx9FCsc(rKa(r{ZhVPpRrM!Z7@lJ1b0@Su$4 zML+(RE55ltZy1AvCJJzn;{tmEEgBpJV83P11H(^L=ewq5uA9%*wj6`RscJF`${|bO zhV5RSEC@{{D+U&#P`qAYB}-UsUW~u#xS##IXTMa@Q=8gLF+Z3}n^=&N1C)w}o*S*Q ziIUWkY$_aGCmd|dlpeSPo(_5?_6>Hzb&i&`!Zj&KJ#Q)RY-3;@+jP2 z<)tl!tTW(DW(qm`va4u+@jQ#f1-=&Mt32(`8;Oi)$gm!F9pmK;(K)!jNs&=pjaU@Q zS}2X~*PF1p*K?<*f9rj}6^^n&M0ioESj$@VBaBlo@)DR}kRiFbQF{2@6NH6HJpb#b zYx_8XRuvP*gdz7!9i1E02vmNnv~93jCbrsO=)%1IH=CLhxM*To#Y5KG!YA3eHdwJK zqq=u6tv4yRAJGw@YUvZ}Ekce}w96_h z0OJlBHF_%1Qi!59D^r5uWuTkWGhbn$l-mXW4_{l#Eq_DkEq@E{WTDTtVvV8J_zDyB zi}rVME86kr-G)j}Yv4uqh82qr=X%Og9Vhi zc+THwc8gSm9f~5?yjny?d!6<`X@Gki^$ma0bf(X_*IgV;Tjpj^*=yDWp4_iNG{}>{ z*$VHba)Fmpsa%$7x3dTq9(6i^@Dsfm!{WPqH`A7f2v)<%_mai%zkdX!IC8(mPt545 zj22}e;7sL8xw{KyHeZ?>=; zJ~Qa#L>d6rdA_YIo~`&tYzf|C?C&e7uh*poxT32}qzSx)xm#vN(R@ag0lp(UA(HoE9tmE+JrZqNDPy|joc#0eJcSBAK!Ct^ z#jcA4M=yqZ<^QeOo@T=^&FKG#1af`&XIX95JpKbw5LG1nJ33atSwq{vT zm4TOHUK|+;$yfWv(YW-s)1q)~@X+Y&YlhFw_L@wp)ktH?5>jO9N71vM2U0IDZ;;j1 z)=Ofg2v^Zwgy_;CF6v{6bh*2B+al%GX=tgTa9hwe!l=!XG1uSo7%I!S0VC^vETAdC z%}$Wsd79v{&L5)r;@Ar|uXtyqhF6lt3~PoOL8cz#D((=wph(UcpTYkvq_ zhAhjf0Az2zY9z;+*-jOGVRL&>KcDIt1F|x=G)dbY1hcr_J@$CI5rG84xi(z0&6>zs zpj(&sG*KhRQe5|ACHtIX8Ev$~=gk@ua06*0 zwVWs00OYuRKU#vpyCwGL>eArW`+fggf2@WPt}Tk2TZ7{`ivEiYUH4X3u9%UaOrDj9x9VtqZVjG9VPYMBSNx$? zOY@jv6JEfWA@OSP^Q!ebW(&LHmZQ?*50rV>EYL31jBHii(^PV@PnUayZ#d82dfRWD zdfsAE4o6RRWrb)uDHaUONRAjJaz6K;t0-_n90q&0w!GYeMuOh%BrFw zzjd3T)*PRnO%$$otgRgh2lrDaOUmoQ+Q$&AD#xy$`6L@$v}94Dm$D|(;#@Pc$^E^! zY83;V8;4qyvFbg;BoZ6lA7__l_Ywcv%`-DtQms$yeFH6?NaOc( zC2g)?-OCP+Oc?&S)9amr0r&L0Pm+_*hhy}RbsXYDTuqPFdfWWvZ=JPZrM^~whP&Wi zgI;)RWl7en)mWMl!ho1`O*NRzitaHpT+>2=Qq-c}`(J5iQacm6V46iLqsKww$RkX! zy*8Efh&lrOJSdD`OVFym~waPiuPJ-Y=*Q$uoxfjOFa^?nwq#@jljj zo}&2O1iwaRg$F$t^}AlW+mYomUXl1~4wB^fhPG_J|H`Ov8Y@7j9Ww!F5VLcd3eFB`CuHd%F#jEmmVlMtQ~$you$@w4$t?XNJBB@JA0 z#uV|UYpDR@Dtca!NmRigYx6@0qx?>)TL>ZInF84i##QO6Ei19&5I!#tX%)uM9b}xb zHT_6n-P(IOw4c<-)yGAT=$&7T^V9#E&I`gzXy*S5VMbhc6icIVFKhJ-57Z-MbE;hMR_WRrj*BKHOnSS(K7FHBw z)z?!Kw}?@1e>-LZh6&e=Y+u2(`CK=qiX*^zqeu`2MJO1-xy~J_f{B^UFjM?pvt}K#heKIZ=~F9K@C7>dJiotgA^R8vRcm$U*v~;D5{HY%bfE5y2A=bmoaKx=ff$ zjIPdP?9ox6+m18FuhU={sw}IkcH&=CjkFj(&NV={eSvQ4${4o4jVs6)D~$N1Mv;k^ zf;K$EmtSE$I~r26c(e&{9`0965n))Z<@$1>8+c_3m4>UT_~!PU!yx;We~lqN++leh zMk@)du*?4Bi?#HO2=#sXJ=Zq1Mv$sVuYlEjNfYbLPs!N--hHXWsoiU*lg>EU zO~`PjjuvY(-bC`Lz8zPgOe$z5AG+bS-|5LmAvS2r@N6h`UhI25v*sOGvgJKGiJjlz z?AdI7P-a#le4vL`_7gkaCXjJmX^uT7#14!JeQVy0nS6@T8*g}$rRkxB25-sZAuo@G z3nGq{Nh!%vc<6x23z0IAu5DTFt)fI-&~TJs`yt6|fa$8NjVWG7j?KZp=Ej5(lCL2qh&CA7>!8K|7p5uEg_ok1!tS>p91BIvL2jWv9E;li3`{wd8a*lJGYto>Cr`I$);T3Dr# z(i@f1V|O?BE)J#sEecMha$=qrHk^`(0_vTZFJ&}cGKD)X*jE(_Xc3I;JJ~VI`~Csi zf=K;8vaukh?GX!=Ggc!^pBER;EEI13U7)CU)`uM@W?EL%d>p8(Yhzx&YI+_T^D!)Y zMhnC-Pe}un>f~3{s=OM1uz5DEwl^H|f21USjggZ&U8bMC^1$0Tudo#4(*irDC)$NagaG4ccC{c>M5K~pjt_kcX3y+Y;m+-%A zSronmzMc~O`9fdD%~;CkLLZ^BopCvkzjd3(+~dy~!-4l}jXIM6x|9r*lgh%k8qkEv zwHdprxSmI>GJz(^bqsW3rSC)z)_$n_+!YKuCL?J%zgmkZPP;Y`if zULWGFM-;wYQh#eWp&nl&Eq*z}%|`W$wo`S#kG0aj4c>x41%SBK?T^3J_RO=&^%a=N(Z;EpiSm%=*0fk*t#&RDv%2ALMyavh0AT{TIr2_CBmON zZlV1hJm#%Px!5J9G#APJ1r3&_9a&cPmo#0FWd+cz#hiAR3RhHsNoj0>hV3A za+%S18V~vHewn_gxH)009u9k7sXC1_J6<&EBOdDdL+Ird1`m(mVE#KV*TRZiokgM~ zg%m}iMU^8VY_W8C%zei%4XKEqEiJ}rErMz^xuzWtBK5*c`h}Y}w7edtNFs#9;5WbSNNFHnuqoOOhR}Fi3Tfyw z+RS{RNG|UjX9kpX9Ou%lfiyHUkjZ3hkH?e$`o`$e^yfK#o)Chf1L?eGAY-tnE5o69 zo?NlC=>f0gvSvT66_SvrB)D>T)Wane*G~8Iy_@RzZ_n*x)yr=&rL~Ebcix2mVx|E( zt8|@aa#I}--*+b;Z~TH8(ixmJl~dI*knVd=5@fpgTs)|9*>1ZZin zZO;K-dHr1kP#KcMd>TvI9Ni`Pjl1oHWF#v&(C?5bYUYIetf-DrCpo9Ihyq-W%~gIE zwA3Piwf)1adGBNL`2v2ghnuclh<_p)Ajb67-GQ&?FAOQEuCCyU`E%HFpqVs1*xIGFxOYQTXai8HNr70$HEWcj-nTCwm+PiLAl_&USWE2vA76M-&_PWq9qg ze%kwUTqop9?SJ z*O4U!*uo(0Xg<#sSk>9jrhySU@`VYDbOzIko!vHOgF(!zFI-VwlxBUMqrz503QW^H zQnx#it;rQN`+Kq=T->;zj*5tdcAT?5bS_g+RpzItUF_}2(p>H>8Ny>69D{sOle0BJ zwH}b#(elyWQ@B$Kx57<-1@2BBeweSGiJJQz(vwaAy&L2vM2? z`21j=RUgLrcu$JP>InDWKAl;OL4rODkI%wo3ABbpvdG%cy7<}aZS0;K;=9+DQ5_ni znOB;Ya>=7hYDi=p-rc&5$DVu%UogPTNsUOy;gWxpCLHiGZ`M?H?LEY0bEe}n&!Xc< zANMa(GMdWp;wx)-`uW!w92uoPVo)9cd4g8jZ+77&!x=afFL5qiUm4@J@-Ss*nYudJ zXPJ(}Rem>WQUeIVy8dC-uK$EcC`iEPWx<^3rS|Ug5shk1!0V;HzLx5WGWNB#^F=Dl z9bqp%`ON{O#9t{H5|U4oIo8BWomFk=EcOI{p9cDqih~0ZZ;gT1)EBI%p2^u{^2Zjs zPtAxSNakQycae&yk4ZItjB^A)A;IH!;q{m#vKoDYjxa|FoH2T*px?#R`Vgi#jRp}K zr36Js9)m}gWfkk|>wC(}%g5zfUN8WuuC6XBrIJVg%ywWXkwYmDoZ+Os5RkJa>vv`N z?V4`1>E)juTF6ziD=G7v_+0`+l%_e4M57g6nyP(FZjAEF=eJ@znjhR$PMN<1UP3Dt zwpjer@_HPlc=?TY2nGZE_`4 z>V?}IV(lO+ab2POs7uS_XnSKUcS7(#XG98<1{4R{vI!qyW zzkiq)UV8_ZY48t!{XH(5ISJQ9MIv7_FoI|_Onq$?d)wOCJv2hDCRz$3&y{LpY%x

~E}vPT^smmzUmW@$F(ByW^2 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%0DS$8|0nri*DmT6p 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 JPDHLkV1l|5=o

+image/svg+xml \ No newline at end of file diff --git a/src/frontend/packages/theme/_helper.scss b/src/frontend/packages/theme/_helper.scss index 7b9015f992..70cb9f9eda 100644 --- a/src/frontend/packages/theme/_helper.scss +++ b/src/frontend/packages/theme/_helper.scss @@ -115,6 +115,6 @@ $oss-dark-theme: mat-dark-theme($oss-dark-primary, $oss-dark-accent, $oss-dark-w $warn: map-get($theme, warn); $primary: map-get($theme, primary); $white: #fff; // Use default palette for status - @return (success: map-get($mat-green, 500), warning: map-get($mat-orange, 500), danger: mat-color($warn), tentative: map-get($mat-grey, 500), busy: mat-color($primary), text: $white, ); + @return (success: map-get($mat-green, 500), info: map-get($mat-blue, 500), warning: map-get($mat-orange, 500), danger: mat-color($warn), tentative: map-get($mat-grey, 500), busy: mat-color($primary), text: $white, ); } } diff --git a/src/jetstream/config.example b/src/jetstream/config.example index 69d9110c8b..417f5f8144 100644 --- a/src/jetstream/config.example +++ b/src/jetstream/config.example @@ -75,5 +75,8 @@ INVITE_USER_CLIENT_SECRET= # Simplify development with FDB (value is port of FDB server: 27016 for FDB, 27017 for MongoDB) #FDB_LOCAL_DEV=27017 +# Analysis services API +#ANALYSIS_SERVICES_API= + # Download link when installing the Kubernetes Dashboard in a targetted Kube Endpoint -# STRATOS_KUBERNETES_DASHBOARD_IMAGE= \ No newline at end of file +# STRATOS_KUBERNETES_DASHBOARD_IMAGE= diff --git a/src/jetstream/go.mod b/src/jetstream/go.mod index 59494917e6..989a9fc1b8 100644 --- a/src/jetstream/go.mod +++ b/src/jetstream/go.mod @@ -36,6 +36,7 @@ require ( github.com/fatih/color v1.7.0 // indirect github.com/go-sql-driver/mysql v1.5.0 github.com/google/go-querystring v1.0.0 // indirect + github.com/google/martian v2.1.0+incompatible github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e // indirect github.com/gorilla/context v1.1.1 github.com/gorilla/securecookie v1.1.1 diff --git a/src/jetstream/go.sum b/src/jetstream/go.sum index df1d085124..ca7f76c6fb 100644 --- a/src/jetstream/go.sum +++ b/src/jetstream/go.sum @@ -56,6 +56,7 @@ github.com/Microsoft/go-winio v0.4.12 h1:xAfWHN1IrQ0NJ9TBC0KBZoqLjzDTr1ML+4MywiU github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46 h1:lsxEuwrXEAokXB9qhlbKWPpo3KMLZQ5WB5WLQRW1uq0= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4= @@ -130,10 +131,13 @@ github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMX github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M= github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.15+incompatible h1:+9RjdC18gMxNQVvSiXvObLu29mOFmkgdsB4cRTlV+EE= github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea h1:n2Ltr3SrfQlf/9nOna1DoGKxLx3qTSI8Ttl6Xrqp6mw= github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cppforlife/go-patch v0.2.0 h1:Y14MnCQjDlbw7WXT4k+u6DPAA9XnygN4BfrSpI/19RU= github.com/cppforlife/go-patch v0.2.0/go.mod h1:67a7aIi94FHDZdoeGSJRRFDp66l9MhaAG1yGxpUoFD8= @@ -357,6 +361,7 @@ github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:Fecb github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc h1:f8eY6cV/x1x+HLjOp4r72s/31/V2aTUtg5oKRRPf8/Q= github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= @@ -478,6 +483,7 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE= github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d h1:7PxY7LVfSZm7PEeBTyK1rj1gABdCO2mbri6GKO1cMDs= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= @@ -509,6 +515,7 @@ github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJ github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc2.0.20190611121236-6cc515888830 h1:yvQ/2Pupw60ON8TYEIGGTAI77yZsWYkiOeHFZWkwlCk= github.com/opencontainers/runc v1.0.0-rc2.0.20190611121236-6cc515888830/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= @@ -634,8 +641,11 @@ github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wK go.mongodb.org/mongo-driver v1.1.3 h1:++7u8r9adKhGR+I79NfEtYrk2ktjenErXM99PSufIoI= go.mongodb.org/mongo-driver v1.1.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569 h1:nSQar3Y0E3VQF/VdZ8PTAilaXpER+d7ypdABCrpwMdg= go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df h1:shvkWr0NAZkg4nPuE3XrKP0VuBPijjk3TfX6Y6acFNg= go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15 h1:Z2sc4+v0JHV6Mn4kX1f2a5nruNjmV+Th32sugE8zwz8= go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -778,6 +788,7 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/square/go-jose.v1 v1.1.2 h1:/5jmADZB+RiKtZGr4HxsEFOEfbfsjTKsVnqpThUpE30= gopkg.in/square/go-jose.v1 v1.1.2/go.mod h1:QpYS+a4WhS+DTlyQIi6Ka7MS3SuR9a055rgXNEe6EiA= @@ -822,12 +833,14 @@ k8s.io/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-201910010437 k8s.io/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20191001043732-d647ddbd755f/go.mod h1:f1tFT2pOqPzfckbG1GjHIzy3G+T2LW7rchcruNoLaiM= k8s.io/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20191001043732-d647ddbd755f h1:X3br+JCtf40mnzQsKAnHnezd1CvCENgG5uLJTbAspZ4= k8s.io/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20191001043732-d647ddbd755f/go.mod h1:PNw+FbGH4/s3zK9V3rAeMiHTbQz2CU/yqAkfQ2UgLVs= +k8s.io/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20191001043732-d647ddbd755f h1:QIhu1g7jmiv/90qGiPiCOTHFYEcrL0HA5P/6G/pt7zM= k8s.io/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20191001043732-d647ddbd755f/go.mod h1:WmFoxjELD2xtWb77Yj9RPibT5ACkQYEW9lPQtNkGtbE= k8s.io/kubernetes/staging/src/k8s.io/cli-runtime v0.0.0-20191001043732-d647ddbd755f h1:6CkT409OUoX4ZiP++1N3id3PCcOoktBvclNsDKPKrfc= k8s.io/kubernetes/staging/src/k8s.io/cli-runtime v0.0.0-20191001043732-d647ddbd755f/go.mod h1:nBogvbgjMgo7AeVA6CuqVO13LVIfmlQ11t6xzAJdBN8= k8s.io/kubernetes/staging/src/k8s.io/client-go v0.0.0-20191001043732-d647ddbd755f h1:ksJC2cpBqkCP8bzmfDYXr65JRpt9JmANvaKIR3qggt4= k8s.io/kubernetes/staging/src/k8s.io/client-go v0.0.0-20191001043732-d647ddbd755f/go.mod h1:GiGfbsjtP4tOW6zgpL8/vCUoyXAV5+9X2onLursPi08= k8s.io/kubernetes/staging/src/k8s.io/code-generator v0.0.0-20191001043732-d647ddbd755f/go.mod h1:L8deZCu6NpzgKzY91TOGKJ1JtAoHd8WyJ/HdoxqZCGo= +k8s.io/kubernetes/staging/src/k8s.io/component-base v0.0.0-20191001043732-d647ddbd755f h1:fwZSUxpQ99UBEkIhHbzY2pE3SPU9Zn4yZkMSolEt6Jw= k8s.io/kubernetes/staging/src/k8s.io/component-base v0.0.0-20191001043732-d647ddbd755f/go.mod h1:spPP+vRNS8EsnNNIhFCZTTuRO3XhV1WoF18HJySoZn8= k8s.io/kubernetes/staging/src/k8s.io/kubectl v0.0.0-20191001043732-d647ddbd755f h1:vH4+rTRLDI8z9dQCZ6cJcIi3RMGZ6JwJWyLbrSNHBCE= k8s.io/kubernetes/staging/src/k8s.io/kubectl v0.0.0-20191001043732-d647ddbd755f/go.mod h1:ellVfoCz8MlDjTnkqsTkU5svJOIjcK3XNx/onmixgDk= @@ -845,6 +858,7 @@ rsc.io/letsencrypt v0.0.1/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY= sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0= sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca h1:6dsH6AYQWbyZmtttJNe8Gq1cXOeS1BdV3eW37zHilAQ= sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/src/jetstream/load_plugins.go b/src/jetstream/load_plugins.go index 188aef7b7c..861eb8c81b 100644 --- a/src/jetstream/load_plugins.go +++ b/src/jetstream/load_plugins.go @@ -7,12 +7,14 @@ import ( "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/cfappssh" "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/cloudfoundry" "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/cloudfoundryhosting" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/analysis" "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/kubernetes" "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/metrics" "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/monocular" "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/userfavorites" "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/userinfo" "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/userinvite" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" log "github.com/sirupsen/logrus" ) @@ -39,6 +41,7 @@ func (pp *portalProxy) loadPlugins() { {"monocular", monocular.Init}, {"userfavorites", userfavorites.Init}, {"autoscaler", autoscaler.Init}, + {"analysis", analysis.Init}, {"backup", backup.Init}, } { plugin, err := p.Init(pp) diff --git a/src/jetstream/plugins/analysis/20200210105400_Analysis.go b/src/jetstream/plugins/analysis/20200210105400_Analysis.go new file mode 100644 index 0000000000..f27d6bf873 --- /dev/null +++ b/src/jetstream/plugins/analysis/20200210105400_Analysis.go @@ -0,0 +1,43 @@ +package analysis + +import ( + "database/sql" + + "bitbucket.org/liamstask/goose/lib/goose" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/datastore" +) + +func init() { + datastore.RegisterMigration(20200210105400, "Analysis", func(txn *sql.Tx, conf *goose.DBConf) error { + + createAnalysisTabls := "CREATE TABLE IF NOT EXISTS analysis (" + createAnalysisTabls += "id VARCHAR(255) NOT NULL," + createAnalysisTabls += "endpoint VARCHAR(36) NOT NULL," + createAnalysisTabls += "endpoint_type VARCHAR(36) NOT NULL," + createAnalysisTabls += "name VARCHAR(255) NOT NULL," + createAnalysisTabls += "user VARCHAR(36) NOT NULL," + createAnalysisTabls += "path VARCHAR(255) NOT NULL," + createAnalysisTabls += "type VARCHAR(64) NOT NULL," + createAnalysisTabls += "format VARCHAR(64) NOT NULL," + createAnalysisTabls += "created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP," + createAnalysisTabls += "acknowledged BOOLEAN NOT NULL DEFAULT FALSE," + createAnalysisTabls += "status VARCHAR(16) NOT NULL," + createAnalysisTabls += "duration INT NOT NULL DEFAULT 0," + createAnalysisTabls += "result VARCHAR(255) NOT NULL," + createAnalysisTabls += "PRIMARY KEY (id) );" + + _, err := txn.Exec(createAnalysisTabls) + if err != nil { + return err + } + + // createIndex := "CREATE INDEX charts_id ON charts (id);" + // _, err = txn.Exec(createIndex) + // if err != nil { + // return err + // } + + return nil + }) +} diff --git a/src/jetstream/plugins/analysis/container/Dockerfile b/src/jetstream/plugins/analysis/container/Dockerfile new file mode 100644 index 0000000000..b9863a323a --- /dev/null +++ b/src/jetstream/plugins/analysis/container/Dockerfile @@ -0,0 +1,61 @@ +FROM splatform/stratos-bk-build-base:leap15_1 as builder + +# Build the API Server for the analysis engines + +RUN mkdir -p /home/stratos/go/src +WORKDIR /home/stratos/go/src +COPY --chown=stratos:users . /home/stratos/go/src +ARG VERSION=1.0.0 +RUN GO111MODULE=on go build -o stratos-analyzers -ldflags -X=main.appVersion=${VERSION} + +# Download the Analysis tools +WORKDIR /home/stratos/analysis +WORKDIR /home/stratos/tmp +USER root + +# Analyzers ==================================================================================================================== + + +# Popeye +ARG POPEYE_VERSION=0.6.2 +# Download archive - popeye executable is in main dir - move it to the analysis folder +RUN wget https://github.com/derailed/popeye/releases/download/v${POPEYE_VERSION}/popeye_${POPEYE_VERSION}_Linux_x86_64.tar.gz && \ + tar -xvf popeye_${POPEYE_VERSION}_Linux_x86_64.tar.gz && \ + mv popeye ../analysis + +# Kube-score +ARG KUBESCORE_VERSION=1.5.0 +RUN wget https://github.com/zegl/kube-score/releases/download/v${KUBESCORE_VERSION}/kube-score_${KUBESCORE_VERSION}_linux_amd64.tar.gz && \ + tar -xvf kube-score_${KUBESCORE_VERSION}_linux_amd64.tar.gz && \ + mv kube-score ../analysis + +# Sonobuoy +# ARG SONOBUOY_VERSION=0.17.2 +# RUN wget https://github.com/vmware-tanzu/sonobuoy/releases/download/v${SONOBUOY_VERSION}/sonobuoy_${SONOBUOY_VERSION}_linux_amd64.tar.gz && \ +# tar -xvf sonobuoy_${SONOBUOY_VERSION}_linux_amd64.tar.gz && \ +# mv sonobuoy ../analysis + +# Need kubectl for Kubescore - TODO: Use correct version depending on cluster +ARG KUBECTL_VERSION=1.16.2 +RUN wget https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl && \ + chmod +x kubectl && \ + mv kubectl ../analysis + +# klar +# ARG KLAR_VERSION=2.4.0 +# RUN wget https://github.com/optiopay/klar/releases/download/v${KLAR_VERSION}/klar-${KLAR_VERSION}-linux-amd64 && \ +# mv klar-${KLAR_VERSION}-linux-amd64 klar && \ +# chmod +x klar && \ +# mv klar ../analysis + +# Final Container ============================================================================================================= + +FROM splatform/stratos-bk-base:leap15_1 + +# Copy tools to the /usr/bin folder so that they are in the path +COPY --from=builder /home/stratos/analysis /usr/bin +COPY --from=builder /home/stratos/go/src/stratos-analyzers /stratos-analyzers +COPY ./scripts /scripts +RUN mkdir /reports + +CMD ["/stratos-analyzers"] diff --git a/src/jetstream/plugins/analysis/container/go.mod b/src/jetstream/plugins/analysis/container/go.mod new file mode 100644 index 0000000000..d9101c6c6c --- /dev/null +++ b/src/jetstream/plugins/analysis/container/go.mod @@ -0,0 +1,12 @@ +module analyzers + +go 1.13 + +require ( + github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect + github.com/labstack/echo v3.3.10+incompatible + github.com/labstack/gommon v0.3.0 // indirect + github.com/sirupsen/logrus v1.4.2 + github.com/valyala/fasttemplate v1.1.0 // indirect + golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d // indirect +) diff --git a/src/jetstream/plugins/analysis/container/go.sum b/src/jetstream/plugins/analysis/container/go.sum new file mode 100644 index 0000000000..ae076adf3a --- /dev/null +++ b/src/jetstream/plugins/analysis/container/go.sum @@ -0,0 +1,48 @@ +github.com/cloudfoundry-incubator/stratos v2.0.0-beta-001+incompatible h1:UUxNbLjhv2cfymub5yNN1tjjqYkteHBBagb4jcbXEIQ= +github.com/cloudfoundry-incubator/stratos/src/jetstream v0.0.0-20200222120421-390cf0f6670b h1:52Py09Cmdnyxr750Tj5InffbWJpCDTWie0RCbxxoUAA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= +github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= +github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4= +github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/src/jetstream/plugins/analysis/container/kubescore.go b/src/jetstream/plugins/analysis/container/kubescore.go new file mode 100644 index 0000000000..959e15964b --- /dev/null +++ b/src/jetstream/plugins/analysis/container/kubescore.go @@ -0,0 +1,59 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "time" + + log "github.com/sirupsen/logrus" +) + +func runKubeScore(job *AnalysisJob) error { + + log.Debug("Running kube-score job") + + job.Busy = true + job.Type = "kubescore" + job.Format = "kubescore" + setJobNameAndPath(job, "Kube-score") + + scriptPath := filepath.Join(getScriptFolder(), "kubescore-runner.sh") + args := []string{scriptPath, job.KubeConfigPath, job.Config.Namespace} + + log.Infof("Running kube score job: %s", job.Path) + + go func() { + // Use our custom script which is a wrapper around kubescore + cmd := exec.Command("bash", args...) + cmd.Dir = job.Folder + cmd.Env = make([]string, 0) + cmd.Env = append(cmd.Env, fmt.Sprintf("KUBECONFIG=%s", job.KubeConfigPath)) + + start := time.Now() + out, err := cmd.Output() + end := time.Now() + + log.Infof("Completed kube score job: %s", job.Path) + + // Remove any config files when done + job.RemoveTempFiles() + + job.Duration = int(end.Sub(start).Seconds()) + + if err != nil { + // There was an error + // Remove the folder + os.Remove(job.Folder) + job.Status = "error" + } else { + reportFile := filepath.Join(job.Folder, "report.log") + ioutil.WriteFile(reportFile, out, os.ModePerm) + job.Status = "completed" + } + }() + + return nil +} diff --git a/src/jetstream/plugins/analysis/container/main.go b/src/jetstream/plugins/analysis/container/main.go new file mode 100644 index 0000000000..533ec83786 --- /dev/null +++ b/src/jetstream/plugins/analysis/container/main.go @@ -0,0 +1,171 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/labstack/echo" + "github.com/labstack/echo/middleware" + log "github.com/sirupsen/logrus" +) + +const ( + defaultPort = 8090 + defaultAddress = "0.0.0.0" + reportsDirEnvVar = "ANALYSIS_REPORTS_DIR" + scriptsDirEnvVar = "ANALYSIS_SCRIPTS_DIR" +) + +type Analyzer struct { + reportsDir string + jobs map[string]*AnalysisJob +} + +func main() { + log.SetFormatter(&log.TextFormatter{ForceColors: true, FullTimestamp: true, TimestampFormat: time.UnixDate}) + + log.SetOutput(os.Stdout) + + log.Info("========================================") + log.Info("=== Stratos Analysis API Server ===") + log.Info("========================================") + log.Info("") + log.Info("Initialization started.") + + analyzer := Analyzer{} + analyzer.jobs = make(map[string]*AnalysisJob) + + analyzer.Start() +} + +func (a *Analyzer) Start() { + + // Reports folder + + // Init reports directory + if reportsDir, ok := os.LookupEnv(reportsDirEnvVar); ok { + dir, err := filepath.Abs(reportsDir) + if err != nil { + log.Fatal("Can not get absolute path for reports folder") + } + a.reportsDir = dir + } else { + a.reportsDir = filepath.Join(os.TempDir(), "stratos-analysis") + } + log.Infof("Using reports folder: %s", a.reportsDir) + + // Make the directory if it does not exit + if _, err := os.Stat(a.reportsDir); os.IsNotExist(err) { + if os.MkdirAll(a.reportsDir, os.ModePerm) != nil { + log.Fatal("Could not create folder for analysis reports") + } + } + + // Start a simple web server + e := echo.New() + e.HideBanner = true + e.HidePort = true + customLoggerConfig := middleware.LoggerConfig{ + Format: `Request: [${time_rfc3339}] Remote-IP:"${remote_ip}" ` + + `Method:"${method}" Path:"${path}" Status:${status} Latency:${latency_human} ` + + `Bytes-In:${bytes_in} Bytes-Out:${bytes_out}` + "\n", + } + e.Use(middleware.LoggerWithConfig(customLoggerConfig)) + e.Use(middleware.Recover()) + + a.registerRoutes(e) + + var engineErr error + address := fmt.Sprintf("%s:%d", defaultAddress, defaultPort) + log.Infof("Starting HTTP Server at address: %s", address) + engineErr = e.Start(address) + + if engineErr != nil { + engineErrStr := fmt.Sprintf("%s", engineErr) + if !strings.Contains(engineErrStr, "Server closed") { + log.Warnf("Failed to start HTTP/S server: %+v", engineErr) + } + } +} + +func (a *Analyzer) registerRoutes(e *echo.Echo) { + api := e.Group("/api") + api.Use(setSecureCacheContentMiddleware) + + // Liveness check + api.GET("/v1/ping", a.ping) + // Run the given analyzer + api.POST("/v1/run/:analyzer", a.run) + // Get status + api.POST("/v1/status", a.status) + // Get a report + api.GET("/v1/report/:user/:endpoint/:id/:file", a.report) + // Delete a report + api.DELETE("/v1/report/:user/:endpoint/:id", a.delete) + // Delete all reports for an endpoint + api.DELETE("/v1/report/:endpoint", a.deleteEndpoint) +} + +func setSecureCacheContentMiddleware(h echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + c.Response().Header().Set("cache-control", "no-store") + c.Response().Header().Set("pragma", "no-cache") + return h(c) + } +} + +// Set the name of the job +func setJobNameAndPath(job *AnalysisJob, title string) { + job.Name = fmt.Sprintf("%s cluster analysis", title) + job.Path = "" + + log.Info("setJobNameAndPath") + log.Infof("%+v", job.Config) + + if job.Config != nil { + if len(job.Config.Namespace) > 0 { + if len(job.Config.App) > 0 { + job.Name = fmt.Sprintf("%s workload analysis: %s in %s", title, job.Config.App, job.Config.Namespace) + job.Path = fmt.Sprintf("%s/%s", job.Config.Namespace, job.Config.App) + } else { + job.Name = fmt.Sprintf("%s namespace analysis: %s", title, job.Config.Namespace) + job.Path = job.Config.Namespace + } + } + } +} + +func getScriptFolder() string { + fallbackPath, err := os.Getwd() + if err != nil { + fallbackPath = "." + } + + // Look first at the env var, then at a relative path to the executable + if dir, ok := os.LookupEnv(scriptsDirEnvVar); ok { + return dir + } + + // Relative to the executable + dir, err := filepath.Abs(filepath.Dir(os.Args[0])) + if err != nil { + log.Error("Could not get folder of the running program") + return fallbackPath + } + + scripts := filepath.Join(dir, "scripts") + if _, err := os.Stat(scripts); !os.IsNotExist(err) { + return scripts + } + + scripts = filepath.Join(dir, "plugins±", "analysis", "container", "scripts") + if _, err := os.Stat(scripts); !os.IsNotExist(err) { + return scripts + } + + log.Error("Unable to locate scripts folder") + return fallbackPath +} diff --git a/src/jetstream/plugins/analysis/container/popeye.go b/src/jetstream/plugins/analysis/container/popeye.go new file mode 100644 index 0000000000..209224a92e --- /dev/null +++ b/src/jetstream/plugins/analysis/container/popeye.go @@ -0,0 +1,108 @@ +package main + +import ( + "encoding/json" + "errors" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "time" + + log "github.com/sirupsen/logrus" +) + +type popEyeSummary struct { + Score int `json:"score"` + Grade string `json:"grade"` +} + +type popEyeResult struct { + PopEye popEyeSummary `json:"popeye"` +} + +func runPopeye(job *AnalysisJob) error { + + log.Debug("Running popeye job") + + job.Busy = true + job.Type = "popeye" + job.Format = "popeye" + setJobNameAndPath(job, "Popeye") + + log.Infof("Running popeye job: %s", job.Path) + + args := []string{"--kubeconfig", job.KubeConfigPath, "-o", "json", "--insecure-skip-tls-verify"} + if len(job.Config.Namespace) > 0 { + args = append(args, "-n") + args = append(args, job.Config.Namespace) + } else { + args = append(args, "-A") + } + + go func() { + cmd := exec.Command("popeye", args...) + cmd.Dir = job.Folder + + start := time.Now() + out, err := cmd.Output() + end := time.Now() + job.EndTime = end + + job.Busy = false + + log.Infof("Completed kube score job: %s", job.Path) + + // Remove any config files when done + job.RemoveTempFiles() + + job.Duration = int(end.Sub(start).Seconds()) + + if err != nil { + // There was an error + // Remove the folder + os.Remove(job.Folder) + job.Status = "error" + } else { + reportFile := filepath.Join(job.Folder, "report.json") + ioutil.WriteFile(reportFile, out, os.ModePerm) + job.Status = "completed" + + // Parse the report + if summary, err := parsePopeyeReport(reportFile); err == nil { + job.Result = serializePopeyeReport(summary) + } + } + }() + + return nil +} + +func parsePopeyeReport(file string) (*popEyeSummary, error) { + jsonFile, err := os.Open(file) + if err != nil { + return nil, err + } + defer jsonFile.Close() + + data, err := ioutil.ReadAll(jsonFile) + if err != nil { + return nil, err + } + + result := popEyeResult{} + if err = json.Unmarshal(data, &result); err != nil { + return nil, errors.New("Failed to parse Popeye report") + } + + return &result.PopEye, nil +} + +func serializePopeyeReport(summary *popEyeSummary) string { + jsonString, err := json.Marshal(summary) + if err != nil { + return "" + } + + return string(jsonString) +} diff --git a/src/jetstream/plugins/analysis/container/routes.go b/src/jetstream/plugins/analysis/container/routes.go new file mode 100644 index 0000000000..3b3c846e2b --- /dev/null +++ b/src/jetstream/plugins/analysis/container/routes.go @@ -0,0 +1,90 @@ +package main + +import ( + "errors" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "strings" + + "github.com/labstack/echo" + log "github.com/sirupsen/logrus" +) + +// Ping endpoint +func (a *Analyzer) ping(ec echo.Context) error { + return nil +} + +// Get a given report +func (a *Analyzer) report(ec echo.Context) error { + + user := ec.Param("user") + endpoint := ec.Param("endpoint") + id := ec.Param("id") + name := ec.Param("file") + + // Name must end in json - we only serve json files + if !strings.HasSuffix(name, ".json") { + return errors.New("Can't serve that file") + } + + file := filepath.Join(a.reportsDir, user, endpoint, id, name) + _, err := os.Stat(file) + if os.IsNotExist(err) { + return echo.NewHTTPError(404, "No such file") + } + + return ec.File(file) +} + +// Delete a given report +func (a *Analyzer) delete(ec echo.Context) error { + log.Debug("delete report") + + user := ec.Param("user") + endpoint := ec.Param("endpoint") + id := ec.Param("id") + folder := filepath.Join(a.reportsDir, user, endpoint, id) + if err := os.RemoveAll(folder); err != nil { + log.Warnf("Could not delete Analysis report folder: %s", folder) + return echo.NewHTTPError(http.StatusInternalServerError, "Could not delete report") + } + + return nil +} + +// Delete all reports for a given endpoint +func (a *Analyzer) deleteEndpoint(ec echo.Context) error { + log.Debug("delete reports for endpoint") + + endpoint := ec.Param("endpoint") + + // Iterate over all user folders + if items, err := ioutil.ReadDir(a.reportsDir); err == nil { + for _, item := range items { + if item.IsDir() { + // This is a user's folder - see if they have a folder for the endpoint + folder := filepath.Join(a.reportsDir, item.Name(), endpoint) + if folderExists(folder) { + if err := os.RemoveAll(folder); err != nil { + log.Warnf("Could not delete Analysis report endpoint folder: %s", folder) + } + } + } + } + } else { + return echo.NewHTTPError(http.StatusInternalServerError, "Error deleteing reports") + } + + return nil +} + +func folderExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return info.IsDir() +} diff --git a/src/jetstream/plugins/analysis/container/run.go b/src/jetstream/plugins/analysis/container/run.go new file mode 100644 index 0000000000..06310a41eb --- /dev/null +++ b/src/jetstream/plugins/analysis/container/run.go @@ -0,0 +1,130 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + + "github.com/labstack/echo" + log "github.com/sirupsen/logrus" +) + +const idHeaderName = "X-Stratos-Analaysis-ID" + +func (a *Analyzer) run(ec echo.Context) error { + err := a.doRun(ec) + if err != nil { + log.Error(err) + } + return err +} + +func (a *Analyzer) doRun(ec echo.Context) error { + + log.Debug("Run analyzer!") + + engine := ec.Param("analyzer") + if len(engine) == 0 { + log.Warn("No analyzer") + return errors.New("No analyzer specified") + } + + // ID is username/endpoint/id + id := ec.Request().Header.Get(idHeaderName) + if len(id) == 0 { + return errors.New("Mising ID header") + } + + folder := filepath.Join(a.reportsDir, id) + if os.MkdirAll(folder, os.ModePerm) != nil { + return errors.New("Could not create folder for analysis report") + } + + tempFiles := make([]string, 0) + reader, err := ec.Request().MultipartReader() + if err != nil { + log.Error("Could not parse request") + log.Error(err) + return errors.New("Failed to parse request payload") + } + + job := AnalysisJob{} + params := kubeAnalyzerConfig{} + + for { + part, err := reader.NextPart() + if err == io.EOF { + break + } + if err != nil { + log.Error("Unexpected error when retrieving a part of the message") + return errors.New("Unexpected error when retrieving a part of the message") + } + defer part.Close() + fileBytes, err := ioutil.ReadAll(part) + if err != nil { + log.Error("Failed to read content of the part") + return errors.New("Failed to read content of the part") + } + filename := part.Header.Get("Content-ID") + + // Decide what to do with the part + switch filename { + case "job": + if err = json.Unmarshal(fileBytes, &job); err != nil { + return fmt.Errorf("Can not parse Job: %v", err) + } + case "body": + if err = json.Unmarshal(fileBytes, ¶ms); err != nil { + return fmt.Errorf("Can not parse parameters: %v", err) + } + job.Config = ¶ms + default: + fullpath := filepath.Join(folder, filename) + if err = ioutil.WriteFile(fullpath, fileBytes, os.ModePerm); err != nil { + log.Error("Could not write data for: %s", filename) + return fmt.Errorf("Could not write file data for: %s", filename) + } + if filename == "kubeconfig" { + job.KubeConfigPath = fullpath + } + tempFiles = append(tempFiles, fullpath) + } + } + + if len(job.ID) == 0 { + return errors.New("Invalid Job metadata supplied") + } + + job.Folder = folder + job.TempFiles = tempFiles + + // Store the job so we track which jobs are running + a.jobs[job.ID] = &job + + job.Status = "running" + + switch engine { + case "popeye": + err = runPopeye(&job) + case "kube-score": + err = runKubeScore(&job) + // case "sonobuoy": + // runSonobuoy(dbStore, file, folder, report, requestBody) + default: + job.Status = "error" + return fmt.Errorf("Unkown analyzer: %s", engine) + } + + if err != nil { + job.Status = "error" + log.Error("Error running analyzer: %s", err) + } + + return ec.JSON(http.StatusOK, job) +} diff --git a/src/jetstream/plugins/analysis/container/scripts/kubescore-runner.sh b/src/jetstream/plugins/analysis/container/scripts/kubescore-runner.sh new file mode 100755 index 0000000000..2763b3008f --- /dev/null +++ b/src/jetstream/plugins/analysis/container/scripts/kubescore-runner.sh @@ -0,0 +1,16 @@ +ARGS="--all-namespaces" + +if [ -n "$2" ]; then + ARGS="-n ${2}" +fi + +# $1 is the kubeconfig file + +echo "Kubescore runner..." +echo "Running report..." + +kubectl api-resources --verbs=list --namespaced -o name \ + | xargs -n1 -I{} bash -c "kubectl get {} $ARGS -oyaml && echo ---" \ + | kube-score score -o json - > report.json + +exit 0 diff --git a/src/jetstream/plugins/analysis/container/scripts/sonobuoy-runner.sh b/src/jetstream/plugins/analysis/container/scripts/sonobuoy-runner.sh new file mode 100755 index 0000000000..8565beed6f --- /dev/null +++ b/src/jetstream/plugins/analysis/container/scripts/sonobuoy-runner.sh @@ -0,0 +1,19 @@ +# $1 is the kubeconfig file + +echo "Sonobuoy runner..." +env +echo "Args" +echo $@ + +echo "Running report..." + +# Run the report and wait +sonobuoy run --wait + +# Retrieve the report + +# Teardown sonobuoy + +# Unpack the report and copy the junit report to report.json at the top-level + +exit 0 diff --git a/src/jetstream/plugins/analysis/container/sonobuoy.go_ b/src/jetstream/plugins/analysis/container/sonobuoy.go_ new file mode 100644 index 0000000000..80be589427 --- /dev/null +++ b/src/jetstream/plugins/analysis/container/sonobuoy.go_ @@ -0,0 +1,92 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/analysis/store" + log "github.com/sirupsen/logrus" +) + +func runSonobuoy(dbStore store.AnalysisStore, kubeconfig, folder string, report store.AnalysisRecord, body []byte) error { + path := "" + namespace := "" + options := &popeyeConfig{} + if err := json.Unmarshal(body, options); err == nil { + namespace = options.Namespace + path = namespace + + if len(options.App) > 0 { + path = fmt.Sprintf("%s/%s", path, options.App) + } + } + report.Name = "Sonobuoy cluster analysis" + report.Type = "sonobuoy" + report.Format = "junit" + + scriptPath := filepath.Join(getScriptFolder(), "sonobuoy-runner.sh") + args := []string{scriptPath, kubeconfig, namespace} + log.Error(scriptPath) + + report.Path = path + parts := len(strings.Split(path, "/")) + if parts == 2 { + report.Name = fmt.Sprintf("Sonobuoy workload analysis: %s in %s", options.App, namespace) + } else if parts == 1 && len(namespace) > 0 { + report.Name = fmt.Sprintf("Sonobuoy namespace analysis: %s", namespace) + } + + _, err := dbStore.Save(report) + if err != nil { + return err + } + + go func() { + // Use our custom script which is a wrapper around kubescore + cmd := exec.Command("bash", args...) + cmd.Dir = folder + cmd.Env = make([]string, 0) + cmd.Env = append(cmd.Env, fmt.Sprintf("KUBECONFIG=%s", kubeconfig)) + log.Info(kubeconfig) + + start := time.Now() + out, err := cmd.Output() + end := time.Now() + + // Remove the config file when we are done + //os.Remove(kubeconfig) + + if err != nil { + // There was an error + // Remove the folder + os.Remove(folder) + log.Error(">>>>>>>>> ERROR <<<<<<<<<") + log.Error(string(out)) + log.Error(err) + report.Status = "error" + } else { + report.Status = "completed" + + // Parse the report + // if summary, err := parsePopeyeReport(reportFile); err == nil { + // report.Result = serializePopeyeReport(summary) + // } + + // Write stdout to log file + reportFile := filepath.Join(folder, "report.log") + ioutil.WriteFile(reportFile, out, os.ModePerm) + } + + report.Duration = int(end.Sub(start).Seconds()) + + dbStore.UpdateReport(report.UserID, &report) + }() + + return nil +} diff --git a/src/jetstream/plugins/analysis/container/status.go b/src/jetstream/plugins/analysis/container/status.go new file mode 100644 index 0000000000..52ad376502 --- /dev/null +++ b/src/jetstream/plugins/analysis/container/status.go @@ -0,0 +1,74 @@ +package main + +import ( + "encoding/json" + "errors" + "io/ioutil" + + "github.com/labstack/echo" + log "github.com/sirupsen/logrus" +) + +func (a *Analyzer) status(ec echo.Context) error { + err := a.doStatus(ec) + if err != nil { + log.Error(err) + } + return err +} + +func (a *Analyzer) doStatus(ec echo.Context) error { + log.Debug("Status") + req := ec.Request() + + // Body contains an array of IDs that the client thinks are running + // We send back updated status for each + + // Get the list of IDs + defer req.Body.Close() + body, err := ioutil.ReadAll(req.Body) + if err != nil { + return errors.New("Could not read body") + } + + ids := make([]string, 0) + if err := json.Unmarshal(body, &ids); err != nil { + return errors.New("Failed to parse body") + } + + response := make(map[string]AnalysisJob) + for _, id := range ids { + if a.jobs[id] == nil { + // Client has a running job that we know nothing about - so must be an error + job := AnalysisJob{ + ID: id, + Status: "error", + } + response[id] = job + } else { + response[id] = *a.jobs[id] + } + } + + // Go through all of the jobs we have and increment the cleanup counter of those that are finished + // Assume after 5 requests to the status API that the caller has the info they need for the completed job + // and remove it + cleanup := make([]string, 0) + for id, job := range a.jobs { + // If the job has finished, increment the cleanup counter + // We will remove it from our cache once we are pretty sure Jetstream has the status + if !job.Busy { + job.CleanupCounter = job.CleanupCounter + 1 + if job.CleanupCounter > 5 { + cleanup = append(cleanup, id) + } + } + } + + for _, id := range cleanup { + delete(a.jobs, id) + } + + ec.JSON(200, response) + return nil +} diff --git a/src/jetstream/plugins/analysis/container/types.go b/src/jetstream/plugins/analysis/container/types.go new file mode 100644 index 0000000000..fb9de49c8c --- /dev/null +++ b/src/jetstream/plugins/analysis/container/types.go @@ -0,0 +1,48 @@ +package main + +import ( + "encoding/json" + "os" + "time" + + log "github.com/sirupsen/logrus" +) + +type kubeAnalyzerConfig struct { + Namespace string `json:"namespace"` + App string `json:"app"` +} + +// AnalysisJob is the metadata format sent to and from the analyzer +type AnalysisJob struct { + ID string `json:"id"` + UserID string `json:"-"` + EndpointType string `json:"endpointType"` + EndpointID string `json:"endpoint"` + Type string `json:"type"` + Path string `json:"path"` + Format string `json:"format"` + Name string `json:"name"` + Status string `json:"status"` + Duration int `json:"duration"` + Result string `json:"-"` + Summary *json.RawMessage `json:"summary"` + Config *kubeAnalyzerConfig `json:"-"` + Folder string `json:"-"` + KubeConfigPath string `json:"-"` + TempFiles []string `json:"-"` + Busy bool `json:"-"` + EndTime time.Time `json:"-"` + CleanupCounter int `json:"-"` +} + +// RemoveTempFiles will remove any temporary files +func (job *AnalysisJob) RemoveTempFiles() { + log.Debug("Removing temporary files") + for _, name := range job.TempFiles { + err := os.Remove(name) + if err != nil { + log.Error("Could not delete file: %s", name) + } + } +} diff --git a/src/jetstream/plugins/analysis/list.go b/src/jetstream/plugins/analysis/list.go new file mode 100644 index 0000000000..c03f7af4fe --- /dev/null +++ b/src/jetstream/plugins/analysis/list.go @@ -0,0 +1,228 @@ +package analysis + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "strings" + "time" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/analysis/store" + + "github.com/labstack/echo" + + log "github.com/sirupsen/logrus" +) + +const mainReportFile = "report.json" + +// listReports will list the analysis repotrs that have run +func (c *Analysis) listReports(ec echo.Context) error { + log.Debug("listReports") + var p = c.portalProxy + + // Need to get a config object for the target endpoint + // endpointGUID := ec.Param("endpoint") + userID := ec.Get("user_id").(string) + endpointID := ec.Param("endpoint") + + // Create a record in the reports datastore + dbStore, err := store.NewAnalysisDBStore(p.GetDatabaseConnection()) + if err != nil { + return err + } + + reports, err := dbStore.List(userID, endpointID) + if err != nil { + return err + } + + for _, report := range reports { + populateSummary(report) + } + + return ec.JSON(200, reports) +} + +// getReportsByPath will list the completed analysis repotrs that have run for the specified path +func (c *Analysis) getReportsByPath(ec echo.Context) error { + log.Debug("getReportsByPath") + var p = c.portalProxy + + // Need to get a config object for the target endpoint + userID := ec.Get("user_id").(string) + endpointID := ec.Param("endpoint") + + pathPrefix := fmt.Sprintf("completed/%s/", endpointID) + index := strings.Index(ec.Request().RequestURI, pathPrefix) + if index < 0 { + return errors.New("Invalid request") + } + path := ec.Request().RequestURI[index+len(pathPrefix):] + + // Create a record in the reports datastore + dbStore, err := store.NewAnalysisDBStore(p.GetDatabaseConnection()) + if err != nil { + return err + } + + reports, err := dbStore.ListCompletedByPath(userID, endpointID, path) + if err != nil { + return err + } + + for _, report := range reports { + populateSummary(report) + } + + return ec.JSON(200, reports) +} + +func populateSummary(report *store.AnalysisRecord) { + if report.Status == "error" { + report.Error = report.Result + } else if len(report.Result) > 0 { + data := []byte(report.Result) + report.Summary = (*json.RawMessage)(&data) + } +} + +func (c *Analysis) getLatestReport(ec echo.Context) error { + log.Debug("getLatestReport") + var p = c.portalProxy + + // Need to get a config object for the target endpoint + userID := ec.Get("user_id").(string) + endpointID := ec.Param("endpoint") + + pathPrefix := fmt.Sprintf("latest/%s/", endpointID) + index := strings.Index(ec.Request().RequestURI, pathPrefix) + if index < 0 { + return errors.New("Invalid request") + } + path := ec.Request().RequestURI[index+len(pathPrefix):] + + // Create a record in the reports datastore + dbStore, err := store.NewAnalysisDBStore(p.GetDatabaseConnection()) + if err != nil { + return err + } + + report, err := dbStore.GetLatestCompleted(userID, endpointID, path) + if err != nil { + return echo.NewHTTPError(404, "No Analysis Report found") + } + + if ec.Request().Method == "HEAD" { + ec.Response().Status = 200 + return nil + } + + // Get the report contents from the analysis server + bytes, err := c.getReportFile(report.UserID, report.EndpointID, report.ID, mainReportFile) + if err != nil { + return err + } + + report.Report = (*json.RawMessage)(&bytes) + return ec.JSON(200, report) +} + +func (c *Analysis) getReport(ec echo.Context) error { + log.Debug("getReport") + var p = c.portalProxy + + // Need to get a config object for the target endpoint + userID := ec.Get("user_id").(string) + ID := ec.Param("id") + file := ec.Param("file") + if len(file) == 0 { + file = mainReportFile + } + + // Create a record in the reports datastore + dbStore, err := store.NewAnalysisDBStore(p.GetDatabaseConnection()) + if err != nil { + return err + } + + report, err := dbStore.Get(userID, ID) + if err != nil { + return err + } + + // Get the report contents from the analysis server + bytes, err := c.getReportFile(report.UserID, report.EndpointID, report.ID, file) + if err != nil { + return err + } + + report.Report = (*json.RawMessage)(&bytes) + return ec.JSON(200, report) +} + +func (c *Analysis) deleteReports(ec echo.Context) error { + log.Debug("deleteReports") + var p = c.portalProxy + + // Need to get a config object for the target endpoint + userID := ec.Get("user_id").(string) + + defer ec.Request().Body.Close() + body, err := ioutil.ReadAll(ec.Request().Body) + if err != nil { + return err + } + + var ids []string + ids = make([]string, 0) + if err = json.Unmarshal(body, &ids); err != nil { + return err + } + + dbStore, err := store.NewAnalysisDBStore(p.GetDatabaseConnection()) + if err != nil { + return err + } + + for _, id := range ids { + // Look up the report to get the endpoint ID + if job, err := dbStore.Get(userID, id); err == nil { + deleteURL := fmt.Sprintf("%s/api/v1/report/%s/%s/%s", c.analysisServer, job.UserID, job.EndpointID, job.ID) + r, _ := http.NewRequest(http.MethodDelete, deleteURL, nil) + client := &http.Client{Timeout: 30 * time.Second} + rsp, err := client.Do(r) + if err != nil { + log.Warnf("Could not delete analysis report for: %s", job.ID) + } else if rsp.StatusCode != http.StatusOK { + log.Warnf("Could not delete analysis report for: %s", job.ID) + } + } + dbStore.Delete(userID, id) + } + + return ec.JSON(200, ids) +} + +func (c *Analysis) getReportFile(userID, endpointID, ID, name string) ([]byte, error) { + // Make request to get report + statusURL := fmt.Sprintf("%s/api/v1/report/%s/%s/%s/%s", c.analysisServer, userID, endpointID, ID, name) + r, _ := http.NewRequest(http.MethodGet, statusURL, nil) + client := &http.Client{Timeout: 30 * time.Second} + rsp, err := client.Do(r) + if err != nil { + return nil, fmt.Errorf("Failed getting report from Analyzer service: %v", err) + } else if rsp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("Failed getting report from Analyzer service: %d", rsp.StatusCode) + } + + defer rsp.Body.Close() + response, err := ioutil.ReadAll(rsp.Body) + if err != nil { + return nil, fmt.Errorf("Could not read response: %v", err) + } + + return response, nil +} diff --git a/src/jetstream/plugins/analysis/main.go b/src/jetstream/plugins/analysis/main.go new file mode 100644 index 0000000000..33dbc43917 --- /dev/null +++ b/src/jetstream/plugins/analysis/main.go @@ -0,0 +1,139 @@ +package analysis + +import ( + "errors" + "fmt" + "io/ioutil" + "net/http" + "time" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/analysis/store" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" + + "github.com/labstack/echo" + log "github.com/sirupsen/logrus" +) + +const ( + analsyisServicesAPIEnvVar = "ANALYSIS_SERVICES_API" + + // Allow specific engines to be enabled + analysisEnginesAPIEnvVar = "ANALYSIS_ENGINES" + + // Names used to communicate settings info back to the front-end client + analysisEnabledPluginConfigSetting = "analysisEnabled" + analysisEnginesPluginConfigSetting = "analysisEngines" + + defaultEngines = "popeye" +) + +// Analysis - Plugin to allow analysers to run over an endpoint cluster +type Analysis struct { + portalProxy interfaces.PortalProxy + analysisServer string +} + +// Init creates a new Analysis +func Init(portalProxy interfaces.PortalProxy) (interfaces.StratosPlugin, error) { + store.InitRepositoryProvider(portalProxy.GetConfig().DatabaseProviderName) + return &Analysis{portalProxy: portalProxy}, nil +} + +// GetMiddlewarePlugin gets the middleware plugin for this plugin +func (analysis *Analysis) GetMiddlewarePlugin() (interfaces.MiddlewarePlugin, error) { + return nil, errors.New("Not implemented") +} + +// GetEndpointPlugin gets the endpoint plugin for this plugin +func (analysis *Analysis) GetEndpointPlugin() (interfaces.EndpointPlugin, error) { + return nil, errors.New("Not implemented") +} + +// GetRoutePlugin gets the route plugin for this plugin +func (analysis *Analysis) GetRoutePlugin() (interfaces.RoutePlugin, error) { + return analysis, nil +} + +// AddAdminGroupRoutes adds the admin routes for this plugin to the Echo server +func (analysis *Analysis) AddAdminGroupRoutes(echoGroup *echo.Group) { + // no-op +} + +// AddSessionGroupRoutes adds the session routes for this plugin to the Echo server +func (analysis *Analysis) AddSessionGroupRoutes(echoGroup *echo.Group) { + echoGroup.GET("/analysis/reports/:endpoint", analysis.listReports) + echoGroup.GET("/analysis/reports/:endpoint/:id", analysis.getReport) + echoGroup.GET("/analysis/reports/:endpoint/:id/:file", analysis.getReport) + + // Get completed reports for the given path + echoGroup.GET("/analysis/completed/:endpoint/*", analysis.getReportsByPath) + + // Get latest report + echoGroup.GET("/analysis/latest/:endpoint/*", analysis.getLatestReport) + echoGroup.HEAD("/analysis/latest/:endpoint/*", analysis.getLatestReport) + + echoGroup.DELETE("/analysis/reports", analysis.deleteReports) + + // Run report + echoGroup.POST("/analysis/run/:analyzer/:endpoint", analysis.runReport) +} + +// Init performs plugin initialization +func (analysis *Analysis) Init() error { + // Only enabled in tech preview + if !analysis.portalProxy.GetConfig().EnableTechPreview { + // This will set PluginsStatus[name] = false, which results in plugins[name] in the FE + return errors.New("Requires tech preview") + } + + // Check env var + if url, ok := analysis.portalProxy.Env().Lookup(analsyisServicesAPIEnvVar); ok { + analysis.analysisServer = url + + // Start background status check + analysis.initStatusCheck() + + if engines, ok := analysis.portalProxy.Env().Lookup(analysisEnginesAPIEnvVar); ok { + analysis.portalProxy.GetConfig().PluginConfig[analysisEnginesPluginConfigSetting] = engines + } else { + analysis.portalProxy.GetConfig().PluginConfig[analysisEnginesPluginConfigSetting] = defaultEngines + } + + return nil + } + + return errors.New("Analysis services API Server not configured") +} + +// OnEndpointNotification called when for endpoint events +func (analysis *Analysis) OnEndpointNotification(action interfaces.EndpointAction, endpoint *interfaces.CNSIRecord) { + if action == interfaces.EndpointUnregisterAction { + // An endpoint was unregistered, so remove all reports + dbStore, err := store.NewAnalysisDBStore(analysis.portalProxy.GetDatabaseConnection()) + if err == nil { + dbStore.DeleteForEndpoint(endpoint.GUID) + + // Now ask the analysis engine to to delete all files on disk + deleteURL := fmt.Sprintf("%s/api/v1/report/%s", analysis.analysisServer, endpoint.GUID) + r, _ := http.NewRequest(http.MethodDelete, deleteURL, nil) + client := &http.Client{Timeout: 30 * time.Second} + rsp, err := client.Do(r) + if err != nil { + log.Errorf("Failed deleting reports from Analyzer service: %v", err) + return + } + + if rsp.StatusCode != http.StatusOK { + log.Errorf("Failed deleting reports from Analyzer service: %d", rsp.StatusCode) + } + + if rsp.Body != nil { + defer rsp.Body.Close() + _, err = ioutil.ReadAll(rsp.Body) + if err != nil { + log.Errorf("Could not read response: %v", err) + } + } + } + } +} diff --git a/src/jetstream/plugins/analysis/run.go b/src/jetstream/plugins/analysis/run.go new file mode 100644 index 0000000000..7c64e569ed --- /dev/null +++ b/src/jetstream/plugins/analysis/run.go @@ -0,0 +1,188 @@ +package analysis + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "mime/multipart" + "net/http" + "net/textproto" + "time" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/analysis/store" + + "github.com/labstack/echo" + uuid "github.com/satori/go.uuid" + log "github.com/sirupsen/logrus" +) + +type popeyeConfig struct { + Namespace string `json:"namespace"` + App string `json:"app"` +} + +type KubeConfigExporter interface { + GetKubeConfigForEndpointUser(endpointID, userID string) (string, error) +} + +const idHeaderName = "X-Stratos-Analaysis-ID" + +func (c *Analysis) runReport(ec echo.Context) error { + log.Debug("runReport") + + analyzer := ec.Param("analyzer") + endpointID := ec.Param("endpoint") + userID := ec.Get("user_id").(string) + + // Look up the endpoint for the user + var p = c.portalProxy + endpoint, err := p.GetCNSIRecord(endpointID) + if err != nil { + return errors.New("Could not get endpoint information") + } + + report := store.AnalysisRecord{ + ID: uuid.NewV4().String(), + EndpointID: endpointID, + EndpointType: endpoint.CNSIType, + UserID: userID, + Path: "", + Created: time.Now(), + Read: false, + Duration: 0, + Status: "pending", + Result: "", + } + + // Create a record in the reports datastore + dbStore, err := store.NewAnalysisDBStore(p.GetDatabaseConnection()) + if err != nil { + return err + } + + report.Name = fmt.Sprintf("Analysis report %s", analyzer) + dbStore.Save((report)) + + err = c.doRunReport(ec, analyzer, endpointID, userID, dbStore, &report) + if err != nil { + report.Status = "error" + report.Result = err.Error() + dbStore.UpdateReport(userID, &report) + } + + return err + +} + +func (c *Analysis) doRunReport(ec echo.Context, analyzer, endpointID, userID string, dbStore store.AnalysisStore, report *store.AnalysisRecord) error { + + // Get Kube Config + k8s := c.portalProxy.GetPlugin("kubernetes") + if k8s == nil { + return errors.New("Could not find Kubernetes plugin") + } + + k8sConfig, ok := k8s.(KubeConfigExporter) + if !ok { + return errors.New("Could not find Kubernetes plugin interface") + } + + config, err := k8sConfig.GetKubeConfigForEndpointUser(endpointID, userID) + if err != nil { + return errors.New("Could not get Kube Config for the endpoint") + } + + id := fmt.Sprintf("%s/%s/%s", userID, endpointID, report.ID) + + // Create a multi-part form to send to the analyzer container + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + + // Add kube config + metadataHeader := textproto.MIMEHeader{} + metadataHeader.Set("Content-Type", "application/yaml") + metadataHeader.Set("Content-ID", "kubeconfig") + part, _ := writer.CreatePart(metadataHeader) + part.Write([]byte(config)) + + requestBody := make([]byte, 0) + + // Read body + defer ec.Request().Body.Close() + if b, err := ioutil.ReadAll((ec.Request().Body)); err == nil { + requestBody = b + } + + // Content that was posted to us + postHeader := textproto.MIMEHeader{} + postHeader.Set("Content-Type", "application/json") + postHeader.Set("Content-ID", "body") + part, _ = writer.CreatePart(postHeader) + part.Write(requestBody) + + // Report config + reportHeader := textproto.MIMEHeader{} + reportHeader.Set("Content-Type", "application/json") + reportHeader.Set("Content-ID", "job") + part, _ = writer.CreatePart(reportHeader) + job, err := json.Marshal(report) + if err != nil { + return errors.New("Could not serialize job") + } + part.Write(job) + writer.Close() + + // Post this to the Analyzer API + contentType := fmt.Sprintf("multipart/form-data; boundary=%s", writer.Boundary()) + uploadURL := fmt.Sprintf("%s/api/v1/run/%s", c.analysisServer, analyzer) + r, _ := http.NewRequest(http.MethodPost, uploadURL, bytes.NewReader(body.Bytes())) + r.Header.Set("Content-Type", contentType) + r.Header.Set(idHeaderName, id) + client := &http.Client{Timeout: 180 * time.Second} + rsp, err := client.Do(r) + if err != nil { + return errors.New("Analysis job failed - could not contact Analysis Server") + } + + if rsp.StatusCode != http.StatusOK { + log.Debugf("Request failed with response code: %d", rsp.StatusCode) + return fmt.Errorf("Analysis job failed with response code: %d", rsp.StatusCode) + } + + // Job submitted okay + // Updated job is in the response + + defer rsp.Body.Close() + response, err := ioutil.ReadAll(rsp.Body) + if err != nil { + return errors.New("Could not read response") + } + + updatedJob := store.AnalysisRecord{} + if err = json.Unmarshal(response, &updatedJob); err != nil { + return errors.New("Could not read response - could not deserialize response") + } + + report.Duration = updatedJob.Duration + report.Status = updatedJob.Status + report.Name = updatedJob.Name + report.Format = updatedJob.Format + report.Type = updatedJob.Type + report.Path = updatedJob.Path + + log.Debug("OK => Job submitted okay") + log.Debug("=======================================================") + log.Debugf("%+v", report) + log.Debug("=======================================================") + + err = dbStore.UpdateReport(userID, report) + if err != nil { + return fmt.Errorf("Could not save report %s", err) + } + + log.Debug("All done - job saved") + + return ec.JSON(200, report) +} diff --git a/src/jetstream/plugins/analysis/status.go b/src/jetstream/plugins/analysis/status.go new file mode 100644 index 0000000000..5635589289 --- /dev/null +++ b/src/jetstream/plugins/analysis/status.go @@ -0,0 +1,109 @@ +package analysis + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "time" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/analysis/store" + + log "github.com/sirupsen/logrus" +) + +// Start a poller to check the status +func (c *Analysis) initStatusCheck() { + + log.Info("Analysis Plugin: Starting status check ...") + + // Just loop forever, checking the status of running jobs every 10s + go func() { + for { + time.Sleep(10 * time.Second) + err := c.checkStatus() + if err != nil { + log.Errorf("Error checking status: %v", err) + } + } + }() +} + +func (c *Analysis) checkStatus() error { + log.Debug("Checking status....") + p := c.portalProxy + // Create a record in the reports datastore + dbStore, err := store.NewAnalysisDBStore(p.GetDatabaseConnection()) + if err != nil { + return fmt.Errorf("Status Check: Can not get anaylsis store db: %v", err) + } + + // Get all running jobs + running, err := dbStore.ListRunning() + if err != nil { + return fmt.Errorf("Can not get list of running jobs: %v", err) + } + + if len(running) == 0 { + return nil + } + + ids := make([]string, 0) + for _, job := range running { + log.Debugf("Got running job: %s", job.ID) + ids = append(ids, job.ID) + } + + data, err := json.Marshal(ids) + if err != nil { + log.Errorf("Could not marshal IDs: %v", err) + return fmt.Errorf("Could not marshal IDs: %v", err) + } + + // Make request to status + statusURL := fmt.Sprintf("%s/api/v1/status", c.analysisServer) + r, _ := http.NewRequest(http.MethodPost, statusURL, bytes.NewReader(data)) + r.Header.Set("Content-Type", "application/json") + client := &http.Client{Timeout: 180 * time.Second} + rsp, err := client.Do(r) + if err != nil { + return fmt.Errorf("Failed getting status from Analyzer service: %v", err) + } + + if rsp.StatusCode != http.StatusOK { + return fmt.Errorf("Failed getting status from Analyzer service: %d", rsp.StatusCode) + } + + defer rsp.Body.Close() + response, err := ioutil.ReadAll(rsp.Body) + if err != nil { + log.Errorf("Could not read response: %v", err) + return fmt.Errorf("Could not read response: %v", err) + } + + // Turn into map of IDs to Jobs + statuses := make(map[string]store.AnalysisRecord) + + if err := json.Unmarshal(response, &statuses); err != nil { + return fmt.Errorf("Could not parse response: %v", err) + } + + for _, job := range running { + if status, ok := statuses[job.ID]; ok { + job.Duration = status.Duration + job.Status = status.Status + if err := dbStore.UpdateReport(job.UserID, job); err != nil { + log.Warnf("Unable to update status for job %s: %v", job.ID, err) + } + } else { + // The analysis server did not know about our job, os mark as error + job.Status = "error" + if err := dbStore.UpdateReport(job.UserID, job); err != nil { + log.Warnf("Unable to update status for job %s: %v", job.ID, err) + } + } + } + + return nil +} diff --git a/src/jetstream/plugins/analysis/store/analysis_store_db.go b/src/jetstream/plugins/analysis/store/analysis_store_db.go new file mode 100644 index 0000000000..481408fa6f --- /dev/null +++ b/src/jetstream/plugins/analysis/store/analysis_store_db.go @@ -0,0 +1,164 @@ +package store + +import ( + "database/sql" + "fmt" + + log "github.com/sirupsen/logrus" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/datastore" +) + +var ( + listReports = `SELECT id, endpoint_type, endpoint, user, name, path, type, format, created, acknowledged, status, duration, result FROM analysis WHERE user = $1 AND endpoint = $2` + listCompletedReportsByPath = `SELECT id, endpoint_type, endpoint, user, name, path, type, format, created, acknowledged, status, duration, result FROM analysis WHERE status = 'completed' AND user = $1 AND endpoint = $2 AND path = $3 ORDER BY created DESC` + getReport = `SELECT id, endpoint_type, endpoint, user, name, path, type, format, created, acknowledged, status, duration, result FROM analysis WHERE user = $1 AND id=$2` + deleteReport = `DELETE FROM analysis WHERE user = $1 AND id = $2` + saveReport = `INSERT INTO analysis (id, user, endpoint_type, endpoint, name, path, type, format, created, acknowledged, status, duration, result) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)` + updateReport = `UPDATE analysis SET type = $1, format = $2, acknowledged = $3, status = $4, duration = $5, result = $6, name = $7, path = $8, result = $9 WHERE user = $10 AND id = $11` + getLatestReport = `SELECT id, endpoint_type, endpoint, user, name, path, type, format, created, acknowledged, status, duration, result FROM analysis WHERE status = 'completed' AND user = $1 AND endpoint = $2 AND path = $3 ORDER BY created DESC` + listRunningReports = `SELECT id, endpoint_type, endpoint, user, name, path, type, format, created, acknowledged, status, duration, result FROM analysis WHERE status = 'running' ORDER BY created DESC` + deleteForEndpoint = `DELETE FROM analysis WHERE endpoint = $1` +) + +// InitRepositoryProvider - One time init for the given DB Provider +func InitRepositoryProvider(databaseProvider string) { + // Modify the database statements if needed, for the given database type + listReports = datastore.ModifySQLStatement(listReports, databaseProvider) + listCompletedReportsByPath = datastore.ModifySQLStatement(listCompletedReportsByPath, databaseProvider) + getReport = datastore.ModifySQLStatement(getReport, databaseProvider) + deleteReport = datastore.ModifySQLStatement(deleteReport, databaseProvider) + saveReport = datastore.ModifySQLStatement(saveReport, databaseProvider) + updateReport = datastore.ModifySQLStatement(updateReport, databaseProvider) + getLatestReport = datastore.ModifySQLStatement(getLatestReport, databaseProvider) + listRunningReports = datastore.ModifySQLStatement(listRunningReports, databaseProvider) + deleteForEndpoint = datastore.ModifySQLStatement(deleteForEndpoint, databaseProvider) +} + +// AnalysisDBStore is a DB-backed Analysis Reports repository +type AnalysisDBStore struct { + db *sql.DB +} + +// NewAnalysisDBStore will create a new instance of the AnalysisDBStore +func NewAnalysisDBStore(dcp *sql.DB) (AnalysisStore, error) { + return &AnalysisDBStore{db: dcp}, nil +} + +// List - Returns a list of all user Analysis Reports for the given endpoint +func (p *AnalysisDBStore) List(userGUID, endpointID string) ([]*AnalysisRecord, error) { + log.Debug("List") + rows, err := p.db.Query(listReports, userGUID, endpointID) + if err != nil { + return nil, fmt.Errorf("Unable to retrieve Analysis Reports records: %v", err) + } + defer rows.Close() + + return list(rows) +} + +func (p *AnalysisDBStore) ListCompletedByPath(userGUID, endpointID, path string) ([]*AnalysisRecord, error) { + log.Debug("ListCompletedByPath") + rows, err := p.db.Query(listCompletedReportsByPath, userGUID, endpointID, path) + if err != nil { + return nil, fmt.Errorf("Unable to retrieve Analysis Reports records: %v", err) + } + defer rows.Close() + + return list(rows) +} + +func (p *AnalysisDBStore) ListRunning() ([]*AnalysisRecord, error) { + log.Debug("ListRunning") + rows, err := p.db.Query(listRunningReports) + if err != nil { + return nil, fmt.Errorf("Unable to retrieve Analysis Reports records: %v", err) + } + defer rows.Close() + + return list(rows) +} + +func list(rows *sql.Rows) ([]*AnalysisRecord, error) { + var reportList []*AnalysisRecord + reportList = make([]*AnalysisRecord, 0) + + for rows.Next() { + report := new(AnalysisRecord) + err := rows.Scan(&report.ID, &report.EndpointType, &report.EndpointID, &report.UserID, &report.Name, &report.Path, &report.Type, &report.Format, &report.Created, &report.Read, &report.Status, &report.Duration, &report.Result) + if err != nil { + return nil, fmt.Errorf("Unable to scan Analysis Reports records: %v", err) + } + reportList = append(reportList, report) + } + + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("Unable to List Analysis Reports records: %v", err) + } + + return reportList, nil +} + +// Get - Get a specific Analysis Report by ID +func (p *AnalysisDBStore) Get(userGUID, ID string) (*AnalysisRecord, error) { + log.Debug("Get") + + report := AnalysisRecord{} + err := p.db.QueryRow(getReport, userGUID, ID).Scan(&report.ID, &report.EndpointType, &report.EndpointID, &report.UserID, &report.Name, &report.Path, &report.Type, &report.Format, &report.Created, &report.Read, &report.Status, &report.Duration, &report.Result) + if err != nil { + msg := "Unable to Get Analysis Report record: %v" + log.Debugf(msg, err) + return nil, fmt.Errorf(msg, err) + } + + return &report, nil +} + +// GetLatestCompleted - Get latest report for the specified path +func (p *AnalysisDBStore) GetLatestCompleted(userGUID, endpointID, path string) (*AnalysisRecord, error) { + log.Debug("GetLatestCompleted") + + report := AnalysisRecord{} + err := p.db.QueryRow(getLatestReport, userGUID, endpointID, path).Scan(&report.ID, &report.EndpointType, &report.EndpointID, &report.UserID, &report.Name, &report.Path, &report.Type, &report.Format, &report.Created, &report.Read, &report.Status, &report.Duration, &report.Result) + if err != nil { + msg := "Unable to get laetst completed Analysis Report record: %v" + log.Debugf(msg, err) + return nil, fmt.Errorf(msg, err) + } + + return &report, nil +} + +// Delete will delete an Analysis Report from the datastore +func (p *AnalysisDBStore) Delete(userGUID string, id string) error { + if _, err := p.db.Exec(deleteReport, userGUID, id); err != nil { + return fmt.Errorf("Unable to delete Analysis Report record: %v", err) + } + + return nil +} + +// UpdateReport will update the dynamic fields of the Analysis Record in thedatastore +func (p *AnalysisDBStore) UpdateReport(userGUID string, report *AnalysisRecord) error { + if _, err := p.db.Exec(updateReport, report.Type, report.Format, report.Read, report.Status, report.Duration, report.Result, report.Name, report.Path, report.Result, userGUID, report.ID); err != nil { + return fmt.Errorf("Unable to update Analysis Report record: %v", err) + } + return nil +} + +// Save will persist an Analysis Report to the datastore +func (p *AnalysisDBStore) Save(report AnalysisRecord) (*AnalysisRecord, error) { + if _, err := p.db.Exec(saveReport, report.ID, report.UserID, report.EndpointType, report.EndpointID, report.Name, report.Path, report.Type, report.Format, report.Created, report.Read, &report.Status, &report.Duration, &report.Result); err != nil { + return nil, fmt.Errorf("Unable to save Analysis Report record: %v", err) + } + + return &report, nil +} + +// DeleteForEndpoint will remove all Analysis Reports for a given endpoint guid +func (p *AnalysisDBStore) DeleteForEndpoint(endpointID string) error { + if _, err := p.db.Exec(deleteForEndpoint, endpointID); err != nil { + return fmt.Errorf("Unable to delete reports for endpoint: %s %v", endpointID, err) + } + return nil +} diff --git a/src/jetstream/plugins/analysis/store/main.go b/src/jetstream/plugins/analysis/store/main.go new file mode 100644 index 0000000000..e9a14edac6 --- /dev/null +++ b/src/jetstream/plugins/analysis/store/main.go @@ -0,0 +1,39 @@ +package store + +import ( + "encoding/json" + "time" +) + +// AnalysisRecord represents an analysis that has been run +type AnalysisRecord struct { + ID string `json:"id"` + UserID string `json:"-"` + EndpointType string `json:"endpointType"` + EndpointID string `json:"endpoint"` + Type string `json:"type"` + Format string `json:"format"` + Name string `json:"name"` + Path string `json:"path"` + Created time.Time `json:"created"` + Read bool `json:"read"` + Status string `json:"status"` + Duration int `json:"duration"` + Result string `json:"-"` + Error string `json:"error"` + Summary *json.RawMessage `json:"summary"` + Report *json.RawMessage `json:"report,omitempty"` +} + +// AnalysisStore is the analysis repository +type AnalysisStore interface { + List(userGUID, endpointID string) ([]*AnalysisRecord, error) + Get(userGUID, id string) (*AnalysisRecord, error) + GetLatestCompleted(userGUID, endpointID, path string) (*AnalysisRecord, error) + ListCompletedByPath(userGUID, endpointID, path string) ([]*AnalysisRecord, error) + ListRunning() ([]*AnalysisRecord, error) + Delete(userGUID, id string) error + DeleteForEndpoint(endpointID string) error + Save(record AnalysisRecord) (*AnalysisRecord, error) + UpdateReport(userGUID string, report *AnalysisRecord) error +} diff --git a/src/jetstream/plugins/kubernetes/endpoint_config.go b/src/jetstream/plugins/kubernetes/endpoint_config.go index 8a7acd85af..8e7b983235 100644 --- a/src/jetstream/plugins/kubernetes/endpoint_config.go +++ b/src/jetstream/plugins/kubernetes/endpoint_config.go @@ -24,10 +24,8 @@ func (c *KubernetesSpecification) GetConfigForEndpoint(masterURL string, token i func (c *KubernetesSpecification) GetConfigForEndpointUser(endpointID, userID string) (*restclient.Config, error) { var p = c.portalProxy - cnsiRecord, err := p.GetCNSIRecord(endpointID) if err != nil { - //return sendSSHError("Could not get endpoint information") return nil, errors.New("Could not get endpoint information") } @@ -40,6 +38,23 @@ func (c *KubernetesSpecification) GetConfigForEndpointUser(endpointID, userID st return c.GetConfigForEndpoint(cnsiRecord.APIEndpoint.String(), tokenRec) } +func (c *KubernetesSpecification) GetKubeConfigForEndpointUser(endpointID, userID string) (string, error) { + + var p = c.portalProxy + cnsiRecord, err := p.GetCNSIRecord(endpointID) + if err != nil { + return "", errors.New("Could not get endpoint information") + } + + // Get token for this users + tokenRec, ok := p.GetCNSITokenRecord(endpointID, userID) + if !ok { + return "", errors.New("Could not get token") + } + + return c.GetKubeConfigForEndpoint(cnsiRecord.APIEndpoint.String(), tokenRec, "") +} + func (c *KubernetesSpecification) getKubeConfigForEndpoint(masterURL string, token interfaces.TokenRecord, namespace string) (*clientcmdapi.Config, error) { name := "cluster-0" diff --git a/src/jetstream/plugins/kubernetes/get_release.go b/src/jetstream/plugins/kubernetes/get_release.go index d072870557..e4627830ab 100644 --- a/src/jetstream/plugins/kubernetes/get_release.go +++ b/src/jetstream/plugins/kubernetes/get_release.go @@ -102,7 +102,7 @@ func (c *KubernetesSpecification) GetReleaseStatus(ec echo.Context) error { // this back incrementally // Parse the manifest - rel := helm.NewHelmRelease(res, endpointGUID, userID) + rel := helm.NewHelmRelease(res, endpointGUID, userID, c.portalProxy) graph := helm.NewHelmReleaseGraph(rel) diff --git a/src/jetstream/plugins/kubernetes/go.mod b/src/jetstream/plugins/kubernetes/go.mod index 288ec1d075..6e44da2a86 100644 --- a/src/jetstream/plugins/kubernetes/go.mod +++ b/src/jetstream/plugins/kubernetes/go.mod @@ -9,7 +9,6 @@ require ( github.com/aws/aws-sdk-go v1.17.5 github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 // indirect github.com/docker/docker v1.13.1 // indirect - github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 // indirect github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484 // indirect github.com/ghodss/yaml v1.0.0 github.com/gorilla/websocket v1.4.0 diff --git a/src/jetstream/plugins/kubernetes/go.sum b/src/jetstream/plugins/kubernetes/go.sum index d3b2e14ea0..3159ac83df 100644 --- a/src/jetstream/plugins/kubernetes/go.sum +++ b/src/jetstream/plugins/kubernetes/go.sum @@ -112,6 +112,7 @@ github.com/deislabs/oras v0.7.0/go.mod h1:sqMKPG3tMyIX9xwXUBRLhZ24o+uT4y6jgBD2Rz github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/disintegration/imaging v1.6.0 h1:nVPXRUUQ36Z7MNf0O77UzgnOb1mkMMor7lmJMJXc/mA= github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/docker/cli v0.0.0-20190506213505-d88565df0c2d h1:qdD+BtyCE1XXpDyhvn0yZVcZOLILdj9Cw4pKu0kQbPQ= github.com/docker/cli v0.0.0-20190506213505-d88565df0c2d/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= @@ -254,6 +255,7 @@ github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= @@ -292,6 +294,8 @@ github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZs github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU= @@ -369,6 +373,7 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kubeapps/common v0.0.0-20181107174310-61d8eb6f11b4 h1:wdTBUArlqtBYGN2Dd4+zsaFxFH0m4iGCHToW10jPX0k= github.com/kubeapps/common v0.0.0-20181107174310-61d8eb6f11b4/go.mod h1:TsgmjeDpbftqhwPKInJ3v+l+xbHs4goiB6DFb2WqY9c= +github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a/go.mod h1:TsgmjeDpbftqhwPKInJ3v+l+xbHs4goiB6DFb2WqY9c= github.com/kubernetes-sigs/aws-iam-authenticator v0.3.0 h1:HOC7YpUao5F3RTIncfBfoh+7/ID1Jl97ALNgEmWIjxo= github.com/kubernetes-sigs/aws-iam-authenticator v0.3.0/go.mod h1:ItxiN33Ho7Di8wiC4S4XqbH1NLF0DNdDWOd/5MI9gJU= github.com/kubernetes-sigs/aws-iam-authenticator v0.3.1-0.20190111160901-390d9087a4bc h1:Ttr4Z3ZrMv4rAXn10UAqOC8ACx+F1omvcyV1a3hRArE= @@ -377,6 +382,7 @@ github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8 github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0= github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= @@ -392,9 +398,12 @@ github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.6 h1:SrwhHcpV4nWrMGdNcC2kXpMfcBVYGDuTArqyhocJgvA= github.com/mattn/go-isatty v0.0.6/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-shellwords v1.0.5/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= @@ -495,8 +504,8 @@ github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNue github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v2.0.0 h1:L7Oc72h7rDqGkbUorN/ncJ4N/y220/YRezHvBoKLOFA= github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday v2.0.0/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday v2.0.0/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= @@ -537,12 +546,16 @@ github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245/go.mod h1:O1c github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/unrolled/render v1.0.0 h1:XYtvhA3UkpB7PqkvhUFYmpKD55OudoIeygcfus4vcd4= github.com/unrolled/render v1.0.0/go.mod h1:tu82oB5W2ykJRVioYsB+IQKcft7ryBr7w12qMBUPyXg= +github.com/unrolled/render v1.0.1/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0obPf6NBdM= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 h1:gKMu1Bf6QINDnvyZuTaACm9ofY+PRh+5vFz4oxBZeF8= github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4/go.mod h1:50wTf68f99/Zt14pr046Tgt3Lp2vLyFZKzbFXTOabXw= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= @@ -559,6 +572,7 @@ github.com/yvasiyarov/gorelic v0.0.6 h1:qMJQYPNdtJ7UNYHjX38KXZtltKTqimMuoQjNnSVI github.com/yvasiyarov/gorelic v0.0.6/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +go.mongodb.org/mongo-driver v1.1.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.17.0/go.mod h1:mp1VrMQxhlqqDpKvH4UcQUa4YwlzNmymAjPrDdfxNpI= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -574,12 +588,15 @@ golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRi golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152 h1:ZC1Xn5A1nlpSmQCIva4bZ3ob3lmhYIefc+GU+DLg1Ow= golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191205161847-0a08dada0ff9 h1:abxekknhS/Drh3uoQDk5Hc7BgeiyI39Crb7vhf/1j5s= +golang.org/x/crypto v0.0.0-20191205161847-0a08dada0ff9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -624,6 +641,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= @@ -641,6 +660,7 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191028164358-195ce5e7f934 h1:u/E0NqCIWRDAo9WCFo6Ko49njPFDLSd3z+X1HgWDMpE= @@ -787,6 +807,7 @@ k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8 k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/helm v2.12.3+incompatible h1:wo1cdYjOnr5Z+LFuhtwIJaeQnec6D4gcg2H5UAKzY6w= k8s.io/helm v2.12.3+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= +k8s.io/helm v2.16.1+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.2.0 h1:0ElL0OHzF3N+OhoJTL0uca20SxtYt4X4+bzHeqrB83c= k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= diff --git a/src/jetstream/plugins/kubernetes/helm/graph.go b/src/jetstream/plugins/kubernetes/helm/graph.go index cea0f9c5dd..a46bf3ac0a 100644 --- a/src/jetstream/plugins/kubernetes/helm/graph.go +++ b/src/jetstream/plugins/kubernetes/helm/graph.go @@ -27,8 +27,12 @@ type ReleaseNode struct { ID string `json:"id"` Label string `json:"label"` Data struct { - Kind string `json:"kind"` - Status NodeStatus `json:"status"` + Kind string `json:"kind"` + Status NodeStatus `json:"status"` + Metadata struct { + Name string `yaml:"name" json:"name"` + Namespace string `yaml:"namespace" json:"namespace"` + } `yaml:"metadata" json:"metadata"` } `json:"data"` } @@ -67,6 +71,8 @@ func (r *HelmReleaseGraph) ParseManifest(release *HelmRelease) { } node.Data.Kind = item.Kind + node.Data.Metadata = item.Metadata + // Note - item.Metadata.Namespace is nil node.Data.Status = "unknown" switch o := item.Resource.(type) { @@ -114,6 +120,9 @@ func (r *HelmReleaseGraph) ParseManifest(release *HelmRelease) { case *rbacv1.RoleBinding: target := getShortResourceId(item.Kind, o.Name) r.ParseRoleBinding(target, o) + case *rbacv1.ClusterRoleBinding: + target := getShortResourceId(item.Kind, o.Name) + r.ParseClusterRoleBinding(target, o) default: log.Debugf("Graph: Unknown type: %s", reflect.TypeOf(o)) } @@ -138,20 +147,15 @@ func (r *HelmReleaseGraph) generateTemporaryNode(id string) { node := ReleaseNode{ ID: id, - Label: parts[1], + Label: strings.Join(parts[1:], "-"), } node.Data.Kind = parts[0] node.Data.Status = "missing" - r.Nodes[node.ID] = node } func getShortResourceId(kind, name string) string { - // // TODO: FIX - empty kind is a pod - // if len(kind) == 0 { - // kind = "Pod" - // } return fmt.Sprintf("%s-%s", kind, name) } @@ -180,6 +184,13 @@ func (r *HelmReleaseGraph) ProcessPod(id string, res KubeResource, spec v1.PodSp } } + // Service Account + saName := spec.ServiceAccountName + if len(saName) > 0 { + ref := fmt.Sprintf("ServiceAccount-%s", saName) + r.AddLink(id, ref) + } + // Go through the pod and process each container // Add a node for each container for _, container := range spec.Containers { @@ -256,3 +267,14 @@ func (r *HelmReleaseGraph) ParseRoleBinding(id string, roleBinding *rbacv1.RoleB roleRefID := fmt.Sprintf("%s-%s", roleBinding.RoleRef.Kind, roleBinding.RoleRef.Name) r.AddLink(id, roleRefID) } + +func (r *HelmReleaseGraph) ParseClusterRoleBinding(id string, roleBinding *rbacv1.ClusterRoleBinding) { + for _, subject := range roleBinding.Subjects { + // TODO: Only match those with the same namespace ???? + subjectID := fmt.Sprintf("%s-%s", subject.Kind, subject.Name) + r.AddLink(id, subjectID) + } + + roleRefID := fmt.Sprintf("%s-%s", roleBinding.RoleRef.Kind, roleBinding.RoleRef.Name) + r.AddLink(id, roleRefID) +} diff --git a/src/jetstream/plugins/kubernetes/helm/release.go b/src/jetstream/plugins/kubernetes/helm/release.go index f8fd9c3131..868200bb89 100644 --- a/src/jetstream/plugins/kubernetes/helm/release.go +++ b/src/jetstream/plugins/kubernetes/helm/release.go @@ -9,6 +9,7 @@ import ( "reflect" "strings" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" log "github.com/sirupsen/logrus" "helm.sh/helm/v3/pkg/release" appsv1 "k8s.io/api/apps/v1" @@ -18,15 +19,16 @@ import ( v1 "k8s.io/api/core/v1" extv1beta1 "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/scheme" - - "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" + "sigs.k8s.io/yaml" ) var resourcesWithoutStatus = map[string]bool{ - "RoleBinding": false, - "Role": false, + "RoleBinding": false, + "Role": false, + "ClusterRole": false, + "ClusterRoleBinding": false, + "PodSecurityPolicy": false, } // HelmRelease represents a Helm Release deployed via Helm @@ -45,7 +47,8 @@ type KubeResource struct { Kind string `yaml:"kind" json:"kind"` APIVersion string `yaml:"apiVersion" json:"apiVersion"` Metadata struct { - Name string `yaml:"name" json:"name"` + Name string `yaml:"name" json:"name"` + Namespace string `yaml:"namespace" json:"namespace"` } `yaml:"metadata" json:"metadata"` Resource interface{} `yaml:"resource"` Manifest bool @@ -56,7 +59,7 @@ func (r *KubeResource) getID() string { } // NewHelmRelease represents extended info about a Helm Release -func NewHelmRelease(info *release.Release, endpoint, user string) *HelmRelease { +func NewHelmRelease(info *release.Release, endpoint, user string, jetstream interfaces.PortalProxy) *HelmRelease { r := &HelmRelease{ Release: info, Endpoint: endpoint, @@ -76,16 +79,26 @@ func (r *HelmRelease) parseManifest() { var bufr strings.Builder for { line, err := buffer.ReadString('\n') - if err != nil || (err == nil && strings.TrimSpace(line) == "---") { + if err != nil || (err == nil && strings.TrimRight(line, "\t \n") == "---") { data := []byte(bufr.String()) if len(data) > 0 { decode := scheme.Codecs.UniversalDeserializer().Decode - obj, _, err := decode([]byte(bufr.String()), nil, nil) + obj, _, err := decode(data, nil, nil) if err != nil { - log.Error(fmt.Sprintf("Helm Manifest Parser: Error while decoding YAML object. Err was: %s", err)) - r.ManifestErrors = true + // Custom Resource Definition + if strings.HasPrefix(err.Error(), "no kind") { + var t interface{} + if err := yaml.Unmarshal(data, &t); err == nil { + r.processYamlResource(t, data) + } else { + log.Errorf("Could not parse custom resource %s", err) + } + } else { + log.Error(fmt.Sprintf("Helm Manifest Parser: Error while decoding YAML object. Err was: %s", err)) + r.ManifestErrors = true + } } else { - r.processResource(obj) + r.processJsonResource(obj) } bufr.Reset() @@ -96,7 +109,11 @@ func (r *HelmRelease) parseManifest() { if err != nil { break } - bufr.WriteString(line) + + // Ignore comments + if !strings.HasPrefix(strings.TrimSpace(line), "#") && !strings.HasPrefix(strings.TrimRight(line, "\t \n"), "---") { + bufr.WriteString(line) + } } } @@ -131,32 +148,61 @@ func (r *HelmRelease) GetPods() []interface{} { return resources } -// process a yaml resource from the helm manifest -func (r *HelmRelease) processResource(obj runtime.Object) { - j, err := json.Marshal(obj) +func (r *HelmRelease) processJsonResource(obj interface{}) { + data, err := json.Marshal(obj) if err == nil { var t KubeResource - if json.Unmarshal(j, &t) == nil { - t.Resource = obj - t.Manifest = true - r.setResource(t) - log.Debugf("Got resource: %s : %s", t.Kind, t.Metadata.Name) - r.processController(t) - r.addJobForResource(t.Kind, t.APIVersion, t.Metadata.Name) + if err := json.Unmarshal(data, &t); err == nil { + // If this is a List, then unpack it + if t.APIVersion == "v1" && t.Kind == "List" { + var list v1.PodList + err := json.Unmarshal(data, &list) + if err == nil { + for _, item := range list.Items { + r.processJsonResource(item) + } + } else { + log.Error("Helm Release Manifest: Could not parse List resource") + } + } else { + r.processKubeResource(obj, t) + } } else { log.Error("Helm Release Manifest: Could not parse Kubernetes resource") } + } else { + log.Errorf("Helm Release ManifestL Could not marshal Kubernetes resource %s", err) + } +} + +func (r *HelmRelease) processYamlResource(obj interface{}, data []byte) { + var t KubeResource + if err := yaml.Unmarshal(data, &t); err == nil { + r.processKubeResource(obj, t) + } else { + log.Error("Helm Release Manifest: Could not parse Kubernetes resource") } } -func (r *HelmRelease) addJobForResource(kind, apiVersion, name string) { +// process a yaml resource from the helm manifest +//func (r *HelmRelease) processResource(obj runtime.Object) { +func (r *HelmRelease) processKubeResource(obj interface{}, t KubeResource) { + t.Resource = obj + t.Manifest = true + r.setResource(t) + log.Debugf("Got resource: %s : %s", t.Kind, t.Metadata.Name) + r.processController(t) + r.addJobForResource(t.Metadata.Namespace, t.Kind, t.APIVersion, t.Metadata.Name) +} + +func (r *HelmRelease) addJobForResource(namespace, kind, apiVersion, name string) { job := KubeResourceJob{ ID: fmt.Sprintf("%s-%s#Pods", kind, name), Endpoint: r.Endpoint, User: r.User, Namespace: r.Namespace, Name: name, - URL: getRestURL(r.Namespace, kind, apiVersion, name), + URL: getRestURL(namespace, kind, apiVersion, name), APIVersion: apiVersion, Kind: kind, } @@ -235,6 +281,8 @@ func (r *HelmRelease) UpdatePods(jetstream interfaces.PortalProxy) { podCopy := &v1.Pod{} *podCopy = pod + podCopy.Kind = "Pod" + podCopy.APIVersion = "v1" res.Resource = podCopy pods[res.getID()] = &res @@ -268,7 +316,7 @@ func (r *HelmRelease) processPodOwners(pod v1.Pod) { } resource.ObjectMeta = metav1.ObjectMeta{ Name: owner.Name, - Namespace: r.Namespace, + Namespace: pod.Namespace, } identifier := getResourceIdentifier(resource.TypeMeta, resource.ObjectMeta) if _, ok := r.Resources[identifier]; !ok { @@ -282,7 +330,7 @@ func (r *HelmRelease) processPodOwners(pod v1.Pod) { res.Resource = &resource r.setResource(res) - r.addJobForResource(owner.Kind, owner.APIVersion, owner.Name) + r.addJobForResource(pod.Namespace, owner.Kind, owner.APIVersion, owner.Name) } } else { log.Debugf("Unexpected Pod owner kind: %s", owner.Kind) @@ -290,15 +338,6 @@ func (r *HelmRelease) processPodOwners(pod v1.Pod) { } } -// func (r *HelmRelease) getKubeResource(typeMeta metav1.TypeMeta, objectMeta metav1.ObjectMeta) KubeResource { -// kres := KubeResource{ -// Kind: typeMeta.Kind, -// APIVersion: typeMeta.APIVersion, -// } -// kres.Metadata.Name = objectMeta.Name -// return kres -// } - func (r *HelmRelease) UpdateResources(jetstream interfaces.PortalProxy) { // This will be an array of resources runner := NewKubeAPIJob(jetstream, r.Jobs) @@ -312,7 +351,7 @@ func (r *HelmRelease) UpdateResources(jetstream interfaces.PortalProxy) { } res.Metadata.Name = j.Name - // TODO: If the status was 404, then we should remove the resource + // If the status was 404, then we should remove the resource if j.StatusCode == http.StatusNotFound { log.Debugf("Resource has been deleted - removing: %s -> %s", j.Kind, j.Name) r.deleteResource(res) @@ -332,7 +371,14 @@ func (r *HelmRelease) UpdateResources(jetstream interfaces.PortalProxy) { res.Resource = obj r.setResource(res) } else { - log.Error("Could not parse resource") + // Just decode from Yaml - could be a CRD + var obj interface{} + if err := yaml.Unmarshal(j.Data, &obj); err == nil { + res.Resource = obj + r.setResource(res) + } else { + log.Error("Could not parse resource") + } } // TODO: If the resource was a job, process the selector again @@ -351,6 +397,22 @@ func getRestURL(namespace, kind, apiVersion, name string) string { name += "/status" } } - restURL = fmt.Sprintf("/%s/%s/namespaces/%s/%ss/%s", base, apiVersion, namespace, strings.ToLower(kind), name) + + kindPlural := pluralize(strings.ToLower(kind)) + if len(namespace) == 0 { + // This is not a namespaced resource + restURL = fmt.Sprintf("/%s/%s/%s/%s", base, apiVersion, kindPlural, name) + } else { + restURL = fmt.Sprintf("/%s/%s/namespaces/%s/%s/%s", base, apiVersion, namespace, kindPlural, name) + } + return restURL } + +func pluralize(resource string) string { + if strings.HasSuffix(resource, "y") { + return fmt.Sprintf("%sies", resource[:len(resource)-1]) + } + + return fmt.Sprintf("%ss", resource) +} From a5b29fa3ef977c75cf5f22cf14ca3d193724e76d Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 24 Jul 2020 16:50:11 +0100 Subject: [PATCH 585/648] Merge upstream #3 (#421) * Improve recent and favourites display (#4421) * Improve recent and favourites display * Remove debug logging * Remove debug logging/subscription leak * Unit test fix * Tweaks following review * Changes following review - favourite & recent icon changes Co-authored-by: Richard Cox * Add line-height to favourite and recent entity labels (#4438) - missed out from another PR following review * Autoscaler e2e tests: Give better failure error when the test can't find the scaling row (#4424) * Improve autoscaler e2e tests * Remove duplicate fail statement * Helm Chart: Remove encryption key volume (#4355) * Remove encryption key volume * Remove encrpytion key volume migration from config-init job * Improve the logout experience (#4439) * Add support for including breaking changes in the changelog * Improve the logout experience * Fix unit tests * Add request for version info to github issues template (#4443) * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Convert scripts to nodejs so they work on Windows (#4462) * Convert scripts to js so they work on Windows * Improve initial developer experience * Fix scss compile on windows * Fix windows build * Use fs not fs-extra where possible (can work without npm install) * Remove gulp * Cleaner fixes for Windows * Reset * Fix whitespace * Address PR feedback Co-authored-by: Neil MacDougall * Shorten file paths containing `cloudfoundry`/`cloud-foundry` (#4466) * Fix 'too long' file paths * Fix build issues * Convert scripts to js so they work on Windows * Fix scss compile on windows * Improve initial developer experience * Fix scss compile on windows * Fix windows build * Use fs not fs-extra where possible (can work without npm install) * Remove gulp * Cleaner fixes for Windows * Reset * Fix whitespace * Fix merge Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall * Fix log out page when there's a custom log in page (#4467) * Add service broker space scope information to service wall list (#4463) * Add service broker space scope information to service wall list * Split out `Space Scope` into it's own `Scope` row/column * Add service broker scope to service instance list (card and table) & app binding list (card) * Fix e2e test * Fix services table, cater for brokers that return 403 * Changes following review - Show space scope link in app service list (quick access) - Harmonize names (Service, broker, scope, etc) - Fix space scope link in services wall * Remove recents and favorites when entities are deleted (#4431) * Improve recent and favourites display * Remove debug logging * Remove debug logging/subscription leak * Unit test fix * Remove recents and favorites when entities are deleted * Fix merge issues * Fix merge issue * Tidy up and improve following PR feedback * Remove out of date comment * Fixed problem where clear was not working * Check favorite exists before deleting * Version bump and change log (#4469) * Version bump and change log * Update changelog Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall --- CHANGELOG.md | 66 +++++++++++++++++ package-lock.json | 2 +- package.json | 2 +- .../src/shared/cf-shared.module.ts | 4 + .../app-service-binding-card.component.html | 22 +++++- .../app-service-binding-card.component.ts | 58 ++++++--------- .../cf-service-card.component.html | 12 ++- .../cf-service-card.component.ts | 21 +++--- .../cf-service-instances-list-config.base.ts | 4 +- .../cf-services-list-config.service.ts | 19 ++++- .../table-cell-service-broker.component.html | 16 ++++ .../table-cell-service-broker.component.scss | 0 ...able-cell-service-broker.component.spec.ts | 25 +++++++ .../table-cell-service-broker.component.ts | 73 +++++++++++++++++++ .../table-cell-service.component.html | 9 ++- .../table-cell-service.component.scss | 7 ++ .../table-cell-service.component.ts | 33 +++++---- .../service-instance-card.component.html | 12 ++- .../service-instance-card.component.ts | 27 +++---- .../src/actions/entity.delete.actions.ts | 31 ++++++++ .../packages/store/src/effects/api.effects.ts | 27 +++++-- .../src/effects/user-favorites-effect.ts | 25 ++++++- .../recently-visited.reducer.helpers.ts | 9 +++ .../recently-visited.reducer.ts | 10 ++- .../application-autoscaler-e2e.spec.ts | 3 - ...ate-service-instances-bind-app-e2e.spec.ts | 2 +- 26 files changed, 416 insertions(+), 103 deletions(-) create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.html create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.scss create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.spec.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.ts create mode 100644 src/frontend/packages/store/src/actions/entity.delete.actions.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 161b42c8ad..3822b7ef27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,71 @@ # Change Log +## 4.0.0 + +[Full Changelog](https://github.com/cloudfoundry/stratos/compare/3.2.1...4.0.0) + +This release contains a number of fixes and improvements: + +**Improvements:** + +- Extensions: Remove the need for symlinks and improve the build process [\#4472](https://github.com/cloudfoundry/stratos/issues/4472) +- Extensions: Allow Themes to be published and installed to/from npm [\#4471](https://github.com/cloudfoundry/stratos/issues/4471) +- Extensions: Move to extensions and themes to be packages [\#4470](https://github.com/cloudfoundry/stratos/issues/4470) +- Show service broker space scope information in service wall list [\#4458](https://github.com/cloudfoundry/stratos/issues/4458) +- Client Secret is shown in the clear in the UI [\#4445](https://github.com/cloudfoundry/stratos/issues/4445) +- Improve sizing of UI elements on desktop browsers [\#4419](https://github.com/cloudfoundry/stratos/issues/4419) +- Theming: Allow more control over link and side navigation colors [\#4406](https://github.com/cloudfoundry/stratos/issues/4406) +- Update to latest set of icons [\#4403](https://github.com/cloudfoundry/stratos/issues/4403) +- Theming: Allow more control over page header colors and style [\#4396](https://github.com/cloudfoundry/stratos/issues/4396) +- Helm Chart: Remove encryption volume [\#4351](https://github.com/cloudfoundry/stratos/issues/4351) +- Improve app summary responsiveness [\#4348](https://github.com/cloudfoundry/stratos/issues/4348) +- Improve UI for the case when we can't determine cf app deployment info [\#4347](https://github.com/cloudfoundry/stratos/issues/4347) +- Change recent activity icon to avoid confusion with the refresh button [\#4346](https://github.com/cloudfoundry/stratos/issues/4346) +- Helm Chart: Change default image pull policy to Always [\#4342](https://github.com/cloudfoundry/stratos/issues/4342) +- Permissions: Org Managers: Disable org role checkboxes in roles stepper if not admin/org manager [\#4332](https://github.com/cloudfoundry/stratos/issues/4332) +- Autoscaler: Add support for custom metrics [\#4298](https://github.com/cloudfoundry/stratos/issues/4298) +- Add support for copying endpoint address in list view [\#4238](https://github.com/cloudfoundry/stratos/issues/4238) +- Update to Angular 9 framework [\#4214](https://github.com/cloudfoundry/stratos/issues/4214) +- Update docker logo in deploy app stepper [\#4133](https://github.com/cloudfoundry/stratos/issues/4133) +- Helm Chart: Remove need for --recreate-pods when upgrading [\#4132](https://github.com/cloudfoundry/stratos/issues/4132) +- Make permissions model extension friendly [\#3789](https://github.com/cloudfoundry/stratos/issues/3789) +- User Favourites: Add icons to cards [\#3409](https://github.com/cloudfoundry/stratos/issues/3409) +- Improve log out experience [\#2587](https://github.com/cloudfoundry/stratos/issues/2587) + +**Fixes:** + +- Ensure `cf push` works from Windows [\#4465](https://github.com/cloudfoundry/stratos/issues/4465) +- Visiting marketplace tab breaks service list [\#4397](https://github.com/cloudfoundry/stratos/issues/4397) +- CF Application reports error after restage [\#4392](https://github.com/cloudfoundry/stratos/issues/4392) +- Helm Chart: Icon is missing [\#4370](https://github.com/cloudfoundry/stratos/issues/4370) +- Permissions: Users with no developer roles can click on create app button [\#4361](https://github.com/cloudfoundry/stratos/issues/4361) +- Edit endpoint not available in table view [\#4349](https://github.com/cloudfoundry/stratos/issues/4349) +- CF: Routes List: Filter by org breaks when user is an org auditor [\#4343](https://github.com/cloudfoundry/stratos/issues/4343) +- Permissions: Only space developers should be able to see add service instance buttons [\#4331](https://github.com/cloudfoundry/stratos/issues/4331) +- Permissions: Only Space Developers should be able to change count, terminate or ssh to instances [\#4330](https://github.com/cloudfoundry/stratos/issues/4330) +- App: Gitlab Tab: Fix console errors [\#4325](https://github.com/cloudfoundry/stratos/issues/4325) +- Permissions: Only space developers should be able to create/unbind/delete routes in app routes list [\#4324](https://github.com/cloudfoundry/stratos/issues/4324) +- Permissions: Only Space Developers should be able to create/edit/delete an Autoscaler policy [\#4323](https://github.com/cloudfoundry/stratos/issues/4323) +- Permissions: Only space developers should be able to see the app summary deployment card [\#4322](https://github.com/cloudfoundry/stratos/issues/4322) +- Duplicated documentation between deploy/kubernetes and Helm Chart README.md [\#4315](https://github.com/cloudfoundry/stratos/issues/4315) +- App Service Edit Binding: cancel of stepper results in leaked subscription [\#4295](https://github.com/cloudfoundry/stratos/issues/4295) +- Exceptions thrown when navigating back from marketplace [\#4287](https://github.com/cloudfoundry/stratos/issues/4287) +- Unbind services stepper fails to show bound services [\#4246](https://github.com/cloudfoundry/stratos/issues/4246) +- Progress icon appears too close to right-hand size of table [\#4234](https://github.com/cloudfoundry/stratos/issues/4234) +- Cancel/create in service instance stepper returns to incorrect locations [\#4052](https://github.com/cloudfoundry/stratos/issues/4052) +- Exception thrown in setup steppers [\#3897](https://github.com/cloudfoundry/stratos/issues/3897) +- Logout leaves the UI as is if the verify or logout call fails [\#2633](https://github.com/cloudfoundry/stratos/issues/2633) +- Cf Build Packs: file name should wrap to next line if too long [\#1803](https://github.com/cloudfoundry/stratos/issues/1803) + +**Breaking Changes:** + +- **Kubernetes: Upgrade only possible from version 3.0.0 or later** + + When deploying into Kubernetes using Helm and upgrading from an earlier version of Stratos using `helm upgrade`, upgrade is **only** supported from version 3.0.0 or later. If you are using an earlier version, first upgrade to version 3.x before then upgrading to the latest version. +- **Angular 9 requires extensions to be declared** + + Extension components must now be made known to the extensions system in the module that they are declared in, using `ExtensionService.declare`. Please check the documentation. This is required to ensure that the new Angular compiler for Ivy does not remove these components for being unreferenced in the application. + ## 3.2.1 [Full Changelog](https://github.com/SUSE/stratos/compare/3.2.0...3.2.1) diff --git a/package-lock.json b/package-lock.json index 8bbb6d91b2..1c002ad57b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "stratos", - "version": "3.2.1", + "version": "4.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 32d6ab56b1..e2996467de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stratos", - "version": "3.2.1", + "version": "4.0.0", "description": "Stratos Console", "main": "index.js", "scripts": { diff --git a/src/frontend/packages/cloud-foundry/src/shared/cf-shared.module.ts b/src/frontend/packages/cloud-foundry/src/shared/cf-shared.module.ts index faca9700ab..206abb267a 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/cf-shared.module.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/cf-shared.module.ts @@ -136,6 +136,9 @@ import { import { TableCellServiceBindableComponent, } from './components/list/list-types/cf-services/table-cell-service-bindable/table-cell-service-bindable.component'; +import { + TableCellServiceBrokerComponent, +} from './components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component'; import { TableCellServiceCfBreadcrumbsComponent, } from './components/list/list-types/cf-services/table-cell-service-cf-breadcrumbs/table-cell-service-cf-breadcrumbs.component'; @@ -248,6 +251,7 @@ const cfListTableCells: Type>[] = [ TableCellServiceReferencesComponent, TableCellServiceInstanceTagsComponent, TableCellCommitAuthorComponent, + TableCellServiceBrokerComponent ]; const cfListCards: Type>[] = [ diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.html index 6bfbddb4da..854f94676f 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.html @@ -1,7 +1,7 @@
- {{ (serviceInstance$ | async)?.entity.entity.name}} + {{ (serviceInstance$ | async)?.entity.name}}
@@ -18,7 +18,25 @@ {{ data.label }} {{ data.data$ | async }} - + + Service Broker + + + + + + + Service Scope + + + + + + + Date Created On + {{ row.metadata.created_at | date:'medium'}} + + Environment Variables diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.ts index 23b66083dc..b3e39e94f2 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.ts @@ -1,7 +1,6 @@ -import { DatePipe } from '@angular/common'; import { Component, OnInit } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; -import { combineLatest as observableCombineLatest, Observable, of as observableOf, of } from 'rxjs'; +import { combineLatest as observableCombineLatest, Observable, of } from 'rxjs'; import { filter, first, map, switchMap } from 'rxjs/operators'; import { @@ -24,7 +23,6 @@ import { serviceBindingEntityType } from '../../../../../../cf-entity-types'; import { ApplicationService } from '../../../../../../features/applications/application.service'; import { isUserProvidedServiceInstance } from '../../../../../../features/cf/cf.helpers'; import { - getServiceBrokerName, getServiceName, getServicePlanName, getServiceSummaryUrl, @@ -34,6 +32,10 @@ import { CfCurrentUserPermissions } from '../../../../../../user-permissions/cf- import { ServiceActionHelperService } from '../../../../../data-services/service-action-helper.service'; import { CSI_CANCEL_URL } from '../../../../add-service-instance/csi-mode.service'; import { EnvVarViewComponent } from '../../../../env-var-view/env-var-view.component'; +import { + TableCellServiceBrokerComponentConfig, + TableCellServiceBrokerComponentMode, +} from '../../cf-services/table-cell-service-broker/table-cell-service-broker.component'; interface EnvVarData { @@ -54,19 +56,25 @@ export class AppServiceBindingCardComponent extends CardCell> | null>; + service$: Observable | null>; serviceInstance$: Observable>>; tags$: Observable[]>; entityConfig: ComponentEntityMonitorConfig; private envVarServicesSection$: Observable; - private isUserProvidedServiceInstance: boolean; + isUserProvidedServiceInstance: boolean; serviceDescription$: Observable; serviceUrl$: Observable; serviceName$: Observable; + brokerNameConfig: TableCellServiceBrokerComponentConfig = { + mode: TableCellServiceBrokerComponentMode.NAME + } + brokerScopeConfig: TableCellServiceBrokerComponentConfig = { + mode: TableCellServiceBrokerComponentMode.SCOPE, + } + constructor( private dialog: MatDialog, - private datePipe: DatePipe, private appService: ApplicationService, private serviceActionHelperService: ServiceActionHelperService, private currentUserPermissionsService: CurrentUserPermissionsService, @@ -105,11 +113,6 @@ export class AppServiceBindingCardComponent extends CardCell !!o.entity.entity.tags), map(o => o.entity.entity.tags.map(t => ({ value: t }))) @@ -126,7 +129,8 @@ export class AppServiceBindingCardComponent extends CardCell cfEntityCatalog.service.store.getEntityService(o.entity.entity.service_guid, this.appService.cfGuid, {}) .waitForEntity$), - filter(service => !!service) + filter(service => !!service), + map(e => e.entity) ); this.listData = [{ label: 'Service Plan', @@ -139,39 +143,19 @@ export class AppServiceBindingCardComponent extends CardCell { - if (this.isUserProvidedServiceInstance) { - return null; - } - const serviceInstance: IServiceInstance = si.entity.entity as IServiceInstance; - return this.service$.pipe( - switchMap(service => { - return getServiceBrokerName( - service.entity.entity.service_broker_guid, - serviceInstance.cfGuid, - ); - }) - ); - }) - ) - }, - ]; - this.envVarServicesSection$ = this.service$.pipe(map(s => s.entity.entity.label)); + }]; + this.envVarServicesSection$ = this.service$.pipe(map(s => s.entity.label)); this.serviceDescription$ = this.service$.pipe( - map(service => service.entity.entity.description) + map(service => service.entity.description) ); this.serviceUrl$ = this.service$.pipe( - map(service => getServiceSummaryUrl(service.entity.entity.cfGuid, service.entity.metadata.guid)) + map(service => getServiceSummaryUrl(service.entity.cfGuid, service.metadata.guid)) ); this.serviceName$ = this.service$.pipe( - map(service => getServiceName(service.entity)) + map(service => getServiceName(service)) ); } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-card/cf-service-card.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-card/cf-service-card.component.html index 0268284b7c..838f06bdb0 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-card/cf-service-card.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-card/cf-service-card.component.html @@ -13,13 +13,19 @@ - Service Broker + Broker - {{ serviceBrokerName$ | async }} + - Service Plans + Scope + + + + + + Plans {{ serviceEntity.entity.service_plans.length }} diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-card/cf-service-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-card/cf-service-card.component.ts index f3696b8c7a..cf73ba00b7 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-card/cf-service-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-card/cf-service-card.component.ts @@ -1,6 +1,5 @@ import { Component, Input } from '@angular/core'; import { Store } from '@ngrx/store'; -import { Observable } from 'rxjs'; import { CFAppState } from '../../../../../../../../cloud-foundry/src/cf-app-state'; import { AppChip } from '../../../../../../../../core/src/shared/components/chips/chips.component'; @@ -9,8 +8,12 @@ import { RouterNav } from '../../../../../../../../store/src/actions/router.acti import { EntityServiceFactory } from '../../../../../../../../store/src/entity-service-factory.service'; import { APIResource } from '../../../../../../../../store/src/types/api.types'; import { IService, IServiceExtra } from '../../../../../../cf-api-svc.types'; -import { getServiceBrokerName, getServiceName } from '../../../../../../features/service-catalog/services-helper'; +import { getServiceName } from '../../../../../../features/service-catalog/services-helper'; import { CfOrgSpaceLabelService } from '../../../../../services/cf-org-space-label.service'; +import { + TableCellServiceBrokerComponentConfig, + TableCellServiceBrokerComponentMode, +} from '../table-cell-service-broker/table-cell-service-broker.component'; export interface ServiceTag { value: string; @@ -27,7 +30,12 @@ export class CfServiceCardComponent extends CardCell> { cfOrgSpace: CfOrgSpaceLabelService; extraInfo: IServiceExtra; tags: AppChip[] = []; - serviceBrokerName$: Observable; + brokerNameConfig: TableCellServiceBrokerComponentConfig = { + mode: TableCellServiceBrokerComponentMode.NAME + } + brokerScopeConfig: TableCellServiceBrokerComponentConfig = { + mode: TableCellServiceBrokerComponentMode.SCOPE + } @Input() disableCardClick = false; @@ -45,13 +53,6 @@ export class CfServiceCardComponent extends CardCell> { if (!this.cfOrgSpace) { this.cfOrgSpace = new CfOrgSpaceLabelService(this.store, this.serviceEntity.entity.cfGuid); } - - if (!this.serviceBrokerName$) { - this.serviceBrokerName$ = getServiceBrokerName( - this.serviceEntity.entity.service_broker_guid, - this.serviceEntity.entity.cfGuid, - ); - } } } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts index 2ed4424106..4dcfd049b1 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts @@ -78,7 +78,7 @@ export class CfServiceInstancesListConfigBase implements IListConfig 'Service', cellComponent: TableCellServiceComponent, - cellFlex: '2' + cellFlex: '3' }, { columnId: 'lastOp', @@ -119,7 +119,7 @@ export class CfServiceInstancesListConfigBase implements IListConfig { }, { columnId: 'broker', headerCell: () => 'Broker', - cellDefinition: { - valuePath: 'entity.label', + cellComponent: TableCellServiceBrokerComponent, + cellConfig: { + mode: TableCellServiceBrokerComponentMode.NAME }, - cellFlex: '1' + cellFlex: '2' + }, { + columnId: 'brokerScope', + headerCell: () => 'Scope', + cellComponent: TableCellServiceBrokerComponent, + cellConfig: { + mode: TableCellServiceBrokerComponentMode.SCOPE + }, + cellFlex: '2' }, { columnId: 'plans', headerCell: () => 'Plans', diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.html new file mode 100644 index 0000000000..1465604a38 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.html @@ -0,0 +1,16 @@ +
+ +- \ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.scss b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.spec.ts new file mode 100644 index 0000000000..d7569ed286 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TableCellServiceBrokerComponent } from './table-cell-service-broker.component'; + +describe('TableCellServiceBrokerComponent', () => { + let component: TableCellServiceBrokerComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [TableCellServiceBrokerComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TableCellServiceBrokerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.ts new file mode 100644 index 0000000000..eea29808c6 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.ts @@ -0,0 +1,73 @@ +import { Component, Input } from '@angular/core'; +import { Observable } from 'rxjs'; +import { filter, map, switchMap } from 'rxjs/operators'; + +import { TableCellCustom } from '../../../../../../../../core/src/shared/components/list/list.types'; +import { APIResource } from '../../../../../../../../store/src/types/api.types'; +import { IServiceBroker } from '../../../../../../cf-api-svc.types'; +import { cfEntityCatalog } from '../../../../../../cf-entity-catalog'; +import { IService } from '../../../../../../public_api'; + +export enum TableCellServiceBrokerComponentMode { + NAME = 'NAME', + SCOPE = 'SCOPE' +} + +export interface TableCellServiceBrokerComponentConfig { + mode: TableCellServiceBrokerComponentMode; + altScope?: boolean; +} + +@Component({ + selector: 'app-table-cell-service-broker', + templateUrl: './table-cell-service-broker.component.html', + styleUrls: ['./table-cell-service-broker.component.scss'] +}) +export class TableCellServiceBrokerComponent extends TableCellCustom> { + + @Input() + config: TableCellServiceBrokerComponentConfig; + + pRow: APIResource; + @Input() + set row(row: APIResource) { + this.pRow = row; + if (row && !this.spaceLink$) { + this.broker$ = cfEntityCatalog.serviceBroker.store.getEntityService(this.row.entity.service_broker_guid, this.row.entity.cfGuid, {}).waitForEntity$ + .pipe( + map(e => e.entity) + ) + this.spaceLink$ = this.broker$.pipe( + filter(broker => !!broker.entity.space_guid), + switchMap(broker => cfEntityCatalog.space.store.getWithOrganization.getEntityService(broker.entity.space_guid, broker.entity.cfGuid).waitForEntity$), + map(e => e.entity), + map(space => ({ + name: space.entity.name, + link: ['/cloud-foundry', + space.entity.cfGuid, + 'organizations', + space.entity.organization_guid, + 'spaces', + space.metadata.guid, + 'summary' + ] + }) + ) + ) + } + } + get row(): APIResource { + return this.pRow; + } + + public spaceLink$: Observable<{ + name: string, + link: string[] + }>; + public broker$: Observable> + + constructor() { + super() + } + +} diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.html index 292f48f586..fd2aa9ce5b 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.html @@ -4,5 +4,12 @@
Plan: {{ row.entity.service_plan?.entity.name }}
-
Broker: {{ serviceBrokerName$ | async }}
+
+ Broker: + +
+
+ Scope: + +
\ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.scss b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.scss index e3f51aafb5..d28afe7bc1 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.scss +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.scss @@ -1,3 +1,10 @@ div > div { padding-bottom: 5px; } + +.broker { + display: flex; + app-table-cell-service-broker { + padding-left: 5px; + } +} diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.ts index 80434fe3b8..b9cbab0fec 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.ts @@ -1,6 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; import { Observable, of } from 'rxjs'; -import { filter, first, map, switchMap } from 'rxjs/operators'; +import { filter, map } from 'rxjs/operators'; import { cfEntityCatalog } from '../../../../../../../../cloud-foundry/src/cf-entity-catalog'; import { userProvidedServiceInstanceEntityType } from '../../../../../../../../cloud-foundry/src/cf-entity-types'; @@ -9,7 +9,11 @@ import { getServiceName } from '../../../../../../../../cloud-foundry/src/featur import { TableCellCustom } from '../../../../../../../../core/src/shared/components/list/list.types'; import { entityCatalog } from '../../../../../../../../store/src/entity-catalog/entity-catalog'; import { APIResource } from '../../../../../../../../store/src/types/api.types'; -import { IServiceInstance } from '../../../../../../cf-api-svc.types'; +import { IService, IServiceInstance } from '../../../../../../cf-api-svc.types'; +import { + TableCellServiceBrokerComponentConfig, + TableCellServiceBrokerComponentMode, +} from '../../cf-services/table-cell-service-broker/table-cell-service-broker.component'; @Component({ selector: 'app-table-cell-service', @@ -20,13 +24,21 @@ export class TableCellServiceComponent extends TableCellCustom; serviceUrl$: Observable; - serviceBrokerName$: Observable; + service$: Observable>; // tslint:disable-next-line:ban-types isUserProvidedServiceInstance: Boolean; @Input() row: APIResource; @Input() entityKey: string; + brokerNameConfig: TableCellServiceBrokerComponentConfig = { + mode: TableCellServiceBrokerComponentMode.NAME + } + brokerScopeConfig: TableCellServiceBrokerComponentConfig = { + mode: TableCellServiceBrokerComponentMode.SCOPE, + altScope: true + } + ngOnInit() { this.isUserProvidedServiceInstance = @@ -46,18 +58,9 @@ export class TableCellServiceComponent extends TableCellCustom `/marketplace/${service.entity.entity.cfGuid}/${service.entity.metadata.guid}/summary`) ); - this.serviceBrokerName$ = service$.pipe( - first(), - switchMap(service => { - const brokerGuid = service.entity.entity.service_broker_guid; - return cfEntityCatalog.serviceBroker.store.getEntityService(brokerGuid, service.entity.entity.cfGuid, {}) - .waitForEntity$.pipe( - map(a => a.entity), - filter(res => !!res), - map(a => a.entity.name), - first() - ); - }) + this.service$ = service$.pipe( + filter(res => !!res), + map(a => a.entity), ); } } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.html index c9fa1a25fe..65a4475306 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.html @@ -22,7 +22,17 @@ Service Broker - {{ serviceBrokerName$ | async }} + + + + + + + Service Scope + + + + Applications Attached diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.ts index ffb0dd8ae5..8f8f8a5592 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.ts @@ -1,7 +1,7 @@ import { Component, Input } from '@angular/core'; import { Store } from '@ngrx/store'; import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; -import { filter, map, switchMap } from 'rxjs/operators'; +import { filter, map } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../../cloud-foundry/src/cf-app-state'; import { serviceInstancesEntityType } from '../../../../../../../../cloud-foundry/src/cf-entity-types'; @@ -17,7 +17,6 @@ import { IService, IServiceInstance } from '../../../../../../cf-api-svc.types'; import { cfEntityCatalog } from '../../../../../../cf-entity-catalog'; import { cfEntityFactory } from '../../../../../../cf-entity-factory'; import { - getServiceBrokerName, getServiceName, getServicePlanName, getServiceSummaryUrl, @@ -26,6 +25,10 @@ import { CfCurrentUserPermissions } from '../../../../../../user-permissions/cf- import { ServiceActionHelperService } from '../../../../../data-services/service-action-helper.service'; import { CfOrgSpaceLabelService } from '../../../../../services/cf-org-space-label.service'; import { CSI_CANCEL_URL } from '../../../../add-service-instance/csi-mode.service'; +import { + TableCellServiceBrokerComponentConfig, + TableCellServiceBrokerComponentMode, +} from '../../cf-services/table-cell-service-broker/table-cell-service-broker.component'; @Component({ selector: 'app-service-instance-card', @@ -97,15 +100,6 @@ export class ServiceInstanceCardComponent extends CardCell getServiceBrokerName(service.entity.service_broker_guid, service.entity.cfGuid)) - ) - } - if (!this.serviceName$) { // See note for this.serviceBrokerName$ this.serviceName$ = this.service$.pipe( @@ -142,12 +136,19 @@ export class ServiceInstanceCardComponent extends CardCell; serviceName$: Observable; servicePlanName: string; serviceUrl: string; - private service$: Observable>; + service$: Observable>; + + brokerNameConfig: TableCellServiceBrokerComponentConfig = { + mode: TableCellServiceBrokerComponentMode.NAME + } + brokerScopeConfig: TableCellServiceBrokerComponentConfig = { + mode: TableCellServiceBrokerComponentMode.SCOPE, + altScope: true + } private detach = () => { this.serviceActionHelperService.detachServiceBinding( diff --git a/src/frontend/packages/store/src/actions/entity.delete.actions.ts b/src/frontend/packages/store/src/actions/entity.delete.actions.ts new file mode 100644 index 0000000000..2b69ee601b --- /dev/null +++ b/src/frontend/packages/store/src/actions/entity.delete.actions.ts @@ -0,0 +1,31 @@ +import { Action } from '@ngrx/store'; + +import { EntityRequestAction } from '../types/request.types'; +import { IFavoriteMetadata, UserFavorite } from '../types/user-favorites.types'; + +export class EntityDeleteCompleteAction implements Action { + + public static ACTION_TYPE = '[Entity] Entity delete complete'; + public type = EntityDeleteCompleteAction.ACTION_TYPE; + + constructor( + public entityGuid: string, + public entityType: string, + public endpointGuid: string, + public endpointType: string, + public action: EntityRequestAction, + ) {} + + // Create an entity delete action if we have all of the properties we need + public static parse(action: EntityRequestAction): EntityDeleteCompleteAction { + if (action.guid && action.entityType && action.endpointType && action.endpointGuid) { + return new EntityDeleteCompleteAction(action.guid, action.entityType, action.endpointGuid, action.endpointType, action); + } + return null; + } + + public asFavorite(): UserFavorite { + return new UserFavorite(this.endpointGuid, this.endpointType, this.entityType, this.entityGuid); + } + +} diff --git a/src/frontend/packages/store/src/effects/api.effects.ts b/src/frontend/packages/store/src/effects/api.effects.ts index 493fe31634..34ee572143 100644 --- a/src/frontend/packages/store/src/effects/api.effects.ts +++ b/src/frontend/packages/store/src/effects/api.effects.ts @@ -3,13 +3,14 @@ import { Actions, Effect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; import { mergeMap, withLatestFrom } from 'rxjs/operators'; +import { EntityDeleteCompleteAction } from '../actions/entity.delete.actions'; import { baseRequestPipelineFactory } from '../entity-request-pipeline/base-single-entity-request.pipeline'; import { basePaginatedRequestPipeline } from '../entity-request-pipeline/entity-pagination-request-pipeline'; import { apiRequestPipelineFactory } from '../entity-request-pipeline/entity-request-pipeline'; import { PipelineHttpClient } from '../entity-request-pipeline/pipline-http-client.service'; import { PaginatedAction } from '../types/pagination.types'; -import { ICFAction } from '../types/request.types'; -import { ApiActionTypes } from './../actions/request.actions'; +import { ICFAction, WrapperRequestActionSuccess } from '../types/request.types'; +import { ApiActionTypes, RequestTypes } from './../actions/request.actions'; import { InternalAppState } from './../app-state'; @Injectable() @@ -18,9 +19,7 @@ export class APIEffect { private actions$: Actions, private store: Store, private httpClient: PipelineHttpClient - ) { - - } + ) { } @Effect() apiRequest$ = this.actions$.pipe( @@ -44,4 +43,22 @@ export class APIEffect { }), ); + // Whenever we spot a delete success operation, look to see if the action + // fulfils the entity delete requirements and dispatch an entity delete action if it does + @Effect() + apiDeleteRequest$ = this.actions$.pipe( + ofType(RequestTypes.SUCCESS), + withLatestFrom(this.store), + mergeMap(([action, appState]) => { + if (action.requestType === 'delete') { + const deleteAction = EntityDeleteCompleteAction.parse(action.apiAction); + if (deleteAction) { + // Dispatch a delete action for the entity + this.store.dispatch(deleteAction); + } + } + return []; + }) + ); + } diff --git a/src/frontend/packages/store/src/effects/user-favorites-effect.ts b/src/frontend/packages/store/src/effects/user-favorites-effect.ts index 02240e52af..76e31662f1 100644 --- a/src/frontend/packages/store/src/effects/user-favorites-effect.ts +++ b/src/frontend/packages/store/src/effects/user-favorites-effect.ts @@ -2,8 +2,9 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Actions, Effect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; -import { catchError, first, mergeMap, switchMap } from 'rxjs/operators'; +import { catchError, first, mergeMap, switchMap, withLatestFrom } from 'rxjs/operators'; +import { EntityDeleteCompleteAction } from '../actions/entity.delete.actions'; import { ClearPaginationOfEntity } from '../actions/pagination.actions'; import { GetUserFavoritesAction, @@ -17,24 +18,24 @@ import { UpdateUserFavoriteMetadataAction, UpdateUserFavoriteMetadataSuccessAction, } from '../actions/user-favourites.actions'; -import { DispatchOnlyAppState } from '../app-state'; +import { InternalAppState } from '../app-state'; import { entityCatalog } from '../entity-catalog/entity-catalog'; import { proxyAPIVersion } from '../jetstream'; import { NormalizedResponse } from '../types/api.types'; import { StartRequestAction, WrapperRequestActionFailed, WrapperRequestActionSuccess } from '../types/request.types'; import { IFavoriteMetadata, UserFavorite, userFavoritesPaginationKey } from '../types/user-favorites.types'; import { UserFavoriteManager } from '../user-favorite-manager'; +import { STRATOS_ENDPOINT_TYPE, userFavouritesEntityType } from './../helpers/stratos-entity-factory'; const favoriteUrlPath = `/pp/${proxyAPIVersion}/favorites`; - @Injectable() export class UserFavoritesEffect { constructor( private http: HttpClient, private actions$: Actions, - private store: Store, + private store: Store, private userFavoriteManager: UserFavoriteManager ) { } @@ -144,4 +145,20 @@ export class UserFavoritesEffect { ); }) ); + + @Effect() + entityDeleteRequest$ = this.actions$.pipe( + ofType(EntityDeleteCompleteAction.ACTION_TYPE), + withLatestFrom(this.store), + mergeMap(([action, appState]) => { + // If there is a favorite, delete it + const fav = action.asFavorite(); + const entityKey = entityCatalog.getEntityKey(STRATOS_ENDPOINT_TYPE, userFavouritesEntityType); + if (appState.requestData && appState.requestData[entityKey] && appState.requestData[entityKey][fav.guid]) { + this.store.dispatch(new RemoveUserFavoriteAction(fav)); + } + return []; + }) + ); + } diff --git a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/recently-visited.reducer.helpers.ts b/src/frontend/packages/store/src/reducers/current-user-roles-reducer/recently-visited.reducer.helpers.ts index f1e84bafca..f63f5a5781 100644 --- a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/recently-visited.reducer.helpers.ts +++ b/src/frontend/packages/store/src/reducers/current-user-roles-reducer/recently-visited.reducer.helpers.ts @@ -1,3 +1,4 @@ +import { EntityDeleteCompleteAction } from '../../actions/entity.delete.actions'; import { AddRecentlyVisitedEntityAction } from '../../actions/recently-visited.actions'; import { IRecentlyVisitedEntity, IRecentlyVisitedState } from '../../types/recently-visited.types'; @@ -62,3 +63,11 @@ export function cleanRecentsList(state: IRecentlyVisitedState, endpointGuids: st // Convert the array back into a map return filtered.reduce(recentArrayToMap, {}); } + +export function clearEntityFromRecentsList(state: IRecentlyVisitedState, action: EntityDeleteCompleteAction): IRecentlyVisitedState { + // Remove entity from the map if it exists + const fav = action.asFavorite(); + const newState = { ...state }; + delete newState[fav.guid]; + return newState; +} \ No newline at end of file diff --git a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/recently-visited.reducer.ts b/src/frontend/packages/store/src/reducers/current-user-roles-reducer/recently-visited.reducer.ts index 79fd5eaee4..19e21f17d9 100644 --- a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/recently-visited.reducer.ts +++ b/src/frontend/packages/store/src/reducers/current-user-roles-reducer/recently-visited.reducer.ts @@ -7,17 +7,25 @@ import { GetAllEndpointsSuccess, UNREGISTER_ENDPOINTS_SUCCESS, } from '../../actions/endpoint.actions'; +import { EntityDeleteCompleteAction } from '../../actions/entity.delete.actions'; import { AddRecentlyVisitedEntityAction, SetRecentlyVisitedEntityAction } from '../../actions/recently-visited.actions'; import { entityCatalog } from '../../entity-catalog/entity-catalog'; import { endpointEntityType, STRATOS_ENDPOINT_TYPE } from '../../helpers/stratos-entity-factory'; import { IRecentlyVisitedState } from '../../types/recently-visited.types'; -import { addRecentlyVisitedEntity, cleanRecentsList, getDefaultRecentState } from './recently-visited.reducer.helpers'; +import { + addRecentlyVisitedEntity, + cleanRecentsList, + clearEntityFromRecentsList, + getDefaultRecentState, +} from './recently-visited.reducer.helpers'; export function recentlyVisitedReducer( state: IRecentlyVisitedState = getDefaultRecentState(), action: Action ): IRecentlyVisitedState { switch (action.type) { + case EntityDeleteCompleteAction.ACTION_TYPE: + return clearEntityFromRecentsList(state, action as EntityDeleteCompleteAction); case AddRecentlyVisitedEntityAction.ACTION_TYPE: return addRecentlyVisitedEntity(state, action as AddRecentlyVisitedEntityAction); case SetRecentlyVisitedEntityAction.ACTION_TYPE: diff --git a/src/test-e2e/application/application-autoscaler-e2e.spec.ts b/src/test-e2e/application/application-autoscaler-e2e.spec.ts index 8eb872fd73..996487c8d4 100644 --- a/src/test-e2e/application/application-autoscaler-e2e.spec.ts +++ b/src/test-e2e/application/application-autoscaler-e2e.spec.ts @@ -534,9 +534,6 @@ describe('Autoscaler -', () => { } else { console.log(`${time}: Waiting for event row: manually refreshing list`); eventPageBase.list.header.refresh(); - if (retries === 0) { - sub.unsubscribe(); - } } } }); diff --git a/src/test-e2e/marketplace/create-service-instances-bind-app-e2e.spec.ts b/src/test-e2e/marketplace/create-service-instances-bind-app-e2e.spec.ts index 91eb24e945..0fe47fada5 100644 --- a/src/test-e2e/marketplace/create-service-instances-bind-app-e2e.spec.ts +++ b/src/test-e2e/marketplace/create-service-instances-bind-app-e2e.spec.ts @@ -50,7 +50,7 @@ describe('Create Service Instance with binding', () => { .then(metaCardRows => { expect(metaCardRows[1].value).toBe(servicesSecrets.publicService.name); expect(metaCardRows[2].value).toBe('shared'); - expect(metaCardRows[4].value).toBe('1'); + expect(metaCardRows[5].value).toBe('1'); }); }); From 302c5deee1a4cef4869fa78a24b62bd9e976c327 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 24 Jul 2020 17:58:15 +0100 Subject: [PATCH 586/648] Security Observability: Add persistent volume, gate on tech preview (#418) * Work in progress Analysis tools * Thursday updates * Friday fixes and improvements * Friday code * Separate analyzers into a separate container * Wire in analyzers container in the Helm chart * Hide analysis UI features when not enabled * Fix sidepanel bug with fallback metadata * Bug fix for change in way tab links are hidden * Remove debug logging * Add refresh button to report selector drop down. Change no reports icon * Add support for adding breadcrumbs in the sub nav bar * Fix unit tests * Fix format issues * Final front-end unit test fixes * Fix issues when deploying via Helm with mariadb * Analyzers container fix. Allow helm chart to be packaged. * Build script fixes * Remove file * WIP: Add support for Clair image scanning * Use klar * Remove binary * Add clair helm chart for dev * Fixes * USe end var for clair server address * Latest updates * Improvements * Minor fixes * Tweak * Fix 1.16 detecton issue with the analyzers * Chart fixes * Changes following first run of script * Changes following npm install * Update custom-src to new model - expose custom module's module's - Add routing module - Tweak stratos.config.ts log output - remove custom-src dir * update naming... custom extensions --> suse extensions * A few tidyups to help review * Fix build issue due to merge * Fixes following merge from upstream * Remove clair from this PR * Ignore example packages when there's a stratos config file * Changes following review * Changes following merge * Update dir names, remove examples folder * Add back in custom-src deploy content, also add product version to config * Revert change needed downstream... (only needed when suse extension is included) * Remove unused wip report viewers * Fix after merge * Move new terminal & config code to plugin, fix more build files * Fix imports and add doc * Fix compilation issues * Change following merge * Tweaks to logging * Fix bug where report can not be deleted * Fix kube config connect after merge, also fix subtype & error on connect * Fix e2e * Improve drop-down menu * Remove strange merge artifacts * Remove build file * Fix graph overview * Numerous improvements to graph parsing and presentation * Remove logging. Add no reports message to workload analysis * Add support for CRDs. KubeCF renders correctly. * Allow which engines are enabled to be configured * Fix issue where reports are not filtered by endpoint * Minor changes following review * Fixes for a few more issues * Add Analyzers image build to Concourse CI * Multiple small fixes - fix text search in analysis list - fix title of links in analysers info page - handle slow connections by only polling analysis list when not already * Fix kubeGuid for helm world * Add AnalysisReportRunnerComponent - Still need to add this to other places * Delete reports when endpoint is unregistered * Buf fixes. Use breadcrumbs in sub-nav * Add run analysis button to workload analyis and graph tabs * Fix select of overlay in workload graphs page * Change default sort order of analysis list to age * Ensure table cell links update on row change * Align table action's icon better * Use a side panel for analyzer info * Add actions/effects for all used analyis actions - Add new ResetPaginationOfType action, like ResetPagination but applies to all types - Allows user to refresh reports list after kicking off new report on namespace & workload tabs - Handle missing report param in reports returned from get all reports * Remove some console.logs, converted some to console.info * Update Kube Dashboard, allow download link to be configurable - Default download link updated to v2.0.3 - Can configured link by setting env var `STRATOS_KUBERNETES_DASHBOARD_IMAGE` - Can configure env var in helm via `console.kubeDashboardImage` - Kube Dashboard now expanded by default (to show namespace drop down) * Fix after merge * Changes following review * Fix expand of kube dashboard header by default * Changes following review * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * WIP Wire in alerts to workload graph - need to understand if namespace should be checked when matching node/resource to alert - need to apply correct colour * Fix workload security analysis overlay slide in * Hide analysis headers info in tech preview & tie in tech preview check to analysisService.hideAnalysis$ - Q should the backend plugins be available in tech preview, see TODO * Hide the Workload Graph view if in tech preview * Fix disable of analysis plugin when tech preview is switched off * Adderss PR feedback * Gate analyzer deployment on tech preview. Add persistent volume * Fixes - removed volume for now * Fix issue with persistent volume * Fix error message colour in login screen * Fix after merge Co-authored-by: Richard Cox --- .../console/templates/analyzers.yaml | 47 +++++++++++++++++++ deploy/kubernetes/console/values.yaml | 6 +++ .../helm-release-resource-graph.component.ts | 13 ++--- .../suse-login/suse-login.component.scss | 1 + 4 files changed, 57 insertions(+), 10 deletions(-) diff --git a/deploy/kubernetes/console/templates/analyzers.yaml b/deploy/kubernetes/console/templates/analyzers.yaml index ca4d5a0d6d..75208610ff 100644 --- a/deploy/kubernetes/console/templates/analyzers.yaml +++ b/deploy/kubernetes/console/templates/analyzers.yaml @@ -1,4 +1,5 @@ --- +{{- if .Values.console.techPreview }} {{- if semverCompare ">=1.16" (printf "%s.%s" .Capabilities.KubeVersion.Major (trimSuffix "+" .Capabilities.KubeVersion.Minor) )}} apiVersion: apps/v1 {{- else }} @@ -20,6 +21,10 @@ spec: app.kubernetes.io/component: "stratos-analyzers" template: metadata: +{{- if .Values.console.podAnnotations }} + annotations: +{{ toYaml .Values.console.podAnnotations | indent 8 }} +{{- end }} labels: app.kubernetes.io/name: "stratos" app.kubernetes.io/instance: "{{ .Release.Name }}" @@ -27,6 +32,9 @@ spec: app.kubernetes.io/component: "stratos-analyzers" helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" app: "{{ .Release.Name }}" + {{- if .Values.console.podExtraLabels}} + {{ toYaml .Values.console.podExtraLabels | nindent 8 }} + {{- end}} spec: containers: - name: analyzers @@ -36,10 +44,25 @@ spec: - name: api containerPort: 8090 env: + - name: STRATOS_IMAGE_REF + value: "{{.Values.consoleVersion}}:{{ .Release.Revision }}" - name: ANALYSIS_SCRIPTS_DIR value: "/scripts" - name: ANALYSIS_REPORTS_DIR value: "/reports" + volumeMounts: + - name: data + mountPath: /reports + {{- if and .Values.kube.registry.username .Values.kube.registry.password }} + imagePullSecrets: + - name: {{.Values.dockerRegistrySecret}} + {{- end }} + {{- if not .Values.console.reportsVolumeDisabled }} + volumes: + - name: data + persistentVolumeClaim: + claimName: "{{ .Release.Name }}-reports" + {{- end }} --- apiVersion: v1 kind: Service @@ -60,3 +83,27 @@ spec: selector: app: "{{ .Release.Name }}" app.kubernetes.io/component: "stratos-analyzers" +--- +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: "{{ .Release.Name }}-reports" + labels: + app.kubernetes.io/name: "stratos" + app.kubernetes.io/instance: "{{ .Release.Name }}" + app.kubernetes.io/version: "{{ .Chart.AppVersion }}" + app.kubernetes.io/component: "stratos-reports-volume" + helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" + annotations: + {{- if .Values.storageClass }} + volume.beta.kubernetes.io/storage-class: {{ .Values.storageClass | quote }} + {{- else }} + volume.alpha.kubernetes.io/storage-class: default + {{- end }} +spec: + accessModes: + - "ReadWriteOnce" + resources: + requests: + storage: {{ default "1Gi" .Values.console.reportsVolumeSize | quote }} +{{- end }} diff --git a/deploy/kubernetes/console/values.yaml b/deploy/kubernetes/console/values.yaml index 5dbf69f47d..d2ee436aca 100644 --- a/deploy/kubernetes/console/values.yaml +++ b/deploy/kubernetes/console/values.yaml @@ -112,6 +112,12 @@ console: # Download link when installing the Kubernetes Dashboard in a targetted Kube Endpoint kubeDashboardImage: + + # Size for analysis reports volume + reportsVolumeSize: 1Gi + + # Do not use a persistent volume for analysis reports + reportsVolumeDisabled: false images: console: stratos-console diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.ts index 9e73f79e75..99235522c1 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.ts @@ -245,16 +245,9 @@ export class HelmReleaseResourceGraphComponent implements OnInit, OnDestroy { private getResource(node: CustomHelmReleaseGraphNode): Observable { return this.helper.fetchReleaseResources().pipe( filter(r => !!r), - // tap(r => { - // console.log(node); - // console.log(r); - // }), - map((r: HelmReleaseResources) => Object.values(r.data).find((res) => { - // if (!res.metadata) { - // console.log(node, res); - // } - return res.metadata.name === node.label && res.kind === node.data.kind; - })), + map((r: HelmReleaseResources) => Object.values(r.data).find((res) => + res.metadata.name === node.label && res.kind === node.data.kind + )), first(), ); } diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss b/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss index 62ccddf629..c84aae5cfa 100644 --- a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss @@ -128,6 +128,7 @@ } } &__message { + color: $suse-gray-fg; font-size: 18px; font-weight: 300; height: 20px; From 4c43db43fae668a2af49c913af477371741a6d43 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 24 Jul 2020 18:08:42 +0100 Subject: [PATCH 587/648] 4.0.0 changelog (#423) --- CHANGELOG.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3822b7ef27..1626295348 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,19 @@ ## 4.0.0 -[Full Changelog](https://github.com/cloudfoundry/stratos/compare/3.2.1...4.0.0) +[Full Changelog](https://github.com/SUSE/stratos/compare/3.2.1...4.0.0) This release contains a number of fixes and improvements: **Improvements:** +- Add Kubernetes Terminal feature [\#422](https://github.com/SUSE/stratos/issues/422) +- Add Dark Mode [\#410](https://github.com/SUSE/stratos/issues/410) +- Add Security Observability support [\#401](https://github.com/SUSE/stratos/issues/401) +- Update kube dashboard version [\#395](https://github.com/SUSE/stratos/issues/395) +- Workload: Add warning if manifest parses with errors [\#390](https://github.com/SUSE/stratos/issues/390) +- Workload Summary: Improve Ready/Not Ready stats when there are terminated containers [\#388](https://github.com/SUSE/stratos/issues/388) +- Update theme to reflect new SUSE branding [\#385](https://github.com/SUSE/stratos/issues/385) - Extensions: Remove the need for symlinks and improve the build process [\#4472](https://github.com/cloudfoundry/stratos/issues/4472) - Extensions: Allow Themes to be published and installed to/from npm [\#4471](https://github.com/cloudfoundry/stratos/issues/4471) - Extensions: Move to extensions and themes to be packages [\#4470](https://github.com/cloudfoundry/stratos/issues/4470) @@ -34,6 +41,9 @@ This release contains a number of fixes and improvements: **Fixes:** +- Helm Chart: Icon is missing [\#393](https://github.com/SUSE/stratos/issues/393) +- Workload Summary: Ring chart series toggled on refresh & legend vs ring colours missmatched [\#389](https://github.com/SUSE/stratos/issues/389) +- Can't connect a K3S endpoint [\#372](https://github.com/SUSE/stratos/issues/372) - Ensure `cf push` works from Windows [\#4465](https://github.com/cloudfoundry/stratos/issues/4465) - Visiting marketplace tab breaks service list [\#4397](https://github.com/cloudfoundry/stratos/issues/4397) - CF Application reports error after restage [\#4392](https://github.com/cloudfoundry/stratos/issues/4392) From da704933bc5697460d490864ac59f0e133a81115 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 28 Jul 2020 09:28:06 +0100 Subject: [PATCH 588/648] Ensure custom login component included in `ng build --prod` (#424) * Allow extensions to reference the custom login component - will then be included in `ng build --prod` - See #4473 * Add suse log in component to extension metadata * Revert "Allow extensions to reference the custom login component" This reverts commit 3988210aed1a4f3a00409c8070ad89613785fd9e. * Revert "Add suse log in component to extension metadata" This reverts commit a92f2f785757430c44a524281db859e7fc35bc78. * Use correct method to include tree shaken component - ExtensionService.declare --- .../packages/suse-extensions/src/custom/suse.module.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/frontend/packages/suse-extensions/src/custom/suse.module.ts b/src/frontend/packages/suse-extensions/src/custom/suse.module.ts index e0d45321b1..d147814f58 100644 --- a/src/frontend/packages/suse-extensions/src/custom/suse.module.ts +++ b/src/frontend/packages/suse-extensions/src/custom/suse.module.ts @@ -3,6 +3,7 @@ import { NgModule } from '@angular/core'; import { CoreModule } from '../../../core/src/core/core.module'; import { CustomizationService, CustomizationsMetadata } from '../../../core/src/core/customizations.types'; import { MDAppModule } from '../../../core/src/core/md.module'; +import { ExtensionService } from '../../../core/src/public-api'; import { SharedModule } from '../../../core/src/shared/shared.module'; import { DemoHelperComponent } from './demo/demo-helper/demo-helper.component'; import { HelmSetupModule } from './helm/helm.setup.module'; @@ -26,6 +27,9 @@ const SuseCustomizations: CustomizationsMetadata = { MDAppModule, KubernetesSetupModule, HelmSetupModule, + ExtensionService.declare([ + SuseLoginComponent, + ]) ], // FIXME: Ensure that anything lazy loaded/in kube endpoint pages is not included here - #3675 declarations: [ From 582506f5c61187a26c31a1b380409be3882ae477 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 28 Jul 2020 09:28:21 +0100 Subject: [PATCH 589/648] Ensure filename/no filename text in connect by file dialog is aligned (#425) - needs porting back --- .../src/shared/components/file-input/file-input.component.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/frontend/packages/core/src/shared/components/file-input/file-input.component.scss b/src/frontend/packages/core/src/shared/components/file-input/file-input.component.scss index b1417ebcc8..aa0e1b908a 100644 --- a/src/frontend/packages/core/src/shared/components/file-input/file-input.component.scss +++ b/src/frontend/packages/core/src/shared/components/file-input/file-input.component.scss @@ -7,6 +7,7 @@ pointer-events: none; } &__field { + align-items: center; display: flex; } &__input { From 381e609f4757929456f07196c8558d60b9957cce Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 28 Jul 2020 09:29:03 +0100 Subject: [PATCH 590/648] Fix workload resource 404s/workload service age field (#426) - jobs to fetch resources were using a bad property for namespace (empty) - this resulted in namespaced resources being fetched as if they weren't, so 404s - seen as missing age field in workload services table & missing data in graph resource slide in --- src/jetstream/plugins/kubernetes/helm/release.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jetstream/plugins/kubernetes/helm/release.go b/src/jetstream/plugins/kubernetes/helm/release.go index 868200bb89..915d1c8a2d 100644 --- a/src/jetstream/plugins/kubernetes/helm/release.go +++ b/src/jetstream/plugins/kubernetes/helm/release.go @@ -192,7 +192,7 @@ func (r *HelmRelease) processKubeResource(obj interface{}, t KubeResource) { r.setResource(t) log.Debugf("Got resource: %s : %s", t.Kind, t.Metadata.Name) r.processController(t) - r.addJobForResource(t.Metadata.Namespace, t.Kind, t.APIVersion, t.Metadata.Name) + r.addJobForResource(r.Namespace, t.Kind, t.APIVersion, t.Metadata.Name) } func (r *HelmRelease) addJobForResource(namespace, kind, apiVersion, name string) { @@ -200,7 +200,7 @@ func (r *HelmRelease) addJobForResource(namespace, kind, apiVersion, name string ID: fmt.Sprintf("%s-%s#Pods", kind, name), Endpoint: r.Endpoint, User: r.User, - Namespace: r.Namespace, + Namespace: namespace, Name: name, URL: getRestURL(namespace, kind, apiVersion, name), APIVersion: apiVersion, From 54507417957bd0b2229cea15cff6ac62bec3fc07 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Thu, 30 Jul 2020 14:07:45 +0100 Subject: [PATCH 591/648] Set memory limits for stratos-chartstore fdbdoclayer container - fixes #429 - apply limit to container showing memory leak - resources cannot be set in `DeploymentSpec` (no `resources` - https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#deploymentspec-v1-apps) --- .../kubernetes/console/templates/fdbdoclayer/deployment.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/deploy/kubernetes/console/templates/fdbdoclayer/deployment.yaml b/deploy/kubernetes/console/templates/fdbdoclayer/deployment.yaml index 4a1a02e06d..90923912b0 100644 --- a/deploy/kubernetes/console/templates/fdbdoclayer/deployment.yaml +++ b/deploy/kubernetes/console/templates/fdbdoclayer/deployment.yaml @@ -86,6 +86,11 @@ spec: - name: certs mountPath: "/etc/secrets" readOnly: true + resources: + limits: + memory: 384Mi + requests: + memory: 256Mi - name: fdbserver image: {{.Values.kube.registry.hostname}}/{{.Values.kube.organization}}/{{.Values.images.fdbserver}}:{{.Values.consoleVersion}} imagePullPolicy: {{.Values.imagePullPolicy}} From b3c3339e0f1cabe3ef2c75d26357dd0c239ad2d4 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 31 Jul 2020 07:53:18 +0100 Subject: [PATCH 592/648] Update change log --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e30ecb5e2..7161d27c13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ This release contains a number of fixes and improvements: **Fixes:** +- Memory leak in stratos-chartstore (fdbdoclayer) [\#429](https://github.com/SUSE/stratos/issues/429) - Helm Chart: Icon is missing [\#393](https://github.com/SUSE/stratos/issues/393) - Workload Summary: Ring chart series toggled on refresh & legend vs ring colours missmatched [\#389](https://github.com/SUSE/stratos/issues/389) - Can't connect a K3S endpoint [\#372](https://github.com/SUSE/stratos/issues/372) From d2fb52d76ecfb98ae4111ca0fa19869837819c78 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 7 Aug 2020 16:20:57 +0100 Subject: [PATCH 593/648] Add typed entity access and `custom-src` removal to change log (#435) --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7161d27c13..9a780c8ba0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ This release contains a number of fixes and improvements: - Workload: Add warning if manifest parses with errors [\#390](https://github.com/SUSE/stratos/issues/390) - Workload Summary: Improve Ready/Not Ready stats when there are terminated containers [\#388](https://github.com/SUSE/stratos/issues/388) - Update theme to reflect new SUSE branding [\#385](https://github.com/SUSE/stratos/issues/385) +- Extensions: Allow typed access to store entities and their actions [\#4494](https://github.com/cloudfoundry/stratos/issues/4494) - Extensions: Remove the need for symlinks and improve the build process [\#4472](https://github.com/cloudfoundry/stratos/issues/4472) - Extensions: Allow Themes to be published and installed to/from npm [\#4471](https://github.com/cloudfoundry/stratos/issues/4471) - Extensions: Move to extensions and themes to be packages [\#4470](https://github.com/cloudfoundry/stratos/issues/4470) @@ -71,6 +72,9 @@ This release contains a number of fixes and improvements: **Breaking Changes:** +- **Customizations in `custom-src` are now npm packages** + + Stratos customizations were previously in `./custom-src` and included in the build via symlinks. These customizations have now moved into local npm packages located in `./src/frontend/packages`. For more details please see our customization documentation at `./website/docs/extensions/introduction.md` or `https://stratos.app/docs/extensions/introduction`. There you will also find instructions on migrating to npm packages and a tool to help automate most of the process. - **Kubernetes: Upgrade only possible from version 3.0.0 or later** When deploying into Kubernetes using Helm and upgrading from an earlier version of Stratos using `helm upgrade`, upgrade is **only** supported from version 3.0.0 or later. If you are using an earlier version, first upgrade to version 3.x before then upgrading to the latest version. From 8ad354a4b78714549e70004f78ae1becb0e88179 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 13 Aug 2020 14:17:34 +0100 Subject: [PATCH 594/648] Fix helm 3 lint issue (#440) --- deploy/kubernetes/console/templates/analyzers.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/kubernetes/console/templates/analyzers.yaml b/deploy/kubernetes/console/templates/analyzers.yaml index 75208610ff..226d984564 100644 --- a/deploy/kubernetes/console/templates/analyzers.yaml +++ b/deploy/kubernetes/console/templates/analyzers.yaml @@ -1,5 +1,5 @@ ---- {{- if .Values.console.techPreview }} +--- {{- if semverCompare ">=1.16" (printf "%s.%s" .Capabilities.KubeVersion.Major (trimSuffix "+" .Capabilities.KubeVersion.Minor) )}} apiVersion: apps/v1 {{- else }} From 2962f50dceda08a5ad0957c129c5ebb04b570504 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 13 Aug 2020 14:27:45 +0100 Subject: [PATCH 595/648] Fix Docker All in One (#439) --- deploy/Dockerfile.all-in-one | 9 ++++++--- deploy/all-in-one/dig.sh | 8 ++++++++ deploy/all-in-one/src-build.sh | 1 - 3 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 deploy/all-in-one/dig.sh diff --git a/deploy/Dockerfile.all-in-one b/deploy/Dockerfile.all-in-one index 5cc1408c3a..9ff9b1a73a 100644 --- a/deploy/Dockerfile.all-in-one +++ b/deploy/Dockerfile.all-in-one @@ -9,12 +9,10 @@ COPY --chown=stratos:users deploy/all-in-one/config.all-in-one.properties config ARG USE_PREBUILT_UI=false -USER root +USER stratos RUN node --version RUN deploy/all-in-one/src-build.sh -USER stratos - # Generate dev-certs RUN CERTS_PATH=/home/stratos/dev-certs ./generate_cert.sh @@ -137,6 +135,11 @@ RUN if [ "x$CANARY_BUILD" != "x" ] ; then printf "\nFORCE_ENABLE_PERSISTENCE_FEA # Enable tech preview features if canary build flag is set RUN if [ "x$CANARY_BUILD" != "x" ] ; then printf "\nENABLE_TECH_PREVIEW=true\n" >> /srv/config.properties ; fi +# Fix dig to resolve localhost to 127.0.0.1 +RUN mv /usr/bin/dig /usr/bin/digit +COPY deploy/all-in-one/dig.sh /usr/bin/dig +RUN chmod +x /usr/bin/dig + EXPOSE 443 # Need to be root to bind to port 443 diff --git a/deploy/all-in-one/dig.sh b/deploy/all-in-one/dig.sh new file mode 100644 index 0000000000..82fb0a6fe6 --- /dev/null +++ b/deploy/all-in-one/dig.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +HOST="${@: -1}" +if [ "$HOST" == "localhost" ]; then + echo "127.0.0.1" +else + /usr/bin/digit $@ +fi diff --git a/deploy/all-in-one/src-build.sh b/deploy/all-in-one/src-build.sh index d1618d6037..29a8049ace 100755 --- a/deploy/all-in-one/src-build.sh +++ b/deploy/all-in-one/src-build.sh @@ -9,7 +9,6 @@ if [ "${USE_PREBUILT_UI}" = "true" ]; then else echo "Building frontend" npm install - npm run customize npm run build if [ $? -ne 0 ]; then echo "Frontend build failed" From afa26632e278bc33cbe2fb8d7f77f0acd359f955 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 18 Aug 2020 14:50:44 +0100 Subject: [PATCH 596/648] Merge upstream (#444) * Ensure filename/no filename text in connect by file dialog is aligned (#4474) - needs porting back * Add typed entity access and `custom-src` removal to change log (#4496) * Fixes #4335: Create Stratos static web site with better documentation (#4446) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * * Review comments fix * Fix broken link to an image in frontend extensions doc * Final tweaks Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Richard Cox Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Richard Cox * Fix and update customization docs (#4478) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * Apply new theming process to acme example * First pass at customization docs, most of themeing done * Final pass at customization docs * Merge fixes Co-authored-by: Veerapuram Varadhan Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update base README, point to website (#4488) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * Apply new theming process to acme example * First pass at customization docs, most of themeing done * Final pass at customization docs * Update base README, point to website Co-authored-by: Veerapuram Varadhan Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update developer docs (#4489) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * Apply new theming process to acme example * First pass at customization docs, most of themeing done * Final pass at customization docs * Update base README, point to website * Update developer docs Still need an overhall, but updated to remove incorrect data and apply website context Co-authored-by: Veerapuram Varadhan Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix docs (#4497) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * Apply new theming process to acme example * First pass at customization docs, most of themeing done * Final pass at customization docs * Update base README, point to website * Update developer docs Still need an overhall, but updated to remove incorrect data and apply website context * Fix PR template Co-authored-by: Veerapuram Varadhan Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Ensure images dependent on techPreview flag are included in imagelist.txt (#4500) * Improve clean-symlinks script (#4509) * Add support for including breaking changes in the changelog * Improve clean-symlinks script * Update changelog.sh * Update changelog.sh * Remove trailing line * Remove tailing line * Change nginx ciphers and make configurable via helm chart values (#4507) * Change nginx ciphers and make configurable via helm chart values * Add support for including breaking changes in the changelog * Improve clean-symlinks script * Update changelog.sh * Update changelog.sh * Remove trailing line * Remove tailing line * Fix bug with cert path patching * Fig bug where protocols not patched * Update version to 4.0.1, add change log (#4514) * Update version to 4.0.1, add change log * Update package-lock * Update package-lock * Update README.md with SUSE specific info * Remove `publish-docs:` github action Co-authored-by: Veerapuram Varadhan Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Veerapuram Varadhan --- .cfignore | 1 + .codeclimate.yml | 3 +- .github/workflows/documentation.yml | 28 + .gitignore | 4 + CHANGELOG.md | 17 + CONTRIBUTING.md | 10 +- README.md | 62 +- build/clean-symlinks.js | 18 +- deploy/Dockerfile.ui | 2 +- deploy/README.md | 8 - deploy/cloud-foundry/README.md | 314 - deploy/containers/nginx/conf/nginx.dev.conf | 2 +- deploy/containers/nginx/conf/nginx.k8s.conf | 4 +- deploy/containers/nginx/run-nginx.sh | 22 +- deploy/kubernetes/console/README.md | 7 +- .../console/templates/deployment.yaml | 4 + deploy/kubernetes/console/values.yaml | 4 + deploy/kubernetes/imagelist.values.yaml | 6 +- docs/contributing.md | 85 - docs/customizing.md | 179 - docs/developers-guide-env-tech.md | 211 - docs/developers-guide.md | 294 - docs/extensions.md | 284 - docs/i18n.md | 3 - docs/images/extensions/tab-example.png | Bin 12163 -> 0 bytes docs/index.html | 1 - docs/issue_template.md | 9 +- docs/overview.md | 64 - docs/roadmap.md | 39 - index.yaml | 76 - package-lock.json | 2115 +-- package.json | 2 +- .../packages/example-theme/_index.scss | 56 +- .../example-theme/assets/core/nav-logo.png | Bin 0 -> 18891 bytes .../packages/example-theme/package.json | 2 +- .../packages/example-theme/sass/custom.scss | 2 - .../sass/custom/acme-colors.scss | 16 + .../example-theme/sass/custom/acme.scss | 70 +- website/.gitignore | 23 + website/README.md | 40 + website/babel.config.js | 3 + website/deploy.sh | 101 + .../docs/advanced}/bosh-metrics.md | 5 +- .../docs/advanced}/invite-user-guide.md | 15 +- {docs => website/docs/advanced}/sso.md | 5 +- {docs => website/docs/deploy}/access.md | 6 +- .../docs/deploy/all-in-one.md | 20 +- .../cloud-foundry/cf-troubleshooting.md | 157 + .../deploy/cloud-foundry/cloud-foundry.md | 162 + .../docs/deploy/cloud-foundry/db-migration.md | 8 +- .../docs/deploy/kubernetes.md | 8 +- website/docs/deploy/kubernetes/install.md | 413 + website/docs/deploy/overview.md | 44 + .../docs/deploy}/troubleshooting.md | 8 +- website/docs/developer/backend.md | 124 + website/docs/developer/contributing.md | 162 + .../developer}/developers-guide-e2e-tests.md | 22 +- .../developer/developers-guide-env-tech.md | 103 + website/docs/developer/frontend-tests.md | 73 + website/docs/developer/frontend.md | 73 + website/docs/developer/introduction.md | 37 + .../docs/extensions/backend.md | 9 +- website/docs/extensions/frontend.md | 459 + website/docs/extensions/introduction.md | 61 + website/docs/extensions/theming.md | 258 + website/docs/extensions/v4-migration.md | 46 + website/docs/introduction.md | 63 + website/docs/license.md | 184 + website/docs/overview.md | 29 + {docs => website/docs}/status_updates.md | 0 website/docs/talks.md | 13 + website/docusaurus.config.js | 96 + website/package-lock.json | 13192 ++++++++++++++++ website/package.json | 31 + website/sidebars.js | 54 + website/src/css/custom.css | 191 + website/src/pages/index.js | 176 + website/src/pages/styles.module.css | 37 + website/static/.nojekyll | 0 .../static}/images/Browserstack-logo.svg | 0 .../images/extensions/app-tab-example.png | Bin .../extensions/appwall-action-example.png | Bin .../static}/images/high-level-arch.png | Bin .../images/screenshots/app-summary.png | Bin website/static/img/cloudfoundry.png | Bin 0 -> 5301 bytes website/static/img/deploy.svg | 1 + website/static/img/easy.svg | 1 + website/static/img/extend.svg | 1 + website/static/img/favicon.ico | Bin 0 -> 15086 bytes website/static/img/kubernetes.svg | 84 + website/static/img/logo.png | Bin 0 -> 93628 bytes website/static/img/logo.svg | 1 + website/static/img/multi-cluster.svg | 3 + website/static/img/open-source.svg | 1 + website/static/img/screens/cf-app.png | Bin 0 -> 67519 bytes website/static/img/screens/endpoints.png | Bin 0 -> 33521 bytes website/static/img/screens/kube-graph.png | Bin 0 -> 63442 bytes 97 files changed, 17073 insertions(+), 3554 deletions(-) create mode 100644 .github/workflows/documentation.yml delete mode 100644 deploy/README.md delete mode 100644 deploy/cloud-foundry/README.md delete mode 100644 docs/contributing.md delete mode 100644 docs/customizing.md delete mode 100644 docs/developers-guide-env-tech.md delete mode 100644 docs/developers-guide.md delete mode 100644 docs/extensions.md delete mode 100644 docs/i18n.md delete mode 100644 docs/images/extensions/tab-example.png delete mode 100644 docs/index.html delete mode 100644 docs/overview.md delete mode 100644 docs/roadmap.md delete mode 100755 index.yaml create mode 100644 src/frontend/packages/example-theme/assets/core/nav-logo.png create mode 100644 website/.gitignore create mode 100644 website/README.md create mode 100644 website/babel.config.js create mode 100755 website/deploy.sh rename {docs => website/docs/advanced}/bosh-metrics.md (98%) rename {docs => website/docs/advanced}/invite-user-guide.md (92%) rename {docs => website/docs/advanced}/sso.md (96%) rename {docs => website/docs/deploy}/access.md (97%) rename deploy/all-in-one/README.md => website/docs/deploy/all-in-one.md (91%) create mode 100644 website/docs/deploy/cloud-foundry/cf-troubleshooting.md create mode 100644 website/docs/deploy/cloud-foundry/cloud-foundry.md rename deploy/cloud-foundry/db-migration/README.md => website/docs/deploy/cloud-foundry/db-migration.md (90%) rename deploy/kubernetes/README.md => website/docs/deploy/kubernetes.md (80%) create mode 100644 website/docs/deploy/kubernetes/install.md create mode 100644 website/docs/deploy/overview.md rename {docs => website/docs/deploy}/troubleshooting.md (78%) create mode 100644 website/docs/developer/backend.md create mode 100644 website/docs/developer/contributing.md rename {docs => website/docs/developer}/developers-guide-e2e-tests.md (87%) create mode 100644 website/docs/developer/developers-guide-env-tech.md create mode 100644 website/docs/developer/frontend-tests.md create mode 100644 website/docs/developer/frontend.md create mode 100644 website/docs/developer/introduction.md rename docs/backend-plugins.md => website/docs/extensions/backend.md (93%) create mode 100644 website/docs/extensions/frontend.md create mode 100644 website/docs/extensions/introduction.md create mode 100644 website/docs/extensions/theming.md create mode 100644 website/docs/extensions/v4-migration.md create mode 100644 website/docs/introduction.md create mode 100644 website/docs/license.md create mode 100644 website/docs/overview.md rename {docs => website/docs}/status_updates.md (100%) create mode 100644 website/docs/talks.md create mode 100644 website/docusaurus.config.js create mode 100644 website/package-lock.json create mode 100644 website/package.json create mode 100644 website/sidebars.js create mode 100644 website/src/css/custom.css create mode 100644 website/src/pages/index.js create mode 100644 website/src/pages/styles.module.css create mode 100644 website/static/.nojekyll rename {docs => website/static}/images/Browserstack-logo.svg (100%) rename {docs => website/static}/images/extensions/app-tab-example.png (100%) rename {docs => website/static}/images/extensions/appwall-action-example.png (100%) rename {docs => website/static}/images/high-level-arch.png (100%) rename {docs => website/static}/images/screenshots/app-summary.png (100%) create mode 100644 website/static/img/cloudfoundry.png create mode 100644 website/static/img/deploy.svg create mode 100644 website/static/img/easy.svg create mode 100644 website/static/img/extend.svg create mode 100644 website/static/img/favicon.ico create mode 100644 website/static/img/kubernetes.svg create mode 100644 website/static/img/logo.png create mode 100644 website/static/img/logo.svg create mode 100644 website/static/img/multi-cluster.svg create mode 100644 website/static/img/open-source.svg create mode 100644 website/static/img/screens/cf-app.png create mode 100644 website/static/img/screens/endpoints.png create mode 100644 website/static/img/screens/kube-graph.png diff --git a/.cfignore b/.cfignore index 21c16f20e7..92be10a235 100644 --- a/.cfignore +++ b/.cfignore @@ -21,3 +21,4 @@ deploy/uaa/ docs/ build/dev_config.json e2e-reports/ +website/ diff --git a/.codeclimate.yml b/.codeclimate.yml index 801ada6c4e..5650724125 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -28,4 +28,5 @@ exclude_patterns: - "**/vendor/" - "**/*.d.ts" - "**/__vendor/" -- "**/*_test.go" \ No newline at end of file +- "**/*_test.go" +- "website/" \ No newline at end of file diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 0000000000..4b78eefdc3 --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,28 @@ +name: documentation + +on: + pull_request: + branches: [master] + push: + branches: [master] + +jobs: + build-docs: + if: github.event_name != 'push' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + with: + node-version: '12.x' + - name: Test Build + run: | + cd website + if [ -e yarn.lock ]; then + yarn install --frozen-lockfile + elif [ -e package-lock.json ]; then + npm ci + else + npm i + fi + npm run build \ No newline at end of file diff --git a/.gitignore b/.gitignore index fd305dfd93..730c756be1 100644 --- a/.gitignore +++ b/.gitignore @@ -122,3 +122,7 @@ stratos-frontend-prebuild.zip src/frontend/assets src/frontend/sass go-vendor-*.tgz + +website/build +website/site-dist +website/.docusaurus diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a780c8ba0..23026997c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Change Log +## 4.0.1 + +[Full Changelog](https://github.com/SUSE/stratos/compare/4.0.0...4.0.1) + +This release contains a number of fixes and improvements: + +**Improvements:** + +- Helm Deployment: Allow nginx protocols and ciphers to be configured via values [\#4512](https://github.com/cloudfoundry/stratos/issues/4512) + +**Fixes:** + +- npm install can sometimes fail when symlinks from a previous Stratos version exist [\#4513](https://github.com/cloudfoundry/stratos/issues/4513) +- Helm Deployment: Default nginx ciphers are too restrictive [\#4503](https://github.com/cloudfoundry/stratos/issues/4503) + + + ## 4.0.0 [Full Changelog](https://github.com/SUSE/stratos/compare/3.2.1...4.0.0) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5da3d8b31d..8e7ed009fc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,12 +1,12 @@ -# Contributing to Stratos UI +# Contributing to Stratos -Stratos UI is an open project and welcomes contributions. These guidelines are provided to help you understand how the project works and to make contributing smooth and fun for everybody involved. +Stratos is an open project and welcomes contributions. These guidelines are provided to help you understand how the project works and to make contributing smooth and fun for everybody involved. There are two main forms of contribution: reporting issues and performing code changes. ## Reporting Issues -If you find a problem with Stratos UI, report it using [GitHub issues](https://github.com/suse/stratos/issues/new). +If you find a problem with Stratos, report it using [GitHub issues](https://github.com/suse/stratos/issues/new). Before reporting a new issue, please take a moment to check whether it has already been reported [here](https://github.com/suse/stratos/issues). If this is the case, please: @@ -29,7 +29,7 @@ This information will help to determine the cause and prepare a fix as fast as p ## Code Changes Code contributions come in various forms and sizes, from simple bug fixes to implementation -of new features. Before making any non-trivial change, get in touch with the Stratos UI developers first. This can prevent wasted effort later. +of new features. Before making any non-trivial change, get in touch with the Stratos developers first. This can prevent wasted effort later. To send your code change, use GitHub pull requests. The workflow is as follows: @@ -43,7 +43,7 @@ To send your code change, use GitHub pull requests. The workflow is as follows: 1. Publish the branch and create a pull request. - 1. Stratos UI developers will review your change and possibly point out issues. + 1. Stratos developers will review your change and possibly point out issues. Adapt the code under their guidance until all issues are resolved. 1. Finally, the pull request will get merged or rejected. diff --git a/README.md b/README.md index d00d11eb12..528627e08f 100644 --- a/README.md +++ b/README.md @@ -9,64 +9,26 @@ Stratos is an Open Source Web-based UI (Console) for managing Cloud Foundry. It allows users and administrators to both manage applications running in the Cloud Foundry cluster and perform cluster management tasks. -If you are looking for a version of Stratos, you can find .. -- V1 in the [v1-master](https://github.com/suse/stratos/tree/v1-master) branch. -- V2 in the [v2-master](https://github.com/suse/stratos/tree/v2-master) branch. +![Stratos Application view](website/static/images/screenshots/app-summary.png) -![Stratos Application view](docs/images/screenshots/app-summary.png) +Please visit our new [documentation site](https://stratos.app/). There you can discover -## Quick Start +1. Our [introduction](https://stratos.app/docs/), including quick start, contributing and troubleshooting guides. +1. How to [deploy](https://stratos.app/docs/deploy/overview) Stratos in a number of environments. + 1. [Cloud Foundry](https://stratos.app/docs/deploy/cloud-foundry/cloud-foundry), as an application. + 1. [Kubernetes](https://stratos.app/docs/deploy/kubernetes), using a Helm chart. + 1. [Docker](https://stratos.app/docs/deploy/all-in-one), as a single container deploying all components. +1. Configuring advanced features such a [Single Sign On](https://stratos.app/docs/advanced/sso) and Cloud Foundry '[invite to org](https://stratos.app/docs/advanced/invite-user-guide)'. +1. Guides for [developers](https://stratos.app/docs/developer/introduction). +1. How to [extend](https://stratos.app/docs/extensions/introduction) Stratos [functionality](https://stratos.app/docs/extensions/frontend) and apply a custom [theme](https://stratos.app/docs/extensions/theming). -To get started quickly, we recommend following the steps to deploy the Stratos Console as a Cloud Foundry Application - see [here](deploy/cloud-foundry). - -If you have [docker](https://www.docker.com/community-edition) installed, you can quickly deploy Stratos using the all-in-one container: -``` -$ docker run -p 4443:443 splatform/stratos:latest -``` - -Once that has finished, you can then access Stratos by visiting https://localhost:4443. - -## Deploying Stratos - -Stratos can be deployed in the following environments: - -1. Cloud Foundry, as an application. See [guide](deploy/cloud-foundry) -2. Kubernetes, using a Helm chart. See [guide](deploy/kubernetes) -3. Docker, single container deploying all components. See [guide](deploy/all-in-one) - -## Troubleshooting - -Please see our [Troubleshooting](docs/troubleshooting) page. - -## Further Reading - -Take a look at the [Feature Set](docs/features.md) for details on the feature set that Stratos provides. - -Get an [Overview](docs/overview.md) of Stratos, its components and the different ways in which it can be deployed. - -Take a look at the [Development Roadmap](docs/roadmap.md) to see where we are heading. We update our status page each week to summarize what we are working on - see the [Status Page](docs/status_updates.md). - -Browse through features and issues in the project's [issues](https://github.com/suse/stratos/issues) page. - -What kind of code is in Stratos? We've integrated [Code Climate](https://codeclimate.com) for some code quality and maintainability metrics. Take a stroll around the [project page](https://codeclimate.com/github/SUSE/stratos) - -## Contributing - -We very much welcome developers who would like to get involved and contribute to the development of the Stratos project. Please refer to the [Contributing guide](CONTRIBUTING.md) for more information. - -For information to help getting started with development, please read the [Developer's Guide](docs/developers-guide.md). - -## Support and feedback - -We have a channel (#stratos) on the Cloud Foundy Slack where you can ask questions, get support or give us feedback. We'd love to hear from you if you are using Stratos. - -You can join the Cloud Foundry Slack here - https://slack.cloudfoundry.org/ - and then join the #stratos channel. +> For more SUSE specific information, specifically regarding Stratos Kubernetes and Helm functionality, please see our `/docs` folder. ## Acknowledgements Tested with Browserstack -Browserstack +Browserstack ## License diff --git a/build/clean-symlinks.js b/build/clean-symlinks.js index 9ae0c0aed1..6ba161c0d1 100644 --- a/build/clean-symlinks.js +++ b/build/clean-symlinks.js @@ -7,7 +7,7 @@ const path = require('path'); const fs = require('fs'); // __dirname is the folder where build.js is located -const STRATOS_DIR= path.resolve(__dirname, '..'); +const STRATOS_DIR = path.resolve(__dirname, '..'); function processFile(filepath) { if (fs.existsSync(filepath)) { @@ -23,14 +23,18 @@ function processFolder(dir) { if (!fs.existsSync(dir)) { return } - fs.readdirSync(dir).forEach( f => { + fs.readdirSync(dir).forEach(f => { let dirPath = path.join(dir, f); - const realPath = fs.realpathSync(dirPath); - const stats = fs.lstatSync(realPath); - if (stats.isDirectory()) { - processFolder(dirPath); + if (!fs.existsSync(dirPath)) { + fs.unlinkSync(dirPath); } else { - processFile(dirPath); + const realPath = fs.realpathSync(dirPath); + const stats = fs.lstatSync(realPath); + if (stats.isDirectory()) { + processFolder(dirPath); + } else { + processFile(dirPath); + } } }); }; diff --git a/deploy/Dockerfile.ui b/deploy/Dockerfile.ui index 19ee495543..b2155de02e 100644 --- a/deploy/Dockerfile.ui +++ b/deploy/Dockerfile.ui @@ -16,7 +16,7 @@ RUN npm install && \ FROM splatform/stratos-nginx-base:leap15_1 as prod-build RUN mkdir -p /usr/share/doc/suse COPY deploy/containers/nginx/LICENSE.txt /usr/share/doc/suse/LICENSE.txt -COPY deploy/containers/nginx/conf/nginx.k8s.conf /etc/nginx/nginx.conf +COPY deploy/containers/nginx/conf/nginx.k8s.conf /etc/nginx/nginx.conf.tmpl COPY --from=base-build /usr/dist /usr/share/nginx/html COPY deploy/containers/nginx/run-nginx.sh/ /run-nginx.sh EXPOSE 80 443 diff --git a/deploy/README.md b/deploy/README.md deleted file mode 100644 index cc7e9ed093..0000000000 --- a/deploy/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Deploying Stratos - -Stratos can be deployed in the following environments: - -1. Cloud Foundry, as an application. See [guide](cloud-foundry) -2. Kubernetes, using a Helm chart. See [guide](kubernetes) -3. Docker, single container deploying all components. See [guide](all-in-one) - diff --git a/deploy/cloud-foundry/README.md b/deploy/cloud-foundry/README.md deleted file mode 100644 index 424faa2a48..0000000000 --- a/deploy/cloud-foundry/README.md +++ /dev/null @@ -1,314 +0,0 @@ -# Deploying as a Cloud Foundry Application - -## Deployment Steps - -The quickest way to install Stratos is to deploy it as a Cloud Foundry application. - -You can do it in two ways: - -1. [Deploy Stratos from source](#Deploy-Stratos-from-source) -1. [Deploy Stratos from docker image](#Deploy-Stratos-from-docker-image) - -You will then be able to open a web browser and navigate to the console URL: - -`https://console.` - -Where `` is the default domain configured for your Cloud Foundry cluster. - -To login use the following credentials detailed [here](../../docs/access.md). - -If you run into issues, please refer to the [Troubleshooting Guide](#troubleshooting) below. - -> The console will pre-configure the host Cloud Foundry endpoint. No other CF instance should be registered unless the instructions in - the section [Associate Cloud Foundry database service](#Associate-Cloud-Foundry-database-service) are followed. - All other deployment methods (helm, docker all-in-one, etc) allow the registration of multiple CF instances by default. - -Note: - -1. You need the cf CLI command line tool installed and available on the path. -1. You need to have configured the cf cli to point to your Cloud Foundry cluster, to be authenticated with your credentials and to be targeted at the organization and space where you want the console application be created. -1. You may need to configure Application Security Groups on your Cloud Foundry Cluster in order that Stratos can communicate with the Cloud Foundry API. See [below](#application-security-groups) for more information. -1. The Stratos Console will automatically detect the API endpoint for your Cloud Foundry. To do so, it relies on the `cf_api_url` value inside the `VCAP_APPLICATION` environment variable. If this is not provided by your Cloud Foundry platform, then you must manually update the application manifest as described [below](#console-fails-to-start). - -### Running Stratos in Production Environments - -Please be aware of the following when running Stratos in a production environment: - -#### Configure a Session Store Secret - -Stratos uses a Session Store Secret to protect the user session cookie. We recommend that you set your own value for this secret - choosing an alphanumeric string of your choice. - -You can configure this secret by editing the application manifest and adding to the `env` section, e.g. - -``` -applications: -- name: console - ... memory, disk settings here - env: - SESSION_STORE_SECRET: -``` - -#### Pre-configure UAA client used for user invites - -> You can skip this step and configure any CFs invite clients via the Stratos UI. - - To set the UAA client for user invites, supply the client id and client secret as environment variables as shown below: - - ``` - INVITE_USER_CLIENT_ID= - INVITE_USER_CLIENT_SECRET= - ``` - -This will set the the UAA client and UAA secret used to invite users for the default CF only. - -See the [invite users guide](../../docs/invite-user-guide.md) for more information about user invites in Stratos. - -#### Use of the Default Embedded SQLite Database - -We do not recommend deploying Stratos to a production environment using the default embedded SQLite Database. Instead we recommend creating -and binding a database service instance to Stratos - for more information see [here](db-migration/README.md). - -### Deploy Stratos from source - -To do so, `clone` the **stratos** repository, `cd` into the newly cloned repository and `push` to Cloud Foundry. This can be done with: - -``` -git clone https://github.com/suse/stratos -cd stratos -git checkout tags/stable -b stable -./build/store-git-metadata.sh -cf push -``` - -If the cf push exceeds the time allowed see the instructions [here](#Pre-building-the-UI) - -#### Pre-building the UI - -Due to the memory usage of the Angular compiler (see below), when deployed to Cloud Foundry via `cf push`, Stratos does not use AOT (Ahead-of-Time) compilation. - -If you wish to enable AOT or reduce the push time, you can pre-build the UI before pushing. - -This can be done with: - -``` -git clone https://github.com/suse/stratos -cd stratos -npm install -npm run prebuild-ui -cf push -``` - -You will need a recent version of Node installed locally to do this. - -The `prebuild-ui` npm script performs a build of the front-end UI and then zips up the resulting folder into a package named `stratos-frontend-prebuild.zip`. The Stratos buildpack will unpack this zip file and use its contents instead of building the UI during staging, when this file is present. - - -#### Memory Usage - -The Stratos Cloud Foundry `manifest.yml` states that the application requires -`1512MB` of memory. This is required during the build process of the -application since building an angular2 app is a memory intensive process. The -memory limit can be scaled down after the app has been pushed, using the cf CLI. - -### Deploy Stratos from docker image - -Deploy Stratos using the [`splatform/stratos`](https://hub.docker.com/r/splatform/stratos) docker image - -> **NOTE:** Your Cloud Foundry must have docker support [enabled](https://docs.cloudfoundry.org/adminguide/docker.html#enable). - -``` -cf push console -o splatform/stratos:stable -m 128M -k 384M -``` -> Note: You can replace `console` in the command above with a name of your choice for the application - -Alternatively cf push using a manifest - -- download [manifest-docker.yml](../../manifest-docker.yml) or create your own manifest file: - ```yaml - applications: - - name: console - docker: - image: splatform/stratos:stable - instances: 1 - memory: 128M - disk_quota: 384M - ``` -- now, you can simply push it to Cloud Foundry: - ``` - cf push -f manifest-docker.yml - ``` - -## Associate Cloud Foundry database service -Follow instructions [here](db-migration/README.md). - -## Use SSO Login - -By default Stratos will present its own login UI and only supports username and password authentication with your UAA. You can configure Stratos to use UAA's login UI by specifying the the `SSO_LOGIN` environment variable in the manifest, for example: - -``` -applications: -- name: console - ... memory, disk settings here - env: - SSO_LOGIN: true -``` - -When SSO Login is enabled, Stratos will also auto-connect to the Cloud Foundry it is deployed in using the token obtained during the SSO Login flow. - -For more information - see [Single-Sign On](../../docs/sso.md). - -## Troubleshooting - -### Creating logs for recent deployments -To create a log file of the push -``` -cf push | tee cfpush.log -``` - -To create a log file of recent console output -``` -cf logs console --recent | tee cfconsole.log -``` ->**NOTE** If the name of the application has been changed from `console` in the manifest file please also change the name in the logs statement - -### Turning on backend debugging logs - -The `LOG_LEVEL` environment variable controls the backend logs - -``` -cf set-env console LOG_LEVEL debug -cf restart console -cf logs console -``` - -would output more debugging output such as - -``` - 2018-10-24T14:47:36.91+0200 [RTR/1] OUT console.my.domain - [2018-10-24T12:47:36.850+0000] "GET /pp/v1/-o1F0L956QhAIK7R56Uc1lMh5L4/apps/3ddc0bc6-a645-4449-9d1b-6fe86146cf61/ssh/0 HTTP/1.1" 500 0 0 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0" "10.228.194.8:42182" "192.168.35.91:61044" x_forwarded_for:"10.228.194.8" x_forwarded_proto:"https" vcap_request_id:"182dddeb-d877-4d58-45f7-0bd886d1caf6" response_time:0.066925325 app_id:"0ba408ef-d0e6-4ab8-96bb-0bc078b8d8fb" app_index:"0" x_b3_traceid:"d166622a0d612fea" x_b3_spanid:"d166622a0d612fea" x_b3_parentspanid:"-" - 2018-10-24T14:47:36.91+0200 [RTR/1] OUT - 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] sessionCleanupMiddleware - 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] errorLoggingMiddleware - 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT INFO[Wed Oct 24 12:47:36 UTC 2018] Not redirecting this request - 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] getSession - 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] getSessionValue - 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] getSession - 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] setStaticContentHeadersMiddleware - 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] urlCheckMiddleware - 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] sessionMiddleware - 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] getSessionValue - 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] getSession - 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] xsrfMiddleware - 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] GetCNSIRecord - 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] Find - 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] decryptToken - 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] Decrypt - [...] -``` - -### Application Security Groups - -If you have problems when deploying Stratos UI as a Cloud Foundry application, check that the Application Security Group you have will allow Stratos to communicate with the Cloud Foundry API. - -For information on the default ASGs, see [here](https://docs.cloudfoundry.org/concepts/asg.html#default-asg). - -To configure a new ASG for the organization and space that are using Stratos, first create a new ASG definition, for example: - -``` -[ - { - "destination":"0.0.0.0-255.255.255.255", - "protocol":"all" - } -] -``` - -Save this to a file, e.g. `my-asg.json`. - -> Note: this allows example all network traffic on all IP ranges - we don't recommend using this. - -Unbind the existing ASG for you organization (`ORG`) and space (`SPACE`) with: - -``` -cf unbind-security-group public_networks ORG SPACE -``` - -Create a new ASG using the definition you saved to a file and give it a name, with: - -``` -cf create-security-group NAME my-asg.json -``` - -Bind this ASG to your `ORG` and `SPACE` with: - -``` -cf bind-security-group NAME ORG SPACE -``` - - -### Console fails to start - -The Stratos Console will automatically detect the API endpoint for your Cloud Foundry. To do so, it relies on the `cf_api` value inside the `VCAP_APPLICATION` environment variable. -To check if the variable is present, use the CF CLI to list environment variables, and inspect the `VCAP_APPLICATION` variable under `System-Provided`. - -``` -$ cf env console -Getting env variables for app console in org SUSE / space dev as admin... -OK - -System-Provided: - - - { - "VCAP_APPLICATION": { - "application_id": ..., - "application_name": "console", - "application_uris": ... - "application_version": ..., - "cf_api": "http://api.cf-dev.io", - ... - } - - No user-defined env variables have been set - ... -``` - -If the `cf_api` environment variable is absent then set the `CF_API_URL` variable. See the following _Setting the `CF_API_URL` env variable in the manifest_ section. - - -However, if the `cf_api` environment variable is present, and an HTTP address is specified, it is possible that insecure traffic may be blocked. See the following _Setting the `CF_API_FORCE_SECURE` env variable in the manifest_ section. - - -#### Setting the `CF_API_URL` env variable in the manifest - -To specify the Cloud Foundry API endpoint, add the `CF_API_URL` variable to the manifest, for example: - -``` -applications: -- name: console - memory: 256M - disk_quota: 256M - host: console - timeout: 180 - buildpack: https://github.com/cloudfoundry/stratos-buildpack - health-check-type: port - env: - CF_API_URL: https://<>> -``` - -#### Setting the `CF_API_FORCE_SECURE` env variable in the manifest - -To force the console to use secured communication with the Cloud Foundry API endpoint (HTTPS rather than HTTP), specify the `CF_API_FORCE_SECURE` environment in the manifest, for example: - -``` -applications: -- name: console - memory: 256M - disk_quota: 256M - host: console - timeout: 180 - buildpack: https://github.com/cloudfoundry/stratos-buildpack - health-check-type: port - env: - CF_API_FORCE_SECURE: true -``` - diff --git a/deploy/containers/nginx/conf/nginx.dev.conf b/deploy/containers/nginx/conf/nginx.dev.conf index 23719b11b9..8f2e5f00cf 100644 --- a/deploy/containers/nginx/conf/nginx.dev.conf +++ b/deploy/containers/nginx/conf/nginx.dev.conf @@ -48,7 +48,7 @@ http { ssl_certificate /etc/secrets/server.crt; ssl_certificate_key /etc/secrets/server.key; ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384; + ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; client_max_body_size 50M; diff --git a/deploy/containers/nginx/conf/nginx.k8s.conf b/deploy/containers/nginx/conf/nginx.k8s.conf index 620b3fd3e3..6b20bc69ab 100644 --- a/deploy/containers/nginx/conf/nginx.k8s.conf +++ b/deploy/containers/nginx/conf/nginx.k8s.conf @@ -47,8 +47,8 @@ http { ssl_certificate /CONSOLE_CERT_PATH/tls.crt; ssl_certificate_key /CONSOLE_CERT_PATH/tls.key; - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384; + ssl_protocols __PROTOCOLS__; + ssl_ciphers __CIPHERS__; ssl_prefer_server_ciphers on; client_max_body_size 50M; diff --git a/deploy/containers/nginx/run-nginx.sh b/deploy/containers/nginx/run-nginx.sh index 295b66688e..2c47cf12b2 100755 --- a/deploy/containers/nginx/run-nginx.sh +++ b/deploy/containers/nginx/run-nginx.sh @@ -5,6 +5,9 @@ echo "Stratos UI Container (nginx)" echo "============================================" echo "" +# Copy the template config to the /etc/nging/nginx.conf +cp /etc/nginx/nginx.conf.tmpl /etc/nginx/nginx.conf + sed -i -e 's@CONSOLE_CERT_PATH@'"${CONSOLE_CERT_PATH}"'@g' /etc/nginx/nginx.conf echo "Checking for certificate at ${CONSOLE_CERT_PATH} ..." @@ -16,5 +19,22 @@ do sleep 1; done -echo "TLS certificate detected ... starting nginx." +echo "TLS certificate detected OK" + +# Patch the config file with the desired ciphers and protocols +echo "Setting nginx ciphers and protocols" + +DEFAULT_PROTOCOLS="TLSv1.2 TLSv1.3" +DEFAULT_CIPHERS="HIGH:!aNULL:!MD5" + +NGINX_PROTOCOLS=${SSL_PROTOCOLS:-$DEFAULT_PROTOCOLS} +NGINX_CIPHERS=${SSL_CIPHERS:-$DEFAULT_CIPHERS} + +echo "SSL Protocols : $NGINX_PROTOCOLS" +echo "SSL Ciphers : $NGINX_CIPHERS" + +sed -i -e 's/__PROTOCOLS__/'"${NGINX_PROTOCOLS}"'/g' /etc/nginx/nginx.conf +sed -i -e 's/__CIPHERS__/'"${NGINX_CIPHERS}"'/g' /etc/nginx/nginx.conf + +echo "Starting nginx ..." nginx -g "daemon off;" diff --git a/deploy/kubernetes/console/README.md b/deploy/kubernetes/console/README.md index 3e156e249e..ef228ed1d7 100644 --- a/deploy/kubernetes/console/README.md +++ b/deploy/kubernetes/console/README.md @@ -22,7 +22,7 @@ Check the repository was successfully added by searching for the `console`, for ``` helm search repo console NAME CHART VERSION APP VERSION DESCRIPTION -stratos/console 3.2.0 3.2.0 A Helm chart for deploying Stratos UI Console +stratos/console 4.0.1 4.0.1 A Helm chart for deploying Stratos UI Console ``` > Note: Version numbers will depend on the version of Stratos available from the Helm repository @@ -115,6 +115,8 @@ The following table lists the configurable parameters of the Stratos Helm chart |console.service.extraLabels|Additional labels to be added to all service resources|| |console.service.ingress.annotations|Annotations to be added to the ingress resource|| |console.service.ingress.extraLabels|Additional labels to be added to the ingress resource|| +|console.sslProtocols|SSL Protocols to use for the nginx configuration|TLSv1.2 TLSv1.3| +|console.sslCiphers|SSL Ciphers to use for the nginx configuration|HIGH:!aNULL:!MD5| |console.nodeSelector|Node selectors to use for the console Pod|| |mariadb.nodeSelector|Node selectors to use for the database Pod|| |configInit.nodeSelector|Node selectors to use for the configuration Pod|| @@ -256,7 +258,7 @@ You will also need to specify: - `console.mariadb.port` as the port of the external MariaDB database server (defaults to 3306) - `console.mariadb.tls` as the TLS mode (default is `false,` use `true` for a TLS connection to the database server) - `console.mariadb.database` as the name of the database -- `console.mariadb.user` as the username to connect to the database server +- `console.mariadb.user` as the username to connect to the database server - `console.mariadb.userPassword` as the password to connect to the database server When using an external database server, Stratos expects that you have: @@ -407,4 +409,3 @@ kubectl create -f storageclass.yaml ``` See [Storage Class documentation](https://kubernetes.io/docs/tasks/administer-cluster/change-default-storage-class/) for more information. - diff --git a/deploy/kubernetes/console/templates/deployment.yaml b/deploy/kubernetes/console/templates/deployment.yaml index 05a92ef3e2..6fd4fd2985 100644 --- a/deploy/kubernetes/console/templates/deployment.yaml +++ b/deploy/kubernetes/console/templates/deployment.yaml @@ -69,6 +69,10 @@ spec: value: "{{.Values.consoleVersion}}:{{ .Release.Revision }}" - name: CONSOLE_CERT_PATH value: "/{{ .Release.Name }}-cert-volume" + - name: SSL_PROTOCOLS + value: "{{ .Values.console.sslProtocols }}" + - name: SSL_CIPHERS + value: "{{ .Values.console.sslCiphers }}" volumeMounts: - mountPath: "/{{ .Release.Name }}-cert-volume" name: "{{ .Release.Name }}-cert-volume" diff --git a/deploy/kubernetes/console/values.yaml b/deploy/kubernetes/console/values.yaml index d2ee436aca..5ac7dc060c 100644 --- a/deploy/kubernetes/console/values.yaml +++ b/deploy/kubernetes/console/values.yaml @@ -118,6 +118,10 @@ console: # Do not use a persistent volume for analysis reports reportsVolumeDisabled: false + + # ssl protocols and ciphers overrides - leave empty for defaults + sslProtocols: + sslCiphers: images: console: stratos-console diff --git a/deploy/kubernetes/imagelist.values.yaml b/deploy/kubernetes/imagelist.values.yaml index 6c5b353e3c..1134c53c59 100644 --- a/deploy/kubernetes/imagelist.values.yaml +++ b/deploy/kubernetes/imagelist.values.yaml @@ -5,4 +5,8 @@ # Use empty docker repository so names are consistent kube: registry: - hostname: \ No newline at end of file + hostname: + +# Enable tech preview to include analyzer image +console: + techPreview: true \ No newline at end of file diff --git a/docs/contributing.md b/docs/contributing.md deleted file mode 100644 index 6e379dd8ac..0000000000 --- a/docs/contributing.md +++ /dev/null @@ -1,85 +0,0 @@ -# Contributing to Stratos UI - -## Reporting issues - -Before reporting an issue, please check whether it has already been reported -[here](https://github.com/SUSE/stratos/issues). If this is the case, please: - -- Read all the comments to confirm that it's the same issue you're having. -- Refrain from adding "same thing here" or "+1" comments. Just hit the - "subscribe" button to get notifications for this issue. -- Add a comment only if you can provide helpful information that has not been - provided in the discussion yet. - -If you want to report a **new issue**, please ensure you give as much detail -as possible about your setup/environment and provide sufficient steps -for the issue to be easily reproduced. - -## Check for assigned people - -We use Github Issues for submitting known issues (e.g. bugs, features, -etc.). Some issues will have someone assigned, meaning that there's already -someone that takes responsibility for fixing the issue. This is not done to -discourage contributions, rather to not step in the work that has already been -done by the assignee. If you want to work on a known issue with someone already -assigned to it, please contact the assignee first (e.g. by -mentioning the assignee in a new comment on the specific issue). This way you -can contribute with ideas, or even with code if the assignee decides that you -can step in. - -If you plan to work on a non assigned issue, please add a comment on the issue -to prevent duplicated work. - -## Sign your work - -The sign-off is a simple line at the end of the explanation for the patch. Your -signature certifies that you wrote the patch or otherwise have the right to pass -it on as an open-source patch. The rules are pretty simple: if you can certify -the below (from [developercertificate.org](http://developercertificate.org/)): - -``` -Developer Certificate of Origin -Version 1.1 - -Copyright (C) 2004, 2006 The Linux Foundation and its contributors. -660 York Street, Suite 102, -San Francisco, CA 94110 USA - -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. - -Developer's Certificate of Origin 1.1 - -By making a contribution to this project, I certify that: - -(a) The contribution was created in whole or in part by me and I - have the right to submit it under the open source license - indicated in the file; or - -(b) The contribution is based upon previous work that, to the best - of my knowledge, is covered under an appropriate open source - license and I have the right under that license to submit that - work with modifications, whether created in whole or in part - by me, under the same open source license (unless I am - permitted to submit under a different license), as indicated - in the file; or - -(c) The contribution was provided directly to me by some other - person who certified (a), (b) or (c) and I have not modified - it. - -(d) I understand and agree that this project and the contribution - are public and that a record of the contribution (including all - personal information I submit with it, including my sign-off) is - maintained indefinitely and may be redistributed consistent with - this project or the open source license(s) involved. -``` - -Then you just add a line to every git commit message: - - Signed-off-by: Joe Smith - -Use your real name (sorry, no pseudonyms or anonymous contributions.) - -If you set your `user.name` and `user.email` git configs, you can sign your -commit automatically with `git commit -s`. diff --git a/docs/customizing.md b/docs/customizing.md deleted file mode 100644 index f0defe7d76..0000000000 --- a/docs/customizing.md +++ /dev/null @@ -1,179 +0,0 @@ -# Customizing Stratos - -Stratos provides a mechanism for customization - the following customizations are currently supported: - -- Changing the theme colors -- Changing certain image assets (favorite icon, login background and logo) -- Overriding styles -- Adding new functionality -- Changing the initial loading indicator - -# Migrating to Stratos V4 Customization -In V4 there are breaking customization changes. These changes allow a much improved approach to extensions by opening the door to npm style plugins. -To aid in migrating we've provided these instructions. - -1) Before updating to the latest code... - 1) Run `npm run customize-reset` to remove all previously created sym links. - 2) Read through the customization documentation below to get a better understanding of the new process. -1) Update your codebase with the desired v4 code. -1) Run `npm install` (only required first time, this will ensure you have the required version of Angular). -1) Change directory to `./build/tools/v4-migration` and run the migration script `./migrate.sh`. - - This will copy your customizations from `custom-src` to a new Angular package `src/frontend/packages/custom_extensions`. -1) Check that the new package exports your custom module and if applicable your custom-routing module. - - The migrate script should do this in `src/frontend/packages/custom_extensions/src/public-api.ts`. -1) Check that your ts config file defines the public api file. - - `src/tsconfig.json` file's `compilerOptions/paths` section should contain something like `"@custom/extensions": ["frontend/packages/custom_extensions/src/public-api.ts"]`. -1) Check that your new package's package.json defines your custom module and if application custom-routing module. - - See `src/frontend/packages/custom_extensions/package.json` file's `stratos` section. - - Note your `routingModule` entry label should not have a preceding `_`. -1) Build Stratos in your usual way, for instance `npm run build`. - - It could be that this fails due to TypeScript import issues, if so go through these and fix. - - During build time the custom packages will be discovered and output, see section starting `Building with these extensions`. These should contain the modules your require. -1) Run Stratos your usual way. Ensure you can navigate to all your custom parts. -1) Once you are happy everything works as intended remove the old `./custom-src` directory and commit you changes. - -## Approach - -In order to customize Stratos, you will need to fork the Stratos GitHub repository and apply customizations in your fork. Our aim is to minimize any merge conflicts that might occur when re-basing your fork with the upstream Stratos repository. - - -Customizations are placed in angular packages in the folder named `src/frontend/packages`. In the future you will be able to host these packages in npm and bring them into Stratos in the usual npm dependency way. - -Each package should contain custom Stratos configuration in it's package.json pointing to the modules it will be required to import. - -stratos.yaml -custom theme -custom styles -custom assets - - -The Stratos approach to customization uses symbolic links. We maintain a default set of resources in the folder `src/misc/custom`. When you run `npm install` or when you explicitly run `npm run customize`, a gulp task (in the file `build/fe-build.js`) runs and creates symbolic links, linking the required files to their expected locations withing the `src` folder. - -If a required file exists in the `custom-src` folder location, the build script will link this file, otherwise, it will link the default resource from `src/misc/custom`. - -Normally, you do not need to run any scripts to apply customizations - they will be applied as part of a `postinstall` script that runs automatically when you do an `npm install`. You can manually run the following scripts if you update or change the customizations: - -- `npm run customize` - creates symbolic links for the required files, looking at the provided customizations and then falling back to default files - -- `npm run customize-default` - creates symbolic links for the required files, ignoring any provided customizations and using the default files - -- `npm run customize-reset` - remove all symbolic links. If you build after running this command you will see errors, as required files are not present. - -### Customizing Images - -The following image resources can be changed by creating the specified file in the folder shown: - -|File name|Folder|Description| -|---|---|---| -|favicon.ico|custom-src/frontend|Favorite icon to use| -|logo.png|custom-src/frontend/assets|Logo to use on login screen and about page| -|nav-logo.png|custom-src/frontend/assets|Logo to use in the top-left side navigation for the application logo| -|login-bg.jpg|custom-src/frontend/assets|Image to use for the login page background| - -> NOTE: The `nav-logo.png` logo should have a height of 36px and a maximum width of 180 pixels. - -### Customizing the Theme - -Stratos uses Material Design and the [angular-material](https://material.angular.io/) library. It uses the same approach to theming. - -To create your own theme, create the file `custom.scss` in the folder `custom-src/frontend/sass`. - -In this file you can set any or all of the following variables: - -|Variable|Purpose| -|---|---| -|$stratos-theme|The main theme to use for Stratos| -|$stratos-nav-theme|Theme to use for the side navigation panel| -|$stratos-status-theme|Theme to use for displaying status in Stratos| - -Note that you do not have to specify all of these - defaults will be used if they are not set. - -In most cases you will probably want to generate a palette for the primary color for your version of Stratos - an example `custom.scss` this for this is shown below: - -``` -$suse-green: ( 50: #E0F7F0, 100: #B3ECD9, 200: #80E0C0, 300: #4DD3A7, 400: #26C994, 500: #00C081, 600: #00BA79, 700: #00B26E, 800: #00AA64, 900: #009C51, A100: #C7FFE0, A200: #94FFC4, A400: #61FFA8, A700: #47FF9A, contrast: (50: #000000, 100: #000000, 200: #000000, 300: #000000, 400: #ffffff, 500: #ffffff, 600: #ffffff, 700: #ffffff, 800: #ffffff, 900: #ffffff, A100: #000000, A200: #000000, A400: #000000, A700: #000000 )); - -$suse-red: ( 50: #ffebee, 100: #ffcdd2, 200: #ef9a9a, 300: #e57373, 400: #ef5350, 500: #f44336, 600: #e53935, 700: #d32f2f, 800: #c62828, 900: #b71c1c, A100: #ff8a80, A200: #ff5252, A400: #ff1744, A700: #d50000, contrast: ( 50: $black-87-opacity, 100: $black-87-opacity, 200: $black-87-opacity, 300: $black-87-opacity, 400: $black-87-opacity, 500: white, 600: white, 700: white, 800: $white-87-opacity, 900: $white-87-opacity, A100: $black-87-opacity, A200: white, A400: white, A700: white, )); - -// Create palettes -$suse-app-primary: mat-palette($suse-green); -$suse-theme-warn: mat-palette($suse-red); - -// Create a theme from the palette (secondary theme is the same as the primary in this example) -$suse-app-theme: mat-light-theme($suse-app-primary, $suse-app-primary, $suse-theme-warn); - -// Set this theme as the one to use -$stratos-theme: $suse-app-theme; -``` - -#### Creating or disabling the Dark theme - -You can also change the Dark theme, if you wish, by defining the following variables: - -|Variable|Purpose| -|---|---| -|$stratos-dark-theme|The dark theme to use for Stratos| -|$stratos-dark-nav-theme|Dark theme to use for the side navigation panel| -|$stratos-dark-status-theme|Dark theme to use for displaying status in Stratos| - -Note that minimally you must supply `stratos-dark-theme` to create a dark theme. - -By default a dark theme is assumed to be available and the default will be used if not overridden. You can disable dark theme support in the UI by setting the following variable in your `custom.scss`: - -``` -$stratos-dark-theme-supported: false; -``` - -### Changing Styles - -We don't generally recommend modifying styles, since from version to version of Stratos, we may change the styles used slightly which can mean any modifications you made will need updating. Should you wish to do so, you can modify these in the same `custom.scss` file that is used for theming. - -As an example, to disable the login background image, add the following to `custom.scss`: - -``` -.stratos .intro { - background-image: none; -} -``` - -Note that the class `stratos` has been placed on the `BODY` tag of the Stratos application to assist with css selector specificity. - -### Adding new Features - -Code for new features should be placed within the `custom-src/frontend/app/custom` folder. You can create any sub-folder structure within this folder. - -When you perform an `npm install` or explicitly run `npm run customize`, the customize script is run and will symlink the folder `custom-src/frontend/app/custom` to `src/frontend/app/custom`. It will also create a module to import your custom code - this is placed in the file `src/frontend/app/custom/custom-import.module.ts`. You should _not_ edit this file. - -Within the `custom-src/frontend/app/custom` folder you must create a module in the file `custom.module.ts` named `CustomModule` - this will be imported into the Stratos application and is the mechanism by which you can add custom code to the front-end. - -We currently expose the following extension points in the Stratos UI: - -- Changing the component to use for the login screen -- Adding new items to the side navigation menu -- Adding new tabs to the Application, Cloud Foundry, Organization and Space views -- Adding new action buttons to the Application Wall, Application, Cloud Foundry, Organization and Space and Endpoint views - -We use Decorators to annotate components to indicate that they are Stratos extensions. - -See [Extensions](extensions.md) for more detail and examples of front-end extensions. - - -### Changing the Initial Loading Indicator - -On slower connections, it can take a few seconds to load the main Javascript resources for Stratos. - -In order to give the user some initial feedback that Stratos is loading, a loading indicator is included in the `index.html` file. This gets shown as early as possible, as soon as this main html file has loaded. Once the main code has been fetched, the view refreshes to show the application. - -A default loading indicator is provided that can be changed. To do so, create the following two files: - -- `custom-src/frontend/loading.css` - CSS styles to be included in a style block in the head of the index page -- `custom-src/frontend/loading.html` - HTML markup to be included the the index page to render the loading indicator - -The files for the default indicator can be found in the `src/frontend/packages/core/misc/custom` folder. - -An example of a different loading indicator is included with the ACME sample in `examples/custom-src/frontend`. - -The customization task will insert the appropriate CSS and HTML files into the main index.html file when it runs. - -Take a look at the template for the `index.html` file in `src/frontend/packages/core/misc/custom/index.html`. The CSS file is inserted where the marker `/** @@LOADING_CSS@@ **/` is and the HTML file where `` is. - diff --git a/docs/developers-guide-env-tech.md b/docs/developers-guide-env-tech.md deleted file mode 100644 index 7e22201b90..0000000000 --- a/docs/developers-guide-env-tech.md +++ /dev/null @@ -1,211 +0,0 @@ -# Stratos Tech + Developer Environment - -## ES6 - -* [http://stack.formidable.com/es6-interactive-guide](http://stack.formidable.com/es6-interactive-guide) -* [http://es6-features.org](http://es6-features.org) - -## TypeScript - -* [https://www.sitepen.com/blog/2013/12/31/definitive-guide-to-typescript/](https://www.sitepen.com/blog/2013/12/31/definitive-guide-to-typescript/) -* [https://learnxinyminutes.com/docs/typescript/](https://learnxinyminutes.com/docs/typescript/) (cheat sheet) -* [https://www.tutorialspoint.com/typescript/](https://www.tutorialspoint.com/typescript/) -* [https://tutorialzine.com/2016/07/learn-typescript-in-30-minutes](https://tutorialzine.com/2016/07/learn-typescript-in-30-minutes) -* [https://www.sitepen.com/blog/2013/12/31/typescript-cheat-sheet/](https://www.sitepen.com/blog/2013/12/31/typescript-cheat-sheet/) (advanced cheat sheet) - -## Angular - -### Angular - -* [https://www.youtube.com/watch?v=KhzGSHNhnbI](https://www.youtube.com/watch?v=KhzGSHNhnbI) (very basic 1h video of angular covering basic app features in a demo) -* [https://angular.io/guide/architecture](https://angular.io/guide/architecture) (official angular intro) -* [https://angular.io/tutorial](https://angular.io/tutorial) (official angular tutorial. Basic to start with but good introduction to routing, http requests, promises, observables and observable event stream later on) - -### Angular CLI - -* [https://cli.angular.io/reference.pdf](https://cli.angular.io/reference.pdf) (cheat sheet) -* [https://github.com/angular/angular-cli](https://github.com/angular/angular-cli) - -### Example Apps - -* https://github.com/aviabird/angularspree (covers everything) - -## Redux - -* [http://redux.js.org](http://redux.js.org) -* [https://gist.github.com/btroncone/a6e4347326749f938510](https://gist.github.com/btroncone/a6e4347326749f938510) -* [https://code-cartoons.com/a-cartoon-intro-to-redux-3afb775501a6](https://code-cartoons.com/a-cartoon-intro-to-redux-3afb775501a6) -* [https://www.youtube.com/watch?v=WIqbzHdEPVM](https://www.youtube.com/watch?v=WIqbzHdEPVM) -* [https://egghead.io/courses/getting-started-with-redux](https://egghead.io/courses/getting-started-with-redux) - -### Observables - -* [http://reactivex.io/documentation/observable.html](http://reactivex.io/documentation/observable.html) - -## VS Code Plug-ins - -There's no mandated IDE, but if you choose VS Code here's some helpful plug-ins - -### Super Helpful - -* TSLint -* SassLint -* TS Hero -* Beautify -* gitignore - -### Helpful - -* Angular 2+ Snippets -* Angular v4 TypeScript Snippets -* Git Lens -* Document This -* Git History -* TODO Parser - * see icon bottom left for todo's in current file - * Add the following config to settings to exclude some folders - - ``` - "TodoParser": { - "folderExclude": ["node_modules", ".vscode"] - } - ``` - - * F1 + `Parse TODOs (all files)` to see all TODOs -* Move TS - Move files and automatically update imports - -### Of Interest - -* Eclipse Keymap -* Code Spell Checker - * List of words to exclude coming soon - -Example settings - -``` -{ - "gitlens.blame.line.enabled": false, - "gitlens.codeLens.recentChange.enabled": false, - "gitlens.codeLens.authors.enabled": false, - "gitlens.blame.highlight.locations": [ - "gutter", - "overview" - ], - "gitlens.currentLine.enabled": false, - "gitlens.hovers.currentLine.enabled": false, - "editor.fontSize": 12, - "editor.formatOnSave": true, - "editor.rulers": [ - 140 - ], - "editor.renderLineHighlight": "none", - "search.exclude": { - "node_modules": true, - "**/node_modules": true, - "**/bower_components": true, - "coverage": true, - "components/*/backend/vendor": true, - "*.orig": true - }, - "search.useIgnoreFilesByDefault": true, - "tslint.validateWithDefaultConfig": true, - "tslint.configFile": "tslint.json", - "tslint.autoFixOnSave": true, - "tslint.alwaysShowStatus": true, - "tslint.alwaysShowRuleFailuresAsWarnings": true, - "explorer.autoReveal": false, - "extensions.ignoreRecommendations": false, - "TodoParser": { - "folderExclude": [ - "node_modules", - ".vscode" - ] - }, - "cSpell.userWords": [ - "Guids", - "action", - "api", - "cnsi", - "cnsis", - "confirmation", - "dialog", - "falsies", - "guid", - "iapi", - "initialise", - "initialised", - "injectable", - "memberof", - "ngrx", - "normalizr", - "strat", - "unsubscribe", - "vars" - ], - "cSpell.language": ",en-GB", - "cSpell.enabledLanguageIds": [ - "c", - "css", - "cpp", - "csharp", - "fonts", - "go", - "handlebars", - "javascript", - "javascriptreact", - "json", - "latex", - "markdown", - "php", - "plaintext", - "python", - "restructuredtext", - "text", - "typescript", - "typescriptreact", - "yml", - "html" - ], - "files.exclude": { - "**/.git": true, - "**/.svn": true, - "**/.hg": true, - "**/CVS": true, - "**/.DS_Store": true, - "**/tmp": true, - "**/node_modules": true, - "**/bower_components": true, - "**/dist": true, - "**/.orig": true - }, - "files.watcherExclude": { - "**/.git/objects/**": true, - "**/.git/subtree-cache/**": true, - "**/node_modules/**": true, - "**/tmp/**": true, - "**/bower_components/**": true, - "**/dist/**": true - }, -} -``` - -## Guides - -### ExpressionChangedAfterItHasBeenCheckedError Error - -#### Links - -* https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4 -* https://github.com/angular/angular/issues/17572 -* https://github.com/angular/angular/issues/6005 - -#### In summary - -* AVOID - * setTimeout - * forcing change detecting -* TRY TO USE - * observeOn(asapScheduler) in observable chain - * ngAfterViewInit -* Generally - * Avoid changing state in child components that will affect a binding in parent component/s \ No newline at end of file diff --git a/docs/developers-guide.md b/docs/developers-guide.md deleted file mode 100644 index 9d9929d16e..0000000000 --- a/docs/developers-guide.md +++ /dev/null @@ -1,294 +0,0 @@ - -# Developing the Stratos Console - -1. [Introduction](#introduction) -1. [Frontend Development](#frontend-development) -1. [Backend Development](#backend-development) - -## Introduction - -Stratos comprises of two main components: - -- A front-end UI that runs in your web browser. This is written in [Typescript](https://www.typescriptlang.org/) and uses the [Angular](https://angular.io/) framework. -- A back-end that provides a web-based API to the front-end. This is written in Go. - -Depending on what you are contributing, you will need to develop with the front-end, back-end or both. - -## Frontend Development - -### Introduction to the stack - -Have a look through the [Env + Tech](developers-guide-env-tech.md) page to get acquainted with some of the new technologies used in v2. -These include video's, tutorials and examples of Angular 2+, Typescript and Redux. There's also some advice on helpful plugins to use if -using Visual Studio Code. If you feel comfortable with these and are happy with your dev environment please skip straight to -[Set up Dependencies](#set-up-dependencies) - -### Set up Dependencies - -* Set up a Stratos backend - The frontend cannot run without a backend. Both backend and frontend exist in this same repo. - * Don't need to make changes to the backend code? To set up a backend run through the [deploy section](https://github.com/cloudfoundry/stratos/blob/master/deploy/README.md), - choose a deployment method and bring one up. These deployments will bring up the entire backend, including api service and database - along with a V2 frontend. - * Need to make changes to the backend code? Follow the below [Backend Development](#Backend-Development) set up guide -* Install [NodeJs](https://nodejs.org) (minimum node version 12.13.0) -* Install [Angular CLI](https://cli.angular.io/) - `npm install -g @angular/cli` - -### Configuration - -Configuration information can be found in two places - -* `./proxy.conf.js` - * In new forks this is missing and needs to be created using `./proxy.conf.template.js` as a template. - * Contains the address of the backend. Which will either be... - * If the backend is deployed via the instructions in the [deploy section](https://github.com/cloudfoundry/stratos/blob/master/deploy/README.md) - the url will be the same address as the V1 console's frontend address. For instance `https://localhost` would translate to - ``` - const PROXY_CONFIG = { - "/pp": { - "target": { - "host": "localhost", - "protocol": "https:", - "port": 443 - }, - "secure": false, - "changeOrigin": true, - "ws": true, - } - ``` - * If the backend is running locally using the instructions [Backend Development](#Backend-Development) below the url will local host - with a port of the `CONSOLE_PROXY_TLS_ADDRESS` value from `src/jetstream/config.properties`. By default this will be 5445. For - instance - ``` - const PROXY_CONFIG = { - "/pp": { - "target": { - "host": "localhost", - "protocol": "https:", - "port": 5443 - }, - "ws": true, - "secure": false, - "changeOrigin": true, - } - } - ``` -* `./src/frontend/environments/environment.ts` for developer vs production like config - * This contains more general settings for the frontend and does not usually need to be changed - -## Run the frontend - -1. (First time only) Copy `./proxy.conf.template.js` to `./proxy.conf.js` and update with required Jetstream url (see above for more info) -1. Run `npm install` -1. Run `npm start` for a dev server. (the app will automatically reload if you change any of the source files) - * If this times out please use `npm run start-high-mem` instead - * To change the port from the default 4200, add `-- --port [new port number]` - * To stop the automatic reload every time a resource changes add `-- --live-reload false` - * To do both the above use `-- --live-reload false --port [new port number]` -1. Navigate to `https://localhost:4200/`. The credentials to log in will be dependent on the Jetstream the console points at. Please refer - to the guides used when setting up the backend for more information - -## Build - -Run `npm run build` to build the project. - -The build artefacts will be stored in the `dist/` directory. This will output a production build of the application. - -## Creating angular items via angular cli - -To create a new angular component run `ng generate component component-name`. You can use a similar command to create other types of angular -items `ng generate `. - -## Theming - -We use the angular material theming mechanism. See [here](https://material.angular.io/guide/theming-your-components) for more information about theming new components added to stratos. - -## Test - -### Lint - -Run `npm run lint` to execute tslint lint checking. - -### Unit tests - -Run `npm test` to execute the unit tests via [Karma](https://karma-runner.github.io). Coverage information can be found in `./coverage` - -To execute an individual package run `ng test `. To execute the tests again automatically on code changes add `--watch=true` - -> **NOTE** npm test will search for chrome on your path. If this is not so please set an env var CHROME_BIN pointing to your executable -(chromium is fine too). - -### End-to-end tests - -Run `npm run e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). - -Run `npm run e2e-dev` to execute end-to-end tests against a locally running instance on `https://localhost:4200` - -More information on the E2E tests and pre-requisites for running them is available here - [E2E Tests](developers-guide-e2e-tests.md). - -### Code Climate - -We use [Code Climate](https://codeclimate.com/github/SUSE/stratos) to check for general code quality issues. This executes against Pull -Requests on creation/push. - -#### Running Code Climate locally -> Generally we would not advise doing this and just rely on the code climate gate to run when pull requests are submitted - -To run locally see instructions [here](https://github.com/codeclimate/codeclimate) to install Code Climate CLI -and engine via docker. Once set ensure you're in the root of the project and execute the following (it may take a while) - -``` -codeclimate analyze -``` - -> **NOTE** Unfortunately this highlights all current issues and not those that are the diff between any master and feature branch. Analyze -can be ran against a single/sub set of files, again with all current issues, but a little more digestible. - -``` -codeclimate analyze -``` - -In a feature branch to compare files that have changed to master, for instance, use the following - -``` -git checkout feature-branch-A -codeclimate analyze $(git diff --name-only master) -``` - -You can also run the above command via npm - -``` -npm run climate -``` - -### Stratos Continue Integration -For each new pull request and any subsequent pushes to it the following actions are executed -- Code quality analysis via Code Climate - https://codeclimate.com/ -- Jenkins CI run, covering.. - - Frontend lint check - - Backend lint check - - Frontend unit tests - - Backend unit tests - - End to end tests -- Security anaylsis via Snyk - https://snyk.io/ - -## Backend Development - -Jetstream is the back-end for Stratos. It is written in Go. - -We use Go Modules for dependency management. - -### Pre-requisites - -You will need the following installed/available: - -* go 1.12 or later. - -*For authentication, **either*** - -* A UAA instance -* A local user account - -### Building the back-end - - -#### Build - -From the `src/jetstream` folder, build the Stratos back-end with: - -``` -npm run build-backend -``` - -The back-end executable is named `jetstream` and should be created within the `src/jetstream` folder. - -### Configuration - -Configuration can either be done via -- Environment Variable and/or Config File -- In the UI when you first use a front end with this backend - -In all cases the configuration is saved to the database on first run. Any subsequent changes require the db to be reset. For the default sqlite -db provider this can be done by deleting `src/jetstream/console-database.db` - -#### Configure by Environment Variables and/or Config File - -By default, the configuration in file `src/jetstream/config.properties` will be used. These can be changed by environment variables -or an overrides file. - -##### Environment variable - -If you wish to use a local user account, ensure you have set the following environment variables: - -- `AUTH_ENDPOINT_TYPE=local` -- `LOCAL_USER` - The username for the local user -- `LOCAL_USER_PASSWORD` - The password for the local user -- `LOCAL_USER_SCOPE=stratos.admin` - This gives the local user admin permissions. Currently other roles are not available. - -If you have a custom uaa, ensure you have set the following environment variables: - -- `UAA_ENDPOINT`- the URL of your UAA - - If you have an existing CF and want to use the same UAA use the `authorization_endpoint` value from `[cf url]/v2/info` - For example for PCF Dev, use: `UAA_ENDPOINT=https://login.local.pcfdev.io`. -- `CONSOLE_CLIENT` - the Client ID to use when authenticating against your UAA (defaults to: 'cf') -- `CONSOLE_CLIENT_SECRET` - the Client ID to use when authenticating against your UAA (defaults to empty) -- `CONSOLE_ADMIN_SCOPE` - an existing UAA scope that will be used to identify users as `Stratos Admins` - -> To use a pre-built Stratos UAA container execute `docker run --name=uaa --rm -p 8080:8080 -P splatform/stratos-uaa`. The UAA will be - available at `http://localhost:8080` with a `CONSOLE_CLIENT` value of `console` - -##### Config File - -To easily persist configuration settings copy `src/jetstream/config.example` to `src/jetstream/config.properties`. The backend will load its -configuration from this file in preference to the default config file, if it exists. You can also modify individual configuration settings -by setting the corresponding environment variable. - -##### To configure a local user account via config file - -In `src/jetstream/config.properties` uncomment the following lines: - -``` -AUTH_ENDPOINT_TYPE=local -LOCAL_USER=localuser -LOCAL_USER_PASSWORD=localuserpass -LOCAL_USER_SCOPE=stratos.admin -``` - -Load the Stratos UI and proceed to log in using the configured credentials. - -#### To configure UAA via Stratos - -1. Go through the `Config File` step above and comment out the `UAA_ENDPOINT` with a `#` in the new `config.properties` file. -1. If any previous configuration attempt has been made reset your database as described above. -1. Continue these steps from [Run](#run). - - You should see the line `Will add setup route and middleware` in the logs -1. Load the Stratos UI as usual and you should be immediately directed to the setup wizard - -The setup wizard that allows you to enter the values normally fetched from environment variables or files. The UI will assist you through -this process, validating that the UAA address and credentials are correct. It will also provide a list of possible scopes for the Stratos Admin - -#### Run - -Execute the following file from `src/jetstream` - -``` -jetstream -``` - -You should see the log as the backend starts up. You can press CTRL+C to stop the backend. - - -#### Automatically register and connect to an existing endpoint -To automatically register a Cloud Foundry add the environment variable/config setting below: - -``` -AUTO_REG_CF_URL= -``` - -Jetstream will then attempt to auto-connect to it with the credentials supplied when logging into Stratos. - -#### Running Jetstream in a container - -We recommend running Stratos using the Docker All-In-One image. - -* Follow instructions in the deploy/all-in-one docs - diff --git a/docs/extensions.md b/docs/extensions.md deleted file mode 100644 index 64c182e6a0..0000000000 --- a/docs/extensions.md +++ /dev/null @@ -1,284 +0,0 @@ -# Front-end Extensions - -An example illustrating the various front-end extension points of Stratos is included in the folder `examples/custom-src`. - -To include the customizations in this example, either copy or symlink the `examples/custom-src` to `custom-src` at the top-level of the Stratos repository. - -Next, run the customization script (this is done automatically when you do an `npm install`) with: - -``` -npm run customize -``` - -You can now run Stratos locally to see the customizations - see the [Developer's Guide](developers-guide.md) for details. - -For a walk-through of extending Stratos, see [Example: Adding a Custom Tab](#example:-adding-a-custom-tab). - -## Extension Points - -### Side Navigation - -New items can be added to the Side Navigation menu with extensions. - -To do so, annotate the routes for your extension with custom metadata, which Stratos will then pick up and add to the side menu. - -A full example is in the folder `examples/custom-src/frontend/app/custom/nav-extension`. - -Your route should have the following metadata in the `data` field: - -``` - stratosNavigation: { - text: '', - matIcon: '<ICON NAME>' - } -``` - -Where `<TITLE>` is the text label to show in the side navigation and `<ICON NAME>` is the icon to use. - -You should place your route declaration in a module named `CustomRoutingModule` in the file `custom-src/frontend/app/custom/custom-routing.module.ts`. - -An example routing module would be: - -``` -import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; - -const customRoutes: Routes = [{ - path: 'example', - loadChildren: 'app/custom/nav-extension/nav-extension.module#NavExtensionModule', - data: { - stratosNavigation: { - text: 'Example', - matIcon: 'extension' - } - } -}]; - -@NgModule({ - imports: [ - RouterModule.forRoot(customRoutes), - ], - declarations: [] -}) -export class CustomRoutingModule { } -``` - -This approach ensures that the Angular compiler creates a separate chunk for the extension at compile time. - -### Custom Tabs - -Tabs can be added to the following views in Stratos: - -- The Application view that shows the detail of an application -- The Cloud Foundry view that shows detail for a Cloud Foundry -- The Cloud Foundry Org view that shows detail for a Cloud Foundry organization -- The Cloud Foundry Space view that shows detail for a Cloud Foundry space - -For example: - -![Example Application tab extension](images/extensions/app-tab-example.png) - -The approach for all of these is the same: - -1. Create a new component that will provide the tab contents -2. Ensure that your component is declared with the `ExtensionService` in the imports of your custom module, for example: -``` - @NgModule({ - imports: [ - ExtensionService.declare([ - ExtensionComponent, - ]) - ], - declarations: [ - ExtensionComponent - ] - }) -``` - -2. Decorate the component with the `StratosTab` decorator, for example: - -``` -@StratosTab({ - type: StratosTabType.Application, - label: '<LABEL>', - link: '<LINK>' -}) -``` - -Where: - -- <TYPE> indicates where the tab should appear and can be: - - StratosTabType.Application - Application View - - StratosTabType.CloudFoundry - Cloud Foundry view - - StratosTabType.CloudFoundryOrg - Cloud Foundry Org view - - StratosTabType.CloudFoundrySpace - Cloud Foundry Space view -- <LABEL> is the text label to use for the tab -- <LINK> is the name to use for the route (this must only contain characters permitted in URLs) - -An example is included in the file `examples/custom-src/frontend/app/custom/app-tab-extension`. - -### Custom Actions - -Actions can be added to the following views in Stratos: - -- The Application Wall view that shows all applications -- The Application view that shows the detail of an application -- The Cloud Foundry view that shows detail for a Cloud Foundry -- The Cloud Foundry Org view that shows detail for a Cloud Foundry organization -- The Cloud Foundry Space view that shows detail for a Cloud Foundry space -- The Endpoints view that shows all endpoints - -An action is a icon button that appears at the top-right of a View. For example: - -![Example Application action extension](images/extensions/appwall-action-example.png) - -The approach for all of these is the same: - -1. Create a new component that will provide the contents to show when the action is clicked -2. Ensure that your component is declared with the `ExtensionService` in the imports of your custom module -3. Decorate the component with the `StratosAction` decorator, for example: - -``` -@StratosAction({ - type: StratosActionType.Applications, - label: '<LABEL>', - link: '<LINK>', - icon: '<ICON> -}) -``` - -Where: - -- <TYPE> indicates where the action should appear and can be: - - StratosActionType.Applications - Application Wall View - - StratosActionType.Application - Application View - - StratosActionType.CloudFoundry - Cloud Foundry view - - StratosActionType.CloudFoundryOrg - Cloud Foundry Org view - - StratosActionType.CloudFoundrySpace - Cloud Foundry Space view - - StratosActionType.Endpoints - Endpoints view -- <ICON> is the icon to show -- <LABEL> is the text label to use for the tooltip of the icon (optional) -- <LINK> is the name to use for the route (this must only contain characters permitted in URLs) - -An example is included in the file `examples/custom-src/frontend/app/custom/app-action-extension`. - -## Example: Adding a Custom Tab - -In this example, we will walk through extending the Stratos front-end. - -This walk-through assumes that you have installed the Angular CLI globally - this can be done with `npm install -g @angular/cli`. - -### Create a new module - -First, create the custom-src folder structure - from the top-level of the Stratos repository run: - -``` -mkdir -p custom-src/frontend/app/custom -mkdir -p /frontend/assets/custom -``` - -Next, run the customize task: - -``` -npm run customize -``` - -This will symlink our custom folder into the Stratos application source folder. - -Next, use the Angular CLI to create the root module for our custom code with: - -``` -ng generate module custom -``` - -This create a new Angular module `CustomModule`. - -Run the customize script again, now that that we have created the custom module with: - -``` -npm run customize -``` - -### Create a new Component for our Tab - -Create a new Angular component with the CLI: - -``` -ng generate component custom/example-tab-extension -``` - -### Add Decorator to make this Component an Extension - -In a text editor, open the file: - -``` -src/frontend/app/custom/example-tab-extension/example-tab-extension.component.ts -``` - -Add the following decorator to the component at the top of the file: - -``` -import { StratosTab, StratosTabType } from '../../core/extension/extension-service'; - -@StratosTab({ - type: StratosTabType.Application, - label: 'Example App Tab', - link: 'example' -}) -``` - -The file should now look like this: - -``` -import { Component, OnInit } from '@angular/core'; -import { StratosTab, StratosTabType } from '../../core/extension/extension-service'; - -@StratosTab({ - type: StratosTabType.Application, - label: 'Example App Tab', - link: 'example' -}) -@Component({ - selector: 'app-example-tab-extension', - templateUrl: './example-tab-extension.component.html', - styleUrls: ['./example-tab-extension.component.scss'] -}) -export class ExampleTabExtensionComponent implements OnInit { - - constructor() { } - - ngOnInit() { - } - -} -``` - -Save the file. - - -### Mark the component as an extensions component - -The last thing we need to do is to mark our Extension component as an extensions component. - -To do this, in a text editor, open the file `src/frontend/app/custom/custom.module.ts` and add the `ExtensionService.declare` to the import sesion, so it looks like this: - -``` -@NgModule({ - imports: [ - CommonModule, - ExtensionService.declare([ - ExampleTabExtensionComponent, - ]) - ], - declarations: [ExampleTabExtensionComponent], - entryComponents: [ExampleTabExtensionComponent] -}) -export class CustomModule { } -``` - -### Run it - -You should now be able to run Stratos locally and see this new tab on the application page for an application - as illustrated below: - -![Example tab extension](images/extensions/tab-example.png) - diff --git a/docs/i18n.md b/docs/i18n.md deleted file mode 100644 index 0fc6e721e9..0000000000 --- a/docs/i18n.md +++ /dev/null @@ -1,3 +0,0 @@ -# Internationalization (i18n) - -WIP diff --git a/docs/images/extensions/tab-example.png b/docs/images/extensions/tab-example.png deleted file mode 100644 index 38afc322b7271958728d4dd97e4775348155cdb9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12163 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 XdTHeN8@YV&k0Ycd<i#t+zWV<!{*+*k diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index a6bdc1e312..0000000000 --- a/docs/index.html +++ /dev/null @@ -1 +0,0 @@ -<h1>Hello GH Pages</h1> diff --git a/docs/issue_template.md b/docs/issue_template.md index dffaf1add3..c8b8913393 100644 --- a/docs/issue_template.md +++ b/docs/issue_template.md @@ -1,8 +1,9 @@ -<!--- For bugs and general issues --> +<!--------- For bugs and general issues ---------> ### Stratos Version <!-- What version of Stratos does this related to? --> <!-- Version information can be seen in the `About` page reached via the User Icon top right --> <!-- If directly using code which branch, commit, etc did the source come from, what repository was it cloned from? --> + ### Frontend Deployment type <!--- Where is the frontend deployed? --> <!--- Put an 'x' in one of the boxes below. --> @@ -25,15 +26,15 @@ ### Actual behaviour -### Steps to reproduce the behaviour +### Steps to reproduce the behavior ### Log output covering before error and any error statements ``` -Insert your log here +Insert log hereCopy ``` -<!--- For feature requests --> +<!--------- For feature requests ---------> ### Detailed Description <!--- Provide a detailed description of the change or addition you are proposing --> diff --git a/docs/overview.md b/docs/overview.md deleted file mode 100644 index c1640d9390..0000000000 --- a/docs/overview.md +++ /dev/null @@ -1,64 +0,0 @@ -# Stratos Overview - -The Stratos Console provides a web-based UI to allow developers and administrators to manage their applications and cloud foundry deployment(s). - -It is designed to manage one or more Cloud Foundry deployments. It does so by managing "endpoints", where each endpoint is a reference to a Cloud Foundry deployment. The notion of an endpoint is not specific to Cloud Foundry, allowing Stratos to connect to other service types in the future. - -Stratos stores endpoint metadata in a relational database. Administrators of Stratos are able to register (add) new endpoints to the Console. All users are able to then connect to these endpoints using their credentials, ensuring that they get the appropriate level of access when interacting with Cloud Foundry. - -The high-level architecture of Stratos is shown in the diagram below: - -![Stratos High-Level Architecture](images/high-level-arch.png) - -The main components: - -* Web UI - Single-page AngularJS web application providing the front-end that runs in the user's browser. -* API Server - Provides the back-end APIs that support the front-end UI. This API takes care of authentication, endpoint management and proxying API requests from the front-end to the desired back-end API. -* Endpoint Datastore - Relational database that stores the registered endpoints and the encrypted user access tokens. - -## Authentication - -Stratos UI authenticates users using a Cloud Foundry UAA service. It must be configured with the details necessary to communicate with a UAA. When the user logs in to the Console, their login will be validated with the UAA. The Console uses a scope to identify Console administrators from regular users. - -Administrators use the 'Endpoints Dashboard' within the Console to add new endpoints to the Console. All users are then able to connect to these endpoints by providing their credentials. The Console will use these credentials to communicate with the UAA for the given endpoint (typically Cloud Foundry) and obtain a refresh and access token. These tokens are encrypted and stored in the Endpoint Datastore. - -When a user interacts with the Console and API requests need to be made to a given Cloud Foundry endpoint, these are sent to the API Server along with a custom http header which indicated which endpoint(s) the requests should be send to. The API Server will forward the request to the appropriate endpoints, first looking up the access and refresh tokens required to communicate with the endpoint(s). If any access token has expired, it will use the refresh token to obtain a new access token. - -## Deployment - -Stratos UI can be deployed in a number of environments: - -* Deployed in Cloud Foundry, as an application. See [guide](../deploy/cloud-foundry) -* Deployed in Kubernetes, using a Helm chart. See [guide](../deploy/kubernetes) -* Deployed in Docker, single container deploying all components. See [guide](../deploy/all-in-one) - -There are differences between the deployments as follows: - -### Deployed in Cloud Foundry as an application - -In this case, Stratos is deployed in a manner optimized for the management of a single Cloud Foundry instance. The 'Endpoints Dashboard' that allows multiple Cloud Foundry endpoints to be registered is not deployed. An extra component is deployed that detects that the Console is running as Cloud Foundry which does the following: - -- Automatically detects the Cloud Foundry endpoint and located the UAA Endpoint to use for authentication -- Authenticates directly against the UAA for the Clound Foundry where the Console is deployed and assumes that Cloud Foundry admins are also Console admins (the UAA Scope 'cloud_controller.admin' is used to identify admins) -- Uses a SQLite database rather than Postgres -- Automatically connects to the Cloud Foundry endpoint when a user logs in to simplify the user flow when using the Console in this case - -In this case, the front-end web application static resources are served by the API Server back-end rather than a separate web server. - -By defaut, a non-persistent SQLite database is used - by automatically registering the cloud foundry endpoint and connecting to it on login, all data stored in the database can be treated as ephimeral, since it will be re-created next time a user logs in. Cloud Foundry Session Affinity is used to ensure that when scaling up the Console Application to multiple instances, the user is also directed to the instance which will know about them and their endpoints (since each Application instance will have its own local SQLite store). - -Alternatively, Stratos can be configured [with a persistent Cloud Foundry database service](deploy/cloud-foundry/db-migration/README.md), which enables features requiring persistence such as user favorites. - -### Deployed in Kubernetes - -In this case, a Helm chart is used to deploy the Console into a Kubernetes environment. - -At the outer-level there are two services - the external service that provides the Console itself and a private service that provides a Postgres DB to the Console service. - -The Console service is provided by a deployment consisting of two containers, one providing the static front-end web application resources, served by an nginx instance, the other providing the API Server back-end. - -### Deployed in Docker using a single container - -In this case, a single Docker image is run in a container that hosts all services together. SQLite is used as the database, so any endpoint metadata registered is lost when the container is destroyed. - -This deployment is recommended only for trying out the Console and for development. diff --git a/docs/roadmap.md b/docs/roadmap.md deleted file mode 100644 index 25c55f2698..0000000000 --- a/docs/roadmap.md +++ /dev/null @@ -1,39 +0,0 @@ -# Stratos Roadmap - Out of date - -Last Updated: 2 July 2018 - -# Version 2 Timeline - -The current focus is on releasing V2 of Stratos. The current timeline is: - -|Date|Milestone| -|---|---| -|15 June|Beta 1| -|20 June|Beta 2| -|25 June|RC 1| -|29 June|RC 2| -|20 July|2.0.0| - -Post 2.0 is released we will be working towards a regular recycle cycle each Sprint - we may start with Monthly releases and finally to releases each 2 weeks. - -## Agile - -We work on a 2-week Sprint cycle. Sprints start on Wednesdays. For reference, Sprint 27 started 4 April 2018. - -We are using GitHub issues to track all work items. See: https://github.com/cloudfoundry/stratos/issues - -## High-Level Features - -The high-level features that we schedule into the roadmap are identified by the ```feature-request``` label. You can see the current set of features [here](https://github.com/cloudfoundry/stratos/issues?q=is%3Aopen+is%3Aissue+label%3Afeature-request). - -### Near-term roadmap - -|#|Description|Issue|Notes| -|---|---|---|---| -|1|Technology Refresh (AngularJS => Angular)|[\#1972](https://github.com/cloudfoundry/stratos/issues/1972)|[Notes](planning/angular.md)| -|2|1st class support for service plan & service instance|[\#1391](https://github.com/cloudfoundry/stratos/issues/1391)|[Notes](planning/services.md)| -|3|Add support for Application and CF Metrics|[\#1985](https://github.com/cloudfoundry/stratos/issues/1985)|[Notes](planning/metrics.md)| -|4|Support UAA login UX directly (SSO)|[\#1384](https://github.com/cloudfoundry/stratos/issues/1384)|| -|5|Support per-endpoint Client ID and Client Secret|[\#2220](https://github.com/cloudfoundry/stratos/issues/2220)|| -|6|Bring Orgs and Spaces view to top-level|[\#2619](https://github.com/cloudfoundry/stratos/issues/2619)|| -|7|Deploy application: Enable deploying from private Git repositories|[\#1442](https://github.com/cloudfoundry/stratos/issues/1442)|| diff --git a/index.yaml b/index.yaml deleted file mode 100755 index 2d8ee50438..0000000000 --- a/index.yaml +++ /dev/null @@ -1,76 +0,0 @@ -apiVersion: v1 -entries: - console: - - apiVersion: v1 - created: 2018-01-09T11:48:55.233833731Z - description: A Helm chart for deploying Stratos UI Console - digest: 378df78adeebfad7d81b63a9f01afbb830c35cf948e3b662349e1d20a0972955 - name: console - urls: - - https://github.com/cloudfoundry-incubator/stratos/releases/download/1.0.0/console-helm-chart-1.0.0.tgz - version: 1.0.0 - - apiVersion: v1 - created: 2017-12-08T13:21:38.611521378Z - description: A Helm chart for deploying Stratos UI Console - digest: 8e8994171cf7ba37c4b37564c8efa07f1fd66dda347900b592ebfaae5407bf3d - name: console - urls: - - https://github.com/cloudfoundry-incubator/stratos/releases/download/0.9.9/console-helm-chart-0.9.9.tgz - version: 0.9.9 - - apiVersion: v1 - created: 2017-11-23T16:31:10.781104928Z - description: A Helm chart for deploying Stratos UI Console - digest: 97bdf40f53053815016e223d49082ba16984b9b5a0a5a2d237ef45bb42183ad8 - name: console - urls: - - https://github.com/cloudfoundry-incubator/stratos/releases/download/0.9.8/console-helm-chart-0.9.8.tgz - version: 0.9.8 - - apiVersion: v1 - created: 2017-11-09T11:58:41.085073375Z - description: A Helm chart for deploying Stratos UI Console - digest: 1a297cebb9bf8d34fe4d679203082fb89894d9b90583988fb0a1349a27dde920 - name: console - urls: - - https://github.com/cloudfoundry-incubator/stratos/releases/download/0.9.7/console-helm-chart-0.9.7.tgz - version: 0.9.7 - - apiVersion: v1 - created: 2017-11-01T11:57:35.330202636Z - description: A Helm chart for deploying Stratos UI Console - digest: 08611ec5de41a71567a033c4975752f31ca895b26316592a75d6dc93c2968e5f - name: console - urls: - - https://github.com/cloudfoundry-incubator/stratos/releases/download/0.9.6/console-helm-chart-0.9.6.tgz - version: 0.9.6 - - apiVersion: v1 - created: 2017-09-21T14:17:00.259268006+01:00 - description: A Helm chart for deploying Console - digest: c0aa7067eebd02cbf775f1adde1948ff679f3753f5d6bb7403798260ad8278d7 - name: console - urls: - - https://github.com/cloudfoundry-incubator/stratos/releases/download/0.9.5/console-helm-chart-0.9.5.tgz - version: 0.9.5 - - apiVersion: v1 - created: 2017-08-21T16:05:19.606723792Z - description: A Helm chart for deploying Console - digest: 488b5011edcc0b22a28f4e70e576351cc6b9f0f34198fb366cc53cddc197c9d1 - name: console - urls: - - https://github.com/cloudfoundry-incubator/stratos/releases/download/0.9.2/console-helm-chart-0.9.2.tgz - version: 0.9.2 - - apiVersion: v1 - created: 2017-08-01T10:54:22.706325757Z - description: A Helm chart for deploying Console - digest: 5c7c2769771d7648647ed33a9e7c326c95cf550cb165aa3144dc8f8062dc66bb - name: console - urls: - - https://github.com/cloudfoundry-incubator/stratos/releases/download/0.9.1/console-helm-chart-0.9.1.tgz - version: 0.9.1 - - apiVersion: v1 - created: 2017-07-27T13:08:56.409964314Z - description: A Helm chart for deploying Console - digest: e05d4dd12d3e518ce8d123480196008815988dc59fa3653ad4593042e92cc36b - name: console - urls: - - https://github.com/cloudfoundry-incubator/stratos/releases/download/0.9.0/console-helm-chart-0.9.0.tgz - version: 0.9.0 -generated: 2018-01-09T11:48:55.232685995Z diff --git a/package-lock.json b/package-lock.json index 1c002ad57b..1823c4b66e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,23 @@ { "name": "stratos", - "version": "4.0.0", + "version": "4.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { + "@angular-builders/custom-webpack": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@angular-builders/custom-webpack/-/custom-webpack-9.2.0.tgz", + "integrity": "sha512-0ivkjENONFm0oNy6hdCod4YaT4dUk80KuP9+eDliWuZIA70yKQgIYMLul0bz6/i+Cm24PaZ2tq4w7kW7AuSMoA==", + "dev": true, + "requires": { + "@angular-devkit/architect": ">=0.900.0 < 0.1000.0", + "@angular-devkit/build-angular": ">=0.900.0 < 0.1000.0", + "@angular-devkit/core": "^9.0.0", + "lodash": "^4.17.10", + "ts-node": "^8.5.2", + "webpack-merge": "^4.2.1" + } + }, "@angular-devkit/architect": { "version": "0.901.7", "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.901.7.tgz", @@ -618,6 +632,17 @@ "resolved": "https://registry.npmjs.org/@angular/router/-/router-9.1.9.tgz", "integrity": "sha512-4u+CWMPB4hCkAsFCEzC94YEWT0wVozqGkc/Dortt2hFaqvZpIegg6iJVZlDxuyDjzFYBPnnbTDdgiTTA8ckfuA==" }, + "@apidevtools/json-schema-ref-parser": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz", + "integrity": "sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg==", + "dev": true, + "requires": { + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "js-yaml": "^3.13.1" + } + }, "@babel/code-frame": { "version": "7.5.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", @@ -2968,6 +2993,12 @@ } } }, + "@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true + }, "@ngrx/effects": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-9.2.0.tgz", @@ -3964,15 +3995,6 @@ "type-fest": "^0.11.0" } }, - "ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", - "dev": true, - "requires": { - "ansi-wrap": "0.1.0" - } - }, "ansi-html": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", @@ -3993,16 +4015,17 @@ "color-convert": "^1.9.0" } }, - "ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", "dev": true }, "anymatch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -4014,15 +4037,6 @@ "integrity": "sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA==", "dev": true }, - "append-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", - "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", - "dev": true, - "requires": { - "buffer-equal": "^1.0.0" - } - }, "append-transform": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", @@ -4081,108 +4095,24 @@ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", "dev": true }, - "arr-filter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", - "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", - "dev": true, - "requires": { - "make-iterator": "^1.0.0" - } - }, "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true }, - "arr-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", - "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", - "dev": true, - "requires": { - "make-iterator": "^1.0.0" - } - }, "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, - "array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", - "dev": true - }, "array-flatten": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", "dev": true }, - "array-initial": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", - "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", - "dev": true, - "requires": { - "array-slice": "^1.0.0", - "is-number": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, - "array-last": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", - "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", - "dev": true, - "requires": { - "is-number": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, - "array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", - "dev": true - }, - "array-sort": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", - "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", - "dev": true, - "requires": { - "default-compare": "^1.0.0", - "get-value": "^2.0.6", - "kind-of": "^5.0.2" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -4320,18 +4250,6 @@ "integrity": "sha1-yL4BCitc0A3qlsgRFgNGk9/dgtE=", "dev": true }, - "async-done": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", - "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.2", - "process-nextick-args": "^2.0.0", - "stream-exhaust": "^1.0.1" - } - }, "async-each": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", @@ -4344,15 +4262,6 @@ "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", "dev": true }, - "async-settle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", - "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", - "dev": true, - "requires": { - "async-done": "^1.2.2" - } - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -4527,23 +4436,6 @@ "object.assign": "^4.1.0" } }, - "bach": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", - "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", - "dev": true, - "requires": { - "arr-filter": "^1.1.1", - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "array-each": "^1.0.0", - "array-initial": "^1.0.0", - "array-last": "^1.1.1", - "async-done": "^1.2.2", - "async-settle": "^1.0.0", - "now-and-later": "^2.0.0" - } - }, "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", @@ -5106,18 +4998,6 @@ "isarray": "^1.0.0" } }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true - }, - "buffer-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", - "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", - "dev": true - }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -5324,6 +5204,12 @@ } } }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", + "dev": true + }, "caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", @@ -5537,6 +5423,20 @@ "integrity": "sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==", "dev": true }, + "cli-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.0.tgz", + "integrity": "sha512-a0VZ8LeraW0jTuCkuAGMNufareGHhyZU9z8OGsW0gXd1hZGi1SRuNRXdbGkraBBKnhyUhyebFWnRbp+dIn0f0A==", + "dev": true, + "requires": { + "ansi-regex": "^2.1.1", + "d": "^1.0.1", + "es5-ext": "^0.10.51", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.14", + "timers-ext": "^0.1.7" + } + }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -5558,41 +5458,12 @@ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", "dev": true }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - }, - "dependencies": { - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - } - } - } - }, "clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", "dev": true }, - "clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", - "dev": true - }, "clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -5613,23 +5484,6 @@ "mimic-response": "^1.0.0" } }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "dev": true - }, - "cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" - } - }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -5697,17 +5551,6 @@ } } }, - "collection-map": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", - "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", - "dev": true, - "requires": { - "arr-map": "^2.0.2", - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - } - }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -5751,12 +5594,6 @@ "simple-swizzle": "^0.2.2" } }, - "color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true - }, "colors": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", @@ -5973,16 +5810,6 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true }, - "copy-props": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", - "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", - "dev": true, - "requires": { - "each-props": "^1.3.0", - "is-plain-object": "^2.0.1" - } - }, "copy-webpack-plugin": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz", @@ -6780,23 +6607,6 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==" }, - "default-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", - "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", - "dev": true, - "requires": { - "kind-of": "^5.0.2" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, "default-gateway": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", @@ -6824,12 +6634,6 @@ } } }, - "default-resolution": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", - "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", - "dev": true - }, "defaults": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", @@ -7010,12 +6814,6 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", "dev": true }, - "detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", - "dev": true - }, "detect-node": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", @@ -7205,16 +7003,6 @@ "stream-shift": "^1.0.0" } }, - "each-props": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", - "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.1", - "object.defaults": "^1.1.0" - } - }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -7263,7 +7051,8 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "emojis-list": { "version": "3.0.0", @@ -8160,18 +7949,6 @@ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", "dev": true }, - "fancy-log": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", - "dev": true, - "requires": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" - } - }, "fast-deep-equal": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", @@ -8452,93 +8229,6 @@ "locate-path": "^2.0.0" } }, - "findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", - "dev": true, - "requires": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "dependencies": { - "expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" - } - }, - "global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "requires": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - } - }, - "global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", - "dev": true, - "requires": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - } - }, - "resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", - "dev": true, - "requires": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - } - } - } - }, - "fined": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", - "dev": true, - "requires": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" - }, - "dependencies": { - "expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" - } - } - } - }, - "flagged-respawn": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", - "dev": true - }, "flat-cache": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", @@ -8616,15 +8306,6 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, "foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", @@ -8797,16 +8478,6 @@ } } }, - "fs-mkdirp-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", - "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" - } - }, "fs-write-stream-atomic": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", @@ -8938,194 +8609,19 @@ } } }, - "glob-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "global-dirs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", + "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==", "dev": true, "requires": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" + "ini": "^1.3.5" } }, - "glob-watcher": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz", - "integrity": "sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-done": "^1.2.0", - "chokidar": "^2.0.0", - "is-negated-glob": "^1.0.0", - "just-debounce": "^1.0.0", - "object.defaults": "^1.1.0" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - } - }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - } - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - } - }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, - "optional": true - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "global-dirs": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", - "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==", - "dev": true, - "requires": { - "ini": "^1.3.5" - } - }, - "global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", "dev": true, "requires": { "global-prefix": "^1.0.1", @@ -9212,15 +8708,6 @@ "minimatch": "~3.0.2" } }, - "glogg": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", - "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", - "dev": true, - "requires": { - "sparkles": "^1.0.0" - } - }, "gonzales-pe-sl": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/gonzales-pe-sl/-/gonzales-pe-sl-4.2.3.tgz", @@ -9271,150 +8758,6 @@ "lodash": "^4.17.15" } }, - "gulp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", - "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", - "dev": true, - "requires": { - "glob-watcher": "^5.0.3", - "gulp-cli": "^2.2.0", - "undertaker": "^1.2.1", - "vinyl-fs": "^3.0.0" - }, - "dependencies": { - "ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "dev": true, - "requires": { - "ansi-wrap": "^0.1.0" - } - }, - "gulp-cli": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", - "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", - "dev": true, - "requires": { - "ansi-colors": "^1.0.1", - "archy": "^1.0.0", - "array-sort": "^1.0.0", - "color-support": "^1.1.3", - "concat-stream": "^1.6.0", - "copy-props": "^2.0.1", - "fancy-log": "^1.3.2", - "gulplog": "^1.0.0", - "interpret": "^1.4.0", - "isobject": "^3.0.1", - "liftoff": "^3.1.0", - "matchdep": "^2.0.0", - "mute-stdout": "^1.0.0", - "pretty-hrtime": "^1.0.0", - "replace-homedir": "^1.0.0", - "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.2.0", - "yargs": "^7.1.0" - } - }, - "interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true - }, - "v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" - } - } - } - }, - "gulp-zip": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/gulp-zip/-/gulp-zip-5.0.1.tgz", - "integrity": "sha512-M/IWLh9RvOpuofDZkgDirtiyz9J3yIqnDOJ3muzk2D/XnZ1ruqPlPLRIpXnl/aZU+xXwKPdOIxjRzkUcVEQyZQ==", - "dev": true, - "requires": { - "get-stream": "^5.1.0", - "plugin-error": "^1.0.1", - "through2": "^3.0.1", - "vinyl": "^2.1.0", - "yazl": "^2.5.1" - }, - "dependencies": { - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==" - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "through2": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", - "dev": true, - "requires": { - "readable-stream": "2 || 3" - } - } - } - }, - "gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", - "dev": true, - "requires": { - "glogg": "^1.0.0" - } - }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -9673,6 +9016,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, "requires": { "parse-passwd": "^1.0.0" } @@ -10143,12 +9487,6 @@ "loose-envify": "^1.0.0" } }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", @@ -10167,16 +9505,6 @@ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "dev": true }, - "is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "requires": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - } - }, "is-absolute-url": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", @@ -10219,6 +9547,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "requires": { "binary-extensions": "^2.0.0" }, @@ -10226,7 +9555,8 @@ "binary-extensions": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==" + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true } } }, @@ -10331,7 +9661,8 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", @@ -10400,12 +9731,6 @@ "xtend": "^4.0.0" } }, - "is-negated-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", - "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", - "dev": true - }, "is-npm": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", @@ -10456,6 +9781,12 @@ "isobject": "^3.0.1" } }, + "is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true + }, "is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", @@ -10488,15 +9819,6 @@ "has": "^1.0.1" } }, - "is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "dev": true, - "requires": { - "is-unc-path": "^1.0.0" - } - }, "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", @@ -10538,21 +9860,6 @@ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, - "is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "dev": true, - "requires": { - "unc-path-regex": "^0.1.2" - } - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, "is-valid-glob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", @@ -11149,6 +10456,43 @@ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "dev": true }, + "json-schema-ref-parser": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz", + "integrity": "sha512-z0JGv7rRD3CnJbZY/qCpscyArdtLJhr/wRBmFUdoZ8xMjsFyNdILSprG2degqRLjBjyhZHAEBpGOxniO9rKTxA==", + "dev": true, + "requires": { + "@apidevtools/json-schema-ref-parser": "9.0.6" + } + }, + "json-schema-to-typescript": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-9.1.1.tgz", + "integrity": "sha512-VrdxmwQROjPBRlHxXwGUa2xzhOMPiNZIVsxZrZjMYtbI7suRFMiEktqaD/gqhfSya7Djy+x8dnJT+H0/0sZO0Q==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.4", + "cli-color": "^2.0.0", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "json-schema-ref-parser": "^9.0.1", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.15", + "minimist": "^1.2.5", + "mkdirp": "^1.0.4", + "mz": "^2.7.0", + "prettier": "^2.0.5", + "stdin": "0.0.1" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } + } + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -11163,12 +10507,6 @@ "kind-of": "^6.0.2" } }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -11235,12 +10573,6 @@ "set-immediate-shim": "~1.0.1" } }, - "just-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", - "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=", - "dev": true - }, "karma": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/karma/-/karma-5.0.1.tgz", @@ -11781,16 +11113,6 @@ "integrity": "sha512-QMQcnKAiQccfQTqtBh/qwquGZ2XK/DXND1jrcN9M8gMMy99Gwla7GQjndVUsEqIaRyP6bsFRuhwRj5poafBGJQ==", "dev": true }, - "last-run": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", - "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", - "dev": true, - "requires": { - "default-resolution": "^2.0.0", - "es6-weak-map": "^2.0.1" - } - }, "latest-version": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", @@ -11800,33 +11122,6 @@ "package-json": "^6.3.0" } }, - "lazystream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", - "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", - "dev": true, - "requires": { - "readable-stream": "^2.0.5" - } - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, - "lead": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", - "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", - "dev": true, - "requires": { - "flush-write-stream": "^1.0.2" - } - }, "less": { "version": "3.11.1", "resolved": "https://registry.npmjs.org/less/-/less-3.11.1.tgz", @@ -11937,59 +11232,12 @@ "immediate": "~3.0.5" } }, - "liftoff": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", - "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", - "dev": true, - "requires": { - "extend": "^3.0.0", - "findup-sync": "^3.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" - } - }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", "dev": true }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "dependencies": { - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==" - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=" - } - } - }, "loader-runner": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", @@ -12019,8 +11267,7 @@ "lodash": { "version": "4.17.19", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", - "dev": true + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" }, "lodash-es": { "version": "4.17.15", @@ -12145,6 +11392,15 @@ } } }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "dev": true, + "requires": { + "es5-ext": "~0.10.2" + } + }, "magic-string": { "version": "0.25.7", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", @@ -12237,15 +11493,6 @@ } } }, - "make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } - }, "mamacro": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", @@ -12291,119 +11538,6 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.2.tgz", "integrity": "sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw==" }, - "matchdep": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", - "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", - "dev": true, - "requires": { - "findup-sync": "^2.0.0", - "micromatch": "^3.0.4", - "resolve": "^1.4.0", - "stack-trace": "0.0.10" - }, - "dependencies": { - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "requires": { - "fill-range": "^7.0.1" - } - }, - "chokidar": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", - "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "normalize-path": "~3.0.0", - "readdirp": "~3.4.0" - } - }, - "expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "requires": { - "homedir-polyfill": "^1.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "findup-sync": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", - "dev": true - }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "optional": true - }, - "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==" - }, - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "requires": { - "is-extglob": "^2.1.0" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" - }, - "mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.0.0.tgz", - "integrity": "sha512-PiVO95TKvhiwgSwg1IdLYlCTdul38yZxZMIcnDSFIBUm4BNZha2qpQ4GpJ++15bHoKDtrW2D69lMfFwdFYtNZQ==" - }, - "readdirp": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", - "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", - "requires": { - "picomatch": "^2.2.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "requires": { - "is-number": "^7.0.0" - } - } - } - }, "matched": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/matched/-/matched-1.0.2.tgz", @@ -12459,6 +11593,22 @@ } } }, + "memoizee": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", + "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.45", + "es6-weak-map": "^2.0.2", + "event-emitter": "^0.3.5", + "is-promise": "^2.1", + "lru-queue": "0.1", + "next-tick": "1", + "timers-ext": "^0.1.5" + } + }, "memory-fs": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", @@ -12970,18 +12120,23 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, - "mute-stdout": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", - "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", - "dev": true - }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -13430,7 +12585,8 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true }, "normalize-range": { "version": "0.1.2", @@ -13449,15 +12605,6 @@ "resolved": "https://registry.npmjs.org/normalizr/-/normalizr-3.6.0.tgz", "integrity": "sha512-25cd8DiDu+pL46KIaxtVVvvEPjGacJgv0yUg950evr62dQ/ks2JO1kf7+Vi5/rMFjaSTSTls7aCnmRlUSljtiA==" }, - "now-and-later": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", - "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", - "dev": true, - "requires": { - "once": "^1.3.2" - } - }, "npm-bundled": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", @@ -14176,18 +13323,6 @@ "object-keys": "^1.0.11" } }, - "object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", - "dev": true, - "requires": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - } - }, "object.getownpropertydescriptors": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", @@ -14279,16 +13414,6 @@ } } }, - "object.map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", - "dev": true, - "requires": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - } - }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", @@ -14298,16 +13423,6 @@ "isobject": "^3.0.1" } }, - "object.reduce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", - "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", - "dev": true, - "requires": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - } - }, "object.values": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", @@ -14587,15 +13702,6 @@ } } }, - "ordered-read-streams": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", - "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", - "dev": true, - "requires": { - "readable-stream": "^2.0.1" - } - }, "original": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", @@ -14617,15 +13723,6 @@ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true, - "requires": { - "lcid": "^1.0.0" - } - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -14922,36 +14019,11 @@ "safe-buffer": "^5.1.1" } }, - "parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", - "dev": true, - "requires": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "dev": true - }, - "parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=" + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true }, "parse5": { "version": "5.1.1", @@ -15029,21 +14101,6 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, - "path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", - "dev": true, - "requires": { - "path-root-regex": "^0.1.0" - } - }, - "path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", - "dev": true - }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -15090,7 +14147,8 @@ "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true }, "pidtree": { "version": "0.3.0", @@ -15182,29 +14240,6 @@ "find-up": "^2.1.0" } }, - "plugin-error": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", - "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", - "dev": true, - "requires": { - "ansi-colors": "^1.0.1", - "arr-diff": "^4.0.0", - "arr-union": "^3.1.0", - "extend-shallow": "^3.0.2" - }, - "dependencies": { - "ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "dev": true, - "requires": { - "ansi-wrap": "^0.1.0" - } - } - } - }, "pluralize": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", @@ -15930,10 +14965,10 @@ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, - "pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "prettier": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", + "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", "dev": true }, "private": { @@ -16444,67 +15479,6 @@ "util-promisify": "^2.1.0" } }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - }, - "dependencies": { - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - } - } - }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", @@ -16560,15 +15534,6 @@ } } }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, - "requires": { - "resolve": "^1.1.6" - } - }, "reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -16688,346 +15653,107 @@ "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - } - } - } - }, - "regexpu-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", - "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", - "dev": true, - "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" - } - }, - "registry-auth-token": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.1.1.tgz", - "integrity": "sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, - "registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, - "regjsgen": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", - "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==", - "dev": true - }, - "regjsparser": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", - "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - } - } - }, - "release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", - "dev": true, - "requires": { - "es6-error": "^4.0.1" - } - }, - "remove-bom-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", - "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5", - "is-utf8": "^0.2.1" - } - }, - "remove-bom-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", - "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", - "dev": true, - "requires": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", - "dev": true - }, - "replace-homedir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", - "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1", - "is-absolute": "^1.0.0", - "remove-trailing-separator": "^1.1.0" - } - }, - "replace-in-file": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/replace-in-file/-/replace-in-file-5.0.2.tgz", - "integrity": "sha512-1Vc7Sbr/rTuHgU1PZuBb7tGsFx3D4NKdhV4BpEF2MuN/6+SoXcFtx+dZ1Zz+5Dq4k5x9js87Y+gXQYPTQ9ppkA==", - "dev": true, - "requires": { - "chalk": "^3.0.0", - "glob": "^7.1.6", - "yargs": "^15.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "yargs": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", - "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.1" + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimstart": "^1.0.0" } }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "string.prototype.trimright": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", + "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimend": "^1.0.0" } } } }, + "regexpu-core": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", + "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.2.0" + } + }, + "registry-auth-token": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.1.1.tgz", + "integrity": "sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA==", + "dev": true, + "requires": { + "rc": "^1.2.8" + } + }, + "registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "dev": true, + "requires": { + "rc": "^1.2.8" + } + }, + "regjsgen": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", + "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==", + "dev": true + }, + "regjsparser": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", + "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -17165,15 +15891,6 @@ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", "dev": true }, - "resolve-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", - "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", - "dev": true, - "requires": { - "value-or-function": "^3.0.0" - } - }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -17807,15 +16524,6 @@ "semver": "^5.3.0" } }, - "semver-greatest-satisfied-range": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", - "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", - "dev": true, - "requires": { - "sver-compat": "^1.5.0" - } - }, "semver-intersect": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.4.0.tgz", @@ -18431,12 +17139,6 @@ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" }, - "sparkles": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", - "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", - "dev": true - }, "spawn-wrap": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", @@ -18666,12 +17368,6 @@ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", "dev": true }, - "stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", - "dev": true - }, "stackframe": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.1.0.tgz", @@ -18713,6 +17409,12 @@ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "dev": true }, + "stdin": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/stdin/-/stdin-0.0.1.tgz", + "integrity": "sha1-0wQZgarsPf28d6GzjWNy449ftx4=", + "dev": true + }, "stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", @@ -18804,12 +17506,6 @@ "stubs": "^3.0.0" } }, - "stream-exhaust": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", - "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", - "dev": true - }, "stream-http": { "version": "2.8.3", "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", @@ -19117,15 +17813,6 @@ "ansi-regex": "^2.0.0" } }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", @@ -19295,16 +17982,6 @@ "has-flag": "^3.0.0" } }, - "sver-compat": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", - "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", - "dev": true, - "requires": { - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" - } - }, "svgo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", @@ -19689,6 +18366,24 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "dev": true, + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -19705,28 +18400,12 @@ "xtend": "~4.0.1" } }, - "through2-filter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", - "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", - "dev": true, - "requires": { - "through2": "~2.0.0", - "xtend": "~4.0.0" - } - }, "thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, - "time-stamp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", - "dev": true - }, "timers-browserify": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", @@ -19736,6 +18415,16 @@ "setimmediate": "^1.0.4" } }, + "timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "dev": true, + "requires": { + "es5-ext": "~0.10.46", + "next-tick": "1" + } + }, "timsort": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", @@ -19751,16 +18440,6 @@ "os-tmpdir": "~1.0.2" } }, - "to-absolute-glob": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", - "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", - "dev": true, - "requires": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" - } - }, "to-array": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", @@ -19826,15 +18505,6 @@ "is-number": "^7.0.0" } }, - "to-through": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", - "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", - "dev": true, - "requires": { - "through2": "^2.0.3" - } - }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -20036,41 +18706,12 @@ "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", "dev": true }, - "unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", - "dev": true - }, "underscore": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", "dev": true }, - "undertaker": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", - "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "bach": "^1.0.0", - "collection-map": "^1.0.0", - "es6-weak-map": "^2.0.1", - "last-run": "^1.1.0", - "object.defaults": "^1.0.0", - "object.reduce": "^1.0.0", - "undertaker-registry": "^1.0.0" - } - }, - "undertaker-registry": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", - "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", - "dev": true - }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -20141,16 +18782,6 @@ "imurmurhash": "^0.1.4" } }, - "unique-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", - "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", - "dev": true, - "requires": { - "json-stable-stringify-without-jsonify": "^1.0.1", - "through2-filter": "^3.0.0" - } - }, "unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", @@ -20561,12 +19192,6 @@ "builtins": "^1.0.3" } }, - "value-or-function": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", - "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", - "dev": true - }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -20590,79 +19215,6 @@ "extsprintf": "^1.2.0" } }, - "vinyl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", - "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", - "dev": true, - "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - } - }, - "vinyl-fs": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", - "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", - "dev": true, - "requires": { - "fs-mkdirp-stream": "^1.0.0", - "glob-stream": "^6.1.0", - "graceful-fs": "^4.0.0", - "is-valid-glob": "^1.0.0", - "lazystream": "^1.0.0", - "lead": "^1.0.0", - "object.assign": "^4.0.4", - "pumpify": "^1.3.5", - "readable-stream": "^2.3.3", - "remove-bom-buffer": "^3.0.0", - "remove-bom-stream": "^1.2.0", - "resolve-options": "^1.1.0", - "through2": "^2.0.0", - "to-through": "^2.0.0", - "value-or-function": "^3.0.0", - "vinyl": "^2.0.0", - "vinyl-sourcemap": "^1.1.0" - }, - "dependencies": { - "is-valid-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", - "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", - "dev": true - } - } - }, - "vinyl-sourcemap": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", - "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", - "dev": true, - "requires": { - "append-buffer": "^1.0.2", - "convert-source-map": "^1.5.0", - "graceful-fs": "^4.1.6", - "normalize-path": "^2.1.1", - "now-and-later": "^2.0.0", - "remove-bom-buffer": "^3.0.0", - "vinyl": "^2.0.0" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, "vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", @@ -21661,12 +20213,6 @@ "isexe": "^2.0.0" } }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", - "dev": true - }, "widest-line": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", @@ -21909,79 +20455,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "yargs": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", - "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", - "dev": true, - "requires": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.0" - }, - "dependencies": { - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - } - } - }, - "yargs-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", - "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", - "dev": true, - "requires": { - "camelcase": "^3.0.0" - }, - "dependencies": { - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - } - } - }, - "yazl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", - "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", - "dev": true, - "requires": { - "buffer-crc32": "~0.2.3" - } - }, "yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", diff --git a/package.json b/package.json index e2996467de..011b676947 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stratos", - "version": "4.0.0", + "version": "4.0.1", "description": "Stratos Console", "main": "index.js", "scripts": { diff --git a/src/frontend/packages/example-theme/_index.scss b/src/frontend/packages/example-theme/_index.scss index a8d3f300e1..b837542374 100644 --- a/src/frontend/packages/example-theme/_index.scss +++ b/src/frontend/packages/example-theme/_index.scss @@ -2,8 +2,60 @@ // Custom Theme @import './sass/custom'; +@import './sass/custom/acme-colors'; + +// Style overrides +@import './sass/custom/acme'; @function stratos-theme() { $theme: stratos-theme-helper($stratos-theme); - @return $theme -} \ No newline at end of file + @return ( + default: create-custom-theme($stratos-theme), + dark: create-dark-theme() + ); +} + +@function create-custom-theme($base-theme) { + $theme: stratos-theme-helper($base-theme); + + // Modify some of the colors + $app-theme: map-get($theme, app-theme); + $app-theme: map-merge($app-theme, ( + header-background-color: $acme-text, + header-foreground-color: $acme-dark-gray, + header-background-span: false, + stratos-title-show-text: true, + user-avatar-background-color: $acme-text, + user-avatar-foreground-color: $acme-primary-color, + user-avatar-header-invert-colors: false, + side-nav: ( + background: $acme-dark-gray, + text: $acme-text-gray, + active: $acme-side-nav-active, + active-text: $acme-text-gray, + hover: $acme-side-nav-active, + hover-text: $acme-text-gray + ) + )); + + $theme: map-merge($theme, ( + app-theme: $app-theme + )); + + @return $theme; +} + +@function create-dark-theme() { + $theme: create-custom-theme($stratos-dark-theme); + + $app-theme: map-get($theme, app-theme); + $app-theme: map-merge($app-theme, ( + header-background-color: $acme-dark-gray, + header-foreground-color: $acme-text, + )); + + $theme: map-merge($theme, ( + app-theme: $app-theme + )); + @return $theme; +} diff --git a/src/frontend/packages/example-theme/assets/core/nav-logo.png b/src/frontend/packages/example-theme/assets/core/nav-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..397545f790c3239791281ecd78d225af67dc4507 GIT binary patch literal 18891 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 literal 0 HcmV?d00001 diff --git a/src/frontend/packages/example-theme/package.json b/src/frontend/packages/example-theme/package.json index 704b682b04..f2006d3cc3 100644 --- a/src/frontend/packages/example-theme/package.json +++ b/src/frontend/packages/example-theme/package.json @@ -6,7 +6,7 @@ "assets/core": "core/assets", "assets/favicon.ico": "favicon.ico" }, - "theme":{ + "theme": { "loadingCss": "loader/loading.css", "loadingHtml": "loader/loading.html" } diff --git a/src/frontend/packages/example-theme/sass/custom.scss b/src/frontend/packages/example-theme/sass/custom.scss index e3eb84c38a..68fa1ded70 100644 --- a/src/frontend/packages/example-theme/sass/custom.scss +++ b/src/frontend/packages/example-theme/sass/custom.scss @@ -14,5 +14,3 @@ $stratos-dark-theme: mat-dark-theme($acme-theme-primary, $acme-theme-primary, $a // Default Theme $stratos-theme: mat-light-theme($acme-theme-primary, $acme-theme-primary, $acme-theme-warn); - -@import 'custom/acme'; diff --git a/src/frontend/packages/example-theme/sass/custom/acme-colors.scss b/src/frontend/packages/example-theme/sass/custom/acme-colors.scss index 878c2cda32..6da6321206 100644 --- a/src/frontend/packages/example-theme/sass/custom/acme-colors.scss +++ b/src/frontend/packages/example-theme/sass/custom/acme-colors.scss @@ -1,3 +1,5 @@ +// These colors need tidying, some aren't used + $acme-primary: #00c081; $acme-secondary: #00243e; $acme-text: #fff; @@ -5,3 +7,17 @@ $acme-text-gray: #ccc; $acme-button-gray: #888; $acme-dark-blue: #06253a; $acme-blue: #073155; + +// Acme Brand primary colour +$acme-primary-color: #035a97; + +// Secondary blue colour +$acme-secondary: #035a97; +$acme-text: #fff; +$acme-text-gray: #ccc; +$acme-dark-blue: #06253a; +$acme-blue: #073155; +$acme-side-nav: $acme-secondary; +$acme-side-nav-active: #003358; + +$acme-dark-gray: #333 diff --git a/src/frontend/packages/example-theme/sass/custom/acme.scss b/src/frontend/packages/example-theme/sass/custom/acme.scss index 75794ebb6e..e4c53f3cbf 100644 --- a/src/frontend/packages/example-theme/sass/custom/acme.scss +++ b/src/frontend/packages/example-theme/sass/custom/acme.scss @@ -1,71 +1,11 @@ -// Style overrides -// Acme Brand primary colour -$acme-primary-color: #035a97; +@import './acme-colors'; -// Secondary blue colour -$acme-secondary: #035a97; -$acme-text: #fff; -$acme-text-gray: #ccc; -$acme-dark-blue: #06253a; -$acme-blue: #073155; -$acme-side-nav: $acme-secondary; -$acme-side-nav-active: #003358; +// Style overrides body.stratos { - app-page-subheader { - .page-subheader { - background-color: #fff; - color: #333; - } - } - - .page-header__divider { - color: #333; - } - - .page-header__menu-button-icon { - background-color: $acme-primary-color; - color: $acme-text; - } - - .mat-tab-nav-bar.mat-primary.mat-background-primary { - .mat-tab-links { - background-color: #fff; - color: #333; - } - .mat-tab-link { - color: #333; - } - .mat-ink-bar { - background-color: $acme-primary-color; + .favorite-list { + .app-no-content-container { + color: $acme-dark-blue; } } - - .stratos-title__logo { - width: 160px; - } - - .page-header .mat-toolbar.mat-primary { - background-color: #fff; - color: #333; - } - - // Use Acme Blue Side Navigation - .side-nav, - .side-nav__top, - .side-nav__item--active:hover { - background-color: $acme-side-nav; - } - .side-nav__item--active, - .side-nav__item:hover { - background-color: $acme-side-nav-active; - } - .side-nav__bottom { - color: $acme-text-gray; - } - - // Show the text Stratos beneath the Acme logo - .stratos-title > .stratos-title__header { - display: inline-block; - } } diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 0000000000..b37ff6e4c3 --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,23 @@ +# Dependencies +/node_modules + +# Production +/build + +# Generated files +.docusaurus +.cache-loader + +# Misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +/website/build +/website/site-dist \ No newline at end of file diff --git a/website/README.md b/website/README.md new file mode 100644 index 0000000000..b70a5be1d0 --- /dev/null +++ b/website/README.md @@ -0,0 +1,40 @@ +# Stratos Website + +This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator. + +> Run the commands below in the `website` folder. + +### Installing Dependencies + +``` +$ npm install +``` + +### Local Development + +``` +$ npm start +``` + +This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server. + +> Note this command will open a web browser on the locally served site (http://localhost:3000) + +### Build + +``` +$ ./deploy.sh -b +``` + +This command generates static content into the `build` directory and can be served using any static contents hosting service. + +### Deployment + +We use GitHub pages - this command is a convenient way to build the website and push to the `gh-pages` branch. + +``` +$ ./deploy.sh +``` + + +> Note: The website is deployed to the GitHub Repository `cf-stratos/wesbite` which hosts https://stratos.app \ No newline at end of file diff --git a/website/babel.config.js b/website/babel.config.js new file mode 100644 index 0000000000..e00595dae7 --- /dev/null +++ b/website/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: [require.resolve('@docusaurus/core/lib/babel/preset')], +}; diff --git a/website/deploy.sh b/website/deploy.sh new file mode 100755 index 0000000000..bdf2c28552 --- /dev/null +++ b/website/deploy.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash + +# Build and deploy the website + +set -euo pipefail + +ARG=${1:-deploy} + +# wesbite folder +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" + +pushd $DIR > /dev/null +echo "Building website ..." + +# Copy helm chart readme +cat << EOF > ./docs/deploy/kubernetes/install.md +--- +id: helm-installation +title: Deploying Using Helm +sidebar_label: Deploy using Helm +--- +EOF + +# Concatentate the helm chart readme file +tail -n +2 ${DIR}/../deploy/kubernetes/console/README.md >> ./docs/deploy/kubernetes/install.md + +# License file + +cat << EOF > ./docs/license.md +--- +id: license +title: License +sidebar_label: License +--- + +Stratos is licensed under the Apache 2.0 Software License, shown below: + +\`\`\` +EOF + +cat ${DIR}/../LICENSE >> ./docs/license.md +echo "\`\`\`" >> ./docs/license.md + +# Contributing + +cat << EOF > ./docs/developer/contributing.md +--- +title: Contributing +sidebar_label: Contributing +--- + +EOF + +tail -n +2 ${DIR}/../CONTRIBUTING.md >> ./docs/developer/contributing.md + +# Copy and generate files only +if [ "$ARG" == "-g" ]; then + exit 0 +fi + +npm run build + +# Generate and build only +if [ "$ARG" == "-b" ]; then + exit 0 +fi + +msg="Website update: $(date)" + +tmpdir=./site-dist +rm -rf site-dist +mkdir -p $tmpdir +pushd $tmpdir +echo "Cloning web site" +git clone git@github.com:cf-stratos/website.git + +echo "Copying newer site content ..." +rsync --delete --exclude=.git -r $DIR/build/ ./website + +cd website + +echo "Adding CNAME file" +echo "stratos.app" > CNAME + +if [ -z "$(git status --porcelain)" ]; then + echo "No changes to publish" +else + echo "Website has changed" + echo "Adding all files" + git add -A + git commit -m "${msg}" + echo "Pushing changes ..." + git push +fi + +popd > /dev/null + +echo "Removing website dist folder" +rm -rf $tmpdir + +popd > /dev/null diff --git a/docs/bosh-metrics.md b/website/docs/advanced/bosh-metrics.md similarity index 98% rename from docs/bosh-metrics.md rename to website/docs/advanced/bosh-metrics.md index f2bd8e5226..8b71e0d51a 100644 --- a/docs/bosh-metrics.md +++ b/website/docs/advanced/bosh-metrics.md @@ -1,4 +1,7 @@ -# Connecting Prometheus-boshrelease to Stratos +--- +title: Connecting Prometheus-boshrelease to Stratos +sidebar_label: Connecting Prometheus-boshrelease +--- Stratos can show some metrics stored in Prometheus. diff --git a/docs/invite-user-guide.md b/website/docs/advanced/invite-user-guide.md similarity index 92% rename from docs/invite-user-guide.md rename to website/docs/advanced/invite-user-guide.md index 58f9283f96..be3690db5d 100644 --- a/docs/invite-user-guide.md +++ b/website/docs/advanced/invite-user-guide.md @@ -1,4 +1,7 @@ -# Invite User Guide +--- +title: Configuring Invite User Support +sidebar_label: Configuring User Invites +--- Stratos provides a way for Cloud Foundry administrators and organization managers to invite users to an organization or space. @@ -39,14 +42,14 @@ In the above example the client id is `stratos-invite` and client secret is `pas Alternatively, you can use an existing UAA Client, if one is already available with the appropriate scopes. -> Note: You will need the Client ID and Client Secret used above when enabling User Invite support in the Stratos UI +> Note: You will need the Client ID and Client Secret used above when enabling User Invite support in Stratos ## Configuring SMTP Server details and (optionally) modifying Email Templates Configuration depends on how you have deployed Stratos. -1. For cf push, see [Configuration for CF Push](#Configuration-for-CF-Push) -1. For Kubernetes with Helm, see: [Configuration for Helm Installation](#Configuration-for-Helm-Installation) +1. For cf push, see [Configuration for CF Push](#configuration-for-cf-push) +1. For Kubernetes with Helm, see: [Configuration for Helm Installation](#configuration-for-helm-installation) ## Enabling User Invite support in Stratos @@ -56,7 +59,7 @@ This action must be performed by an Administrator in Stratos. 1) Use the `Configure` button in the `User Invitation Support` section. 1) Supply the uaa client id and secret and click `Configure` -> Note: If Stratos has been deployed via `cf push` and the steps under the `Pre-configure invite UAA client` header in the [CF deploy guide](../deploy/cloud-foundry/README.md) have been followed, you will not follow these steps for the default CF. +> Note: If Stratos has been deployed via `cf push` and the steps under the `Pre-configure invite UAA client` header in the [CF deploy guide](../deploy/cloud-foundry/cloud-foundry) have been followed, you will not follow these steps for the default CF. ## Configuration for CF Push @@ -142,7 +145,7 @@ When developing locally, we recommend using [mailcatcher](https://mailcatcher.me To install mailcatcher via docker, use the following command: `docker run -d -p 1080:80 -p 1025:25 --name mail tophfr/mailcatcher`. Once mailcatcher is installed, continue to follow the instructions below. -SMTP server details can be set via rhe usual environment variable approach or, when running locally, in the `jetstream/config.properties` file (see Backend Development - Configuration in [developers-guide](./developers-guide.md)). The config settings, with example values, are as follows: +SMTP server details can be set via rhe usual environment variable approach or, when running locally, in the `jetstream/config.properties` file (see Backend Development - Configuration in [developers-guide](../developer/introduction)). The config settings, with example values, are as follows: ``` SMTP_FROM_ADDRESS=Stratos<test@test.com> diff --git a/docs/sso.md b/website/docs/advanced/sso.md similarity index 96% rename from docs/sso.md rename to website/docs/advanced/sso.md index 8f9f747c67..6dfb23a357 100644 --- a/docs/sso.md +++ b/website/docs/advanced/sso.md @@ -1,4 +1,7 @@ -# Single Sign On +--- +title: Configuring Single Sign On +sidebar_label: Configuring Single Sign On +--- By default, Stratos will authenticate against a UAA using username and password, for both logging into Stratos and when connecting Cloud Foundry endpoints. diff --git a/docs/access.md b/website/docs/deploy/access.md similarity index 97% rename from docs/access.md rename to website/docs/deploy/access.md index e55688dc5c..81ffe6a171 100644 --- a/docs/access.md +++ b/website/docs/deploy/access.md @@ -1,4 +1,8 @@ -# Accessing Stratos +--- +id: access +title: Accessing Stratos +sidebar_label: Accessing Stratos +--- Depending on the deployment mode, you may require access to an UAA. Stratos also has the option to configure a local user account which removes the need for a UAA in non-Cloud Foundry deployments. diff --git a/deploy/all-in-one/README.md b/website/docs/deploy/all-in-one.md similarity index 91% rename from deploy/all-in-one/README.md rename to website/docs/deploy/all-in-one.md index a9d392934e..a8a8024c80 100644 --- a/deploy/all-in-one/README.md +++ b/website/docs/deploy/all-in-one.md @@ -1,12 +1,26 @@ -# Deploying with the All-In-One Docker Container +--- +id: all-in-one +title: Deploying with the All-In-One Docker Container +sidebar_label: Docker All-in-One +--- The all-in-one container sets up the Stratos components in a single container. ## Requirements: -You will need to have installed Docker, see: +You will need to have installed Docker, see [Docker Installation Documentation](https://docs.docker.com/engine/installation/). -* [Docker](https://docs.docker.com/engine/installation/) +## Quick Start + +Run Stratos in Docker locally: + +``` +$ docker run -p 4443:443 splatform/stratos:latest +``` + +Once that has finished, you can then access Stratos by visiting [https://localhost:4443](https://localhost:4443). + +You can configure a local admin account and set the password for future logins. ## Note regarding the Stratos Session Store Secret diff --git a/website/docs/deploy/cloud-foundry/cf-troubleshooting.md b/website/docs/deploy/cloud-foundry/cf-troubleshooting.md new file mode 100644 index 0000000000..c1b0f3ce71 --- /dev/null +++ b/website/docs/deploy/cloud-foundry/cf-troubleshooting.md @@ -0,0 +1,157 @@ +--- +title: Troubleshooting Cloud Foundry Deployment +sidebar_label: Troubleshooting +--- + +## Creating logs for recent deployments +To create a log file of the push +``` +cf push | tee cfpush.log +``` + +To create a log file of recent console output +``` +cf logs console --recent | tee cfconsole.log +``` +>**NOTE** If the name of the application has been changed from `console` in the manifest file please also change the name in the logs statement + +## Turning on backend debugging logs + +The `LOG_LEVEL` environment variable controls the backend logs + +``` +cf set-env console LOG_LEVEL debug +cf restart console +cf logs console +``` + +would output more debugging output such as + +``` + 2018-10-24T14:47:36.91+0200 [RTR/1] OUT console.my.domain - [2018-10-24T12:47:36.850+0000] "GET /pp/v1/-o1F0L956QhAIK7R56Uc1lMh5L4/apps/3ddc0bc6-a645-4449-9d1b-6fe86146cf61/ssh/0 HTTP/1.1" 500 0 0 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0" "10.228.194.8:42182" "192.168.35.91:61044" x_forwarded_for:"10.228.194.8" x_forwarded_proto:"https" vcap_request_id:"182dddeb-d877-4d58-45f7-0bd886d1caf6" response_time:0.066925325 app_id:"0ba408ef-d0e6-4ab8-96bb-0bc078b8d8fb" app_index:"0" x_b3_traceid:"d166622a0d612fea" x_b3_spanid:"d166622a0d612fea" x_b3_parentspanid:"-" + 2018-10-24T14:47:36.91+0200 [RTR/1] OUT + 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] sessionCleanupMiddleware + 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] errorLoggingMiddleware + 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT INFO[Wed Oct 24 12:47:36 UTC 2018] Not redirecting this request + 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] getSession + 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] getSessionValue + 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] getSession + 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] setStaticContentHeadersMiddleware + 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] urlCheckMiddleware + 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] sessionMiddleware + 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] getSessionValue + 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] getSession + 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] xsrfMiddleware + 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] GetCNSIRecord + 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] Find + 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] decryptToken + 2018-10-24T14:47:36.85+0200 [APP/PROC/WEB/0] OUT DEBU[Wed Oct 24 12:47:36 UTC 2018] Decrypt + [...] +``` + +## Application Security Groups + +If you have problems when deploying Stratos as a Cloud Foundry application, check that the Application Security Group you have will allow Stratos to communicate with the Cloud Foundry API. + +For information on the default ASGs, see [here](https://docs.cloudfoundry.org/concepts/asg.html#default-asg). + +To configure a new ASG for the organization and space that are using Stratos, first create a new ASG definition, for example: + +``` +[ + { + "destination":"0.0.0.0-255.255.255.255", + "protocol":"all" + } +] +``` + +Save this to a file, e.g. `my-asg.json`. + +> Note: this allows example all network traffic on all IP ranges - we don't recommend using this. + +Unbind the existing ASG for you organization (`ORG`) and space (`SPACE`) with: + +``` +cf unbind-security-group public_networks ORG SPACE +``` + +Create a new ASG using the definition you saved to a file and give it a name, with: + +``` +cf create-security-group NAME my-asg.json +``` + +Bind this ASG to your `ORG` and `SPACE` with: + +``` +cf bind-security-group NAME ORG SPACE +``` + + +## Console fails to start + +The Stratos Console will automatically detect the API endpoint for your Cloud Foundry. To do so, it relies on the `cf_api` value inside the `VCAP_APPLICATION` environment variable. +To check if the variable is present, use the CF CLI to list environment variables, and inspect the `VCAP_APPLICATION` variable under `System-Provided`. + +``` +$ cf env console +Getting env variables for app console in org SUSE / space dev as admin... +OK + +System-Provided: + + + { + "VCAP_APPLICATION": { + "application_id": ..., + "application_name": "console", + "application_uris": ... + "application_version": ..., + "cf_api": "http://api.cf-dev.io", + ... + } + + No user-defined env variables have been set + ... +``` + +If the `cf_api` environment variable is absent then set the `CF_API_URL` variable. See the following _Setting the `CF_API_URL` env variable in the manifest_ section. + + +However, if the `cf_api` environment variable is present, and an HTTP address is specified, it is possible that insecure traffic may be blocked. See the following _Setting the `CF_API_FORCE_SECURE` env variable in the manifest_ section. + + +#### Setting the `CF_API_URL` env variable in the manifest + +To specify the Cloud Foundry API endpoint, add the `CF_API_URL` variable to the manifest, for example: + +``` +applications: +- name: console + memory: 256M + disk_quota: 256M + host: console + timeout: 180 + buildpack: https://github.com/cloudfoundry/stratos-buildpack + health-check-type: port + env: + CF_API_URL: https://<<CLOUD FOUNDRY API ENDPOINT>>> +``` + +#### Setting the `CF_API_FORCE_SECURE` env variable in the manifest + +To force the console to use secured communication with the Cloud Foundry API endpoint (HTTPS rather than HTTP), specify the `CF_API_FORCE_SECURE` environment in the manifest, for example: + +``` +applications: +- name: console + memory: 256M + disk_quota: 256M + host: console + timeout: 180 + buildpack: https://github.com/cloudfoundry/stratos-buildpack + health-check-type: port + env: + CF_API_FORCE_SECURE: true +``` diff --git a/website/docs/deploy/cloud-foundry/cloud-foundry.md b/website/docs/deploy/cloud-foundry/cloud-foundry.md new file mode 100644 index 0000000000..1f876ced45 --- /dev/null +++ b/website/docs/deploy/cloud-foundry/cloud-foundry.md @@ -0,0 +1,162 @@ +--- +id: cloud-foundry +title: Deploying as a Cloud Foundry Application +sidebar_label: Deploy on Cloud Foundry +--- + +## Deployment Steps + +Stratos can be pushed as an application to Cloud Foundry. + +You can do it in two ways: + +1. [Deploy Stratos from source](#deploy-stratos-from-source) +1. [Deploy Stratos from docker image](#deploy-stratos-from-docker-image) + +You will then be able to open a web browser and navigate to the console URL: + +`https://console.<DOMAIN>` + +Where `<DOMAIN>` is the default domain configured for your Cloud Foundry cluster. + +To login use the following credentials detailed [here](../access). + +If you run into issues, please refer to the [Troubleshooting Guide](cf-troubleshooting) below. + +> The console will pre-configure the host Cloud Foundry endpoint. No other CF instance should be registered unless the instructions in + the section [Associate Cloud Foundry database service](#associate-cloud-foundry-database-service) are followed. + All other deployment methods (helm, docker all-in-one, etc) allow the registration of multiple CF instances by default. + +Note: + +1. You need the cf CLI command line tool installed and available on the path. +1. You need to have configured the cf cli to point to your Cloud Foundry cluster, to be authenticated with your credentials and to be targeted at the organization and space where you want the console application be created. +1. You may need to configure Application Security Groups on your Cloud Foundry Cluster in order that Stratos can communicate with the Cloud Foundry API. See [below](#application-security-groups) for more information. +1. The Stratos Console will automatically detect the API endpoint for your Cloud Foundry. To do so, it relies on the `cf_api_url` value inside the `VCAP_APPLICATION` environment variable. If this is not provided by your Cloud Foundry platform, then you must manually update the application manifest as described [below](#console-fails-to-start). + +### Running Stratos in Production Environments + +Please be aware of the following when running Stratos in a production environment: + +#### Configure a Session Store Secret + +Stratos uses a Session Store Secret to protect the user session cookie. We recommend that you set your own value for this secret - choosing an alphanumeric string of your choice. + +You can configure this secret by editing the application manifest and adding to the `env` section, e.g. + +``` +applications: +- name: console + ... memory, disk settings here + env: + SESSION_STORE_SECRET: <your session store secret here> +``` + +#### Pre-configure UAA client used for user invites + +> You can skip this step and configure any CFs invite clients via the Stratos UI. + + To set the UAA client for user invites, supply the client id and client secret as environment variables as shown below: + + ``` + INVITE_USER_CLIENT_ID=<UAA_CLIENT_ID> + INVITE_USER_CLIENT_SECRET=<UAA_CLIENT_SECRET> + ``` + +This will set the the UAA client and UAA secret used to invite users for the default CF only. + +See the [invite users guide](../guides/admin/invite-user-guide) for more information about user invites in Stratos. + +#### Use of the Default Embedded SQLite Database + +We do not recommend deploying Stratos to a production environment using the default embedded SQLite Database. Instead we recommend creating +and binding a database service instance to Stratos - for more information see [here](db-migration). + +### Deploy Stratos from source + +To do so, `clone` the **stratos** repository, `cd` into the newly cloned repository and `push` to Cloud Foundry. This can be done with: + +``` +git clone https://github.com/cloudfoundry/stratos +cd stratos +git checkout tags/stable -b stable +./build/store-git-metadata.sh +cf push +``` + +If the cf push exceeds the time allowed see the instructions [here](#pre-building-the-ui) + +#### Pre-building the UI + +Due to the memory usage of the Angular compiler (see below), when deployed to Cloud Foundry via `cf push`, Stratos does not use AOT (Ahead-of-Time) compilation. + +If you wish to enable AOT or reduce the push time, you can pre-build the UI before pushing. + +This can be done with: + +``` +git clone https://github.com/cloudfoundry/stratos +cd stratos +npm install +npm run prebuild-ui +cf push +``` + +You will need a recent version of Node installed locally to do this. + +The `prebuild-ui` npm script performs a build of the front-end UI and then zips up the resulting folder into a package named `stratos-frontend-prebuild.zip`. The Stratos buildpack will unpack this zip file and use its contents instead of building the UI during staging, when this file is present. + + +#### Memory Usage + +The Stratos Cloud Foundry `manifest.yml` states that the application requires +`1512MB` of memory. This is required during the build process of the +application since building an angular2 app is a memory intensive process. The +memory limit can be scaled down after the app has been pushed, using the cf CLI. + +### Deploy Stratos from docker image + +Deploy Stratos using the [`splatform/stratos`](https://hub.docker.com/r/splatform/stratos) docker image + +> **NOTE:** Your Cloud Foundry must have docker support [enabled](https://docs.cloudfoundry.org/adminguide/docker.html#enable). + +``` +cf push console -o splatform/stratos:stable -m 128M -k 384M +``` +> Note: You can replace `console` in the command above with a name of your choice for the application + +Alternatively cf push using a manifest + +- download [manifest-docker.yml](../../../manifest-docker.yml) or create your own manifest file: + ```yaml + applications: + - name: console + docker: + image: splatform/stratos:stable + instances: 1 + memory: 128M + disk_quota: 384M + ``` +- now, you can simply push it to Cloud Foundry: + ``` + cf push -f manifest-docker.yml + ``` + +## Associate Cloud Foundry database service +Follow instructions [here](db-migration). + +## Use SSO Login + +By default Stratos will present its own login UI and only supports username and password authentication with your UAA. You can configure Stratos to use UAA's login UI by specifying the the `SSO_LOGIN` environment variable in the manifest, for example: + +``` +applications: +- name: console + ... memory, disk settings here + env: + SSO_LOGIN: true +``` + +When SSO Login is enabled, Stratos will also auto-connect to the Cloud Foundry it is deployed in using the token obtained during the SSO Login flow. + +For more information - see [Single-Sign On](../../advanced/sso). diff --git a/deploy/cloud-foundry/db-migration/README.md b/website/docs/deploy/cloud-foundry/db-migration.md similarity index 90% rename from deploy/cloud-foundry/db-migration/README.md rename to website/docs/deploy/cloud-foundry/db-migration.md index 281b5f9352..98cd856928 100644 --- a/deploy/cloud-foundry/db-migration/README.md +++ b/website/docs/deploy/cloud-foundry/db-migration.md @@ -1,6 +1,10 @@ -# Associate a Cloud Foundry database service +--- +id: db-migration +title: Associate a Cloud Foundry database service +sidebar_label: Database service bindings +--- -As described in the standard `cf push` instructions [here](../README.md) Stratos, when deployed via `cf push`, +As described in the standard `cf push` instructions [here](cloud-foundry) Stratos, when deployed via `cf push`, does not contain any way to persist data over application restarts and db entries such as registered endpoints and user tokens are lost. To resolve this a Cloud Foundry db service can be bound to to it. Run through the steps below to implement. diff --git a/deploy/kubernetes/README.md b/website/docs/deploy/kubernetes.md similarity index 80% rename from deploy/kubernetes/README.md rename to website/docs/deploy/kubernetes.md index 6d8d583560..55ed9cfeb2 100644 --- a/deploy/kubernetes/README.md +++ b/website/docs/deploy/kubernetes.md @@ -1,4 +1,8 @@ -# Deploying in Kubernetes +--- +id: kubernetes +title: Deploying in Kubernetes +sidebar_label: Overview +--- Stratos can be deployed to Kubernetes using [Helm](https://github.com/kubernetes/helm). @@ -12,4 +16,4 @@ You will need to have both the `kubectl` and `helm` CLIs installed and available The Stratos Helm chart contains a `README.md` file that contains installation instructions and configuration documentation. -This document is also available in our GitHub repository here: [README.md](https://github.com/cloudfoundry/stratos/blob/master/deploy/kubernetes/console/README.md). +This document is also available in [here](kubernetes/helm-installation). diff --git a/website/docs/deploy/kubernetes/install.md b/website/docs/deploy/kubernetes/install.md new file mode 100644 index 0000000000..fad7b188d4 --- /dev/null +++ b/website/docs/deploy/kubernetes/install.md @@ -0,0 +1,413 @@ +--- +id: helm-installation +title: Deploying Using Helm +sidebar_label: Deploy using Helm +--- + +Stratos is an Open Source Web-based Management console for Cloud Foundry. It allows users and administrators to both manage applications running in the Cloud Foundry cluster and perform cluster management tasks. + +## Installation + +Stratos can be installed to a Kubernetes cluster using Helm. Either Helm 2 or Helm 3 can be used, although we recommend using the newer Helm 3 version. + +Ensure the [Helm](https://github.com/helm/helm) client and [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) CLIs are installed. If you are using Helm 2, ensure you've initialized Tiller into your cluster with the appropriate +Service Account. + +The Helm chart is published to the Stratos Helm repository. + +You will need to have the Stratos Helm repository added to your Helm setup, if you do not, run: + +``` +helm repo add stratos https://cloudfoundry.github.io/stratos +``` + +Check the repository was successfully added by searching for the `console`, for example: + +``` +helm search repo console +NAME CHART VERSION APP VERSION DESCRIPTION +stratos/console 3.2.0 3.2.0 A Helm chart for deploying Stratos UI Console +``` + +> Note: Version numbers will depend on the version of Stratos available from the Helm repository + +> Note: Commands shown in this document are for Helm version 3. For Helm version 2, when installing, instead of supplying the name via the `--name` flag, it is supplied as the first argument, before the chart name. + +To install Stratos: + +``` +kubectl create namespace console +helm install my-console stratos/console --namespace=console +``` + +> **Note**: The first `kubectl` command will create a namespace for Stratos. With Helm 3 you must create a namespace before installing. +We recommend installing Stratos into a separate namespace. + +> **Note**: This assumes that a storage class exists in the Kubernetes cluster that has been marked as `default`. If no such storage class exists, a specific storage class needs to be specified, please see the following section *Specifying a custom Storage Class*. + +> You can change the namespace (--namespace) and the release name to values of your choice. + +This will create a Stratos instance named `my-console` in a namespace called `console` in your Kubernetes cluster. + +After the install, you should be able to access the Console in a web browser by following [the instructions](#accessing-the-console) below. + +Advanced installation topics are covered in the the [Advanced Topics](#advanced-topics) section below. + +# Helm Chart Configuration + +The following table lists the configurable parameters of the Stratos Helm chart and their default values. + +|Parameter|Description|Default| +|---|---|---| +|imagePullPolicy|Image pull policy|IfNotPresent| +|console.sessionStoreSecret|Secret to use when encrypting session tokens|auto-generated random value| +|console.ssoLogin|Whether to enable SSO Login and use the UAA Login UI instead of the built-in one|false| +|console.backendLogLevel|Log level for backend (info, debug)|info| +|console.service.externalIPs|External IPs to add to the console service|[]| +|console.service.loadBalancerIP|IP address to assign to the load balancer for the metrics service (if supported)|| +|console.service.loadBalancerSourceRanges|List of IP CIDRs allowed access to load balancer (if supported)|[]| +|console.service.type|Service type for the console (ClusterIP, NodePort, LoadBalancer, ExternalName etc)|ClusterIP| +|console.service.servicePort|Service port for the console service|443| +|console.service.externalName|External name for the console service when service type is ExternalName|| +|console.service.nodePort|Node port to use for the console service when service type is NodePort or LoadBalancer|| +|console.ingress.enabled|Enable ingress for the console service|false| +|console.ingress.host|Host for the ingress resource||| +|console.ingress.secretName|Name of an existing secret containing the TLS certificate for ingress||| +|console.service.http.enabled|Enabled HTTP access to the console service (as well as HTTPS)|false| +|console.service.http.servicePort|Service port for HTTP access to the console service when enabled|80| +|console.service.http.nodePort|Node port for HTTP access to the console service (as well as HTTPS)|| +|console.templatesConfigMapName|Name of config map that provides the template files for user invitation emails|| +|console.userInviteSubject|Email subject of the user invitation message|| +|console.techPreview|Enable/disable Tech Preview features|false| +|console.ui.listMaxSize|Override the default maximum number of entities that a configured list can fetch. When a list meets this amount additional pages are not fetched|| +|console.ui.listAllowLoadMaxed|If the maximum list size is met give the user the option to fetch all results|false| +|console.localAdminPassword|Use local admin user instead of UAA - set to a password to enable|| +|console.tlsSecretName|Secret containing TLS certificate to use for the Console|| +|console.mariadb.external|Use an external database instead of the built-in MariaDB|false| +|console.mariadb.database|Name of the database to use|console| +|console.mariadb.user|Name of the user for accessing the database|console| +|console.mariadb.userPassword|Password of the user for accessing the database. Leave blank for the built-in database to generate a random password|| +|console.mariadb.rootPassword|Password of the root user for accessing the database. Leave blank for the built-in database to generate a random password|| +|console.mariadb.host|Hostname of the database when using an external db|| +|console.mariadb.port|Port of the database when using an external db|3306| +|console.mariadb.tls|TLS mode when connecting to database (true, false, skip-verify, preferred)|false| +|uaa.endpoint|URL of the UAA endpoint to authenticate with || +|uaa.consoleClient|Client to use when authenticating with the UAA|cf| +|uaa.consoleClientSecret|Client secret to use when authenticating with the UAA|| +|uaa.consoleAdminIdentifier|Scope that identifies an admin user of Stratos (e.g. cloud_controller.admin|| +|uaa.skipSSLValidation|Skip SSL validation when when authenticating with the UAA|false| +|env.SMTP_AUTH|Authenticate against the SMTP server using AUTH command when Sending User Invite emails|false| +|env.SMTP_FROM_ADDRESS|From email address to use when Sending User Invite emails|| +|env.SMTP_USER|User name to use for authentication when Sending User Invite emails|| +|env.SMTP_PASSWORD|Password to use for authentication when Sending User Invite emails|| +|env.SMTP_HOST|Server host address to use for authentication when Sending User Invite emails|| +|env.SMTP_PORT|Server port to use for authentication when Sending User Invite emails|| +|kube.auth|Set to "rbac" if the Kubernetes cluster supports Role-based access control|"rbac"| +|kube.organization|Registry organization to use when pulling images|| +|kube.registry.hostname|Hostname of registry to use when pulling images|docker.io| +|kube.registry.username|Username to use when pulling images from the registry|| +|kube.registry.password|Password to use when pulling images from the registry|| +|console.podAnnotations|Annotations to be added to all pod resources|| +|console.podExtraLabels|Additional labels to be added to all pod resources|| +|console.statefulSetAnnotations|Annotations to be added to all statefulset resources|| +|console.statefulSetExtraLabels|Additional labels to be added to all statefulset resources|| +|console.deploymentAnnotations|Annotations to be added to all deployment resources|| +|console.deploymentExtraLabels|Additional labels to be added to all deployment resources|| +|console.jobAnnotations|Annotations to be added to all job resources|| +|console.jobExtraLabels|Additional labels to be added to all job resources|| +|console.service.annotations|Annotations to be added to all service resources|| +|console.service.extraLabels|Additional labels to be added to all service resources|| +|console.service.ingress.annotations|Annotations to be added to the ingress resource|| +|console.service.ingress.extraLabels|Additional labels to be added to the ingress resource|| +|console.nodeSelector|Node selectors to use for the console Pod|| +|mariadb.nodeSelector|Node selectors to use for the database Pod|| +|configInit.nodeSelector|Node selectors to use for the configuration Pod|| + +## Accessing the Console + +To check the status of the instance use the following command: + +``` +helm status my-console +``` + +> Note: Replace `my-console` with the value you used for the `name` parameter, or if you did not provide one, use the `helm list` command to find the release name that was automatically generated for you. + +The console is exposed via an HTTPS service - `RELEASE-NAME-ui-ext` (where RELEASE-NAME is the name used for the `name` parameter when installing). You can find the details of this service with: + +``` +kubectl get services -n NAMESPACE +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +my-console-mariadb ClusterIP 10.105.216.25 <none> 3306/TCP 60s +my-console-ui-ext NodePort 10.109.207.70 <none> 443:31067/TCP 60s +``` + +> In this example, Stratos has been deployed withe the service configured as `NodePort`. + +The URL you use for accessing Stratos will depend on the service configuration and the environment that you have used. + +> Note: If you did not provide a certificate when installing, Stratos will use a self-signed certificate, so you will see a certificate warning which you access Stratos in a browser. + +# Upgrading your deployment + +To upgrade your instance when using the Helm repository, fetch any updates to the repository: + +``` +helm repo update +``` + +To update an instance, the following assumes your instance is called `my-console`: + +``` +helm upgrade my-console stratos/console +``` + +After the upgrade, perform a `helm list` to ensure your console is the latest version. + +## Uninstalling + +To uninstall Stratos, delete the Helm release and also delete the Kubernetes namespace: + +``` +helm delete my-console --purge +kubectl delete namespace console +``` + +> Note: Stratos creates secrets in the namespace as part of an initialization job. These are not managed by Helm, so make sure you +delete the namespace to remove these secrets. + +# Advanced Topics + +## Using a Load Balancer + +If your Kubernetes deployment supports automatic configuration of a load balancer (e.g. Google Container Engine), specify the parameters `console.service.type=LoadBalancer` when installing. + + +``` +kubectl create namespace console +helm install my-console stratos/console --namespace=console --set console.service.type=LoadBalancer +``` + +## Using an Ingress Controller + +If your Kubernetes Cluster supports Ingress, you can expose Stratos through Ingress by supplying the appropriate ingress configuration when installing. + +This configuration is described below: + +|Parameter|Description|Default| +|----|---|---| +|console.service.ingress.enabled|Enables ingress|false| +|console.service.ingress.annotations|Annotations to be added to the ingress resource.|{}| +|console.service.ingress.extraLabels|Additional labels to be added to the ingress resource.|{}| +|console.service.ingress.host|The host name that will be used for the Stratos service.|| +|console.service.ingress.secretName|The existing TLS secret that contains the certificate for ingress.|| + +You must provide `console.service.ingress.host` when enabling ingress. + +By default a certificate will be generated for TLS. You can provide your own certificate by creating a secret and specifying this with `console.service.ingress.secretName`. + +> Note: If you do not supply `console.service.ingress.host` but do supply `env.DOMAIN` then the host `console.[env.DOMAIN]` will be used. + +## Deploying from a Private Image Repository + +If the images used by the chart are hosted in a private repository, the following needs to be specified. Save the following to a file called `private_overrides.yaml`. Replace `REGISTRY USER PASSSWORD`, `REGISTRY USERNAME`, `REGISTRY URL` with the appropriate values. `USER EMAIL` can be left blank. + +``` +kube: + registry: + password: <REGISTRY USER PASSWORD> + username: <REGISTRY USERNAME> + hostname: <REGISTRY URL> + email: <USER EMAIL or leave blank> +``` + +Deploy with: + +``` +kubectl create namespace console +helm install my-console stratos/console --namespace=console -f private_overrides.yaml +``` + +## Deploying with your own TLS certificates + +By default the console will generate self-signed certificates for demo purposes. To configure Stratos to use your provided TLS certificate, create a TLS secret in the namespace you are installing into and specify this secret name using the helm chart value `console.tlsSecretName` when installing. + +Assuming you have your certificate and key in the files `tls.crt` and `tls.key`, create the secret with: + +``` +kubectl create secret tls -n NAMESPACE stratos-tls-secret --cert=tls.crt --key=tls.key +``` + +> Where `NAMESPACE` is the namespace you are installing Stratos into - you will need to manually create the namespace first if it does not already exist. + +You can now install Stratos with: + +``` +kubectl create namespace console +helm install my-console stratos/console --namespace=console --set console.tlsSecretName=stratos-tls-secret + +``` + +## Using an External Database + +You can choose to use Stratos with an external database, rather than deploying a single-node MariaDB instance as part of the Helm install. + +To do so, specify `console.mariabdb.external=true` when deploying. + +You will also need to specify: + +- `console.mariadb.host` as the hostname of the external MariaDB database server +- `console.mariadb.port` as the port of the external MariaDB database server (defaults to 3306) +- `console.mariadb.tls` as the TLS mode (default is `false,` use `true` for a TLS connection to the database server) +- `console.mariadb.database` as the name of the database +- `console.mariadb.user` as the username to connect to the database server +- `console.mariadb.userPassword` as the password to connect to the database server + +When using an external database server, Stratos expects that you have: + +- Created a user that will be used to access the database +- Created a database for the Stratos tables and data +- Granted appropriate permissions so that the user can access the database + +> Note: When using a database from a Cloud provider, ensure that the username is correct - in some cases this will be `username@servername` - check the provided connection documentation + +## Specifying UAA configuration + +When deploying with SCF, the `scf-config-values.yaml` (see [SCF Wiki link](https://github.com/SUSE/scf/wiki/How-to-Install-SCF#configuring-the-deployment)) can be supplied when installing Stratos. + +``` +kubectl create namespace console +helm install my-console stratos/console --namespace=console -f scf-config-values.yaml +``` + +UAA configuration can be specified by providing the following configuration. + +Create a yaml file with the content below and and update according to your environment and save to a file called `uaa-config.yaml`. +``` +uaa: + endpoint: https://uaa.cf-dev.io:2793 + consoleClient: cf + consoleClientSecret: + consoleAdminIdentifier: cloud_controller.admin + skipSSLValidation: false +``` + +To install Stratos with the above specified configuration: + +``` +kubectl create namespace console +helm install my-console stratos/console --namespace=console -f uaa-config.yaml +``` + +## Configuring a local user account + +This allows for deployment without a UAA. To enable the local user account, supply a password for the local user in the deployment command, as follows. All other steps for each deployment method should be followed as in the preceding sections above. + +To deploy using our Helm repository: + +``` +kubectl create namespace console +helm install my-console stratos/console --namespace=console --set console.localAdminPassword=<password> +``` + +## Specifying Annotations and Labels + +In some scenarios it is useful to be able to add custom annotations and/or labels to the Kubernetes resources that the Stratos Helm chart creates. + +The Stratos Helm chart exposes a number of Helm chart values that cabe specified in order to do this - they are: + +|Parameter|Description|Default| +|----|---|---| +|console.podAnnotations|Annotations to be added to all pod resources|| +|console.podExtraLabels|Additional labels to be added to all pod resources|| +|console.statefulSetAnnotations|Annotations to be added to all statefulset resources| +|console.statefulSetExtraLabels|Additional labels to be added to all statefulset resources|| +|console.deploymentAnnotations|Annotations to be added to all deployment resources|| +|console.deploymentExtraLabels|Additional labels to be added to all deployment resources|| +|console.jobAnnotations|Annotations to be added to all job resources|| +|console.jobExtraLabels|Additional labels to be added to all job resources|| +|console.service.annotations|Annotations to be added to all service resources|| +|console.service.extraLabels|Additional labels to be added to all service resources|| +|console.service.ingress.annotations|Annotations to be added to the ingress resource|| +|console.service.ingress.extraLabels|Additional labels to be added to the ingress resource|| + +## Requirements + +### Storage Class + +Stratos uses persistent volumes. In order to deploy it in your Kubernetes environment, you must +have a storage class available. + +Without configuration, the Stratos Helm Chart will use the default storage class. If a default storage +class is not available, installation will fail. + +To check if a `default` storage class exists, you can list your configured storage classes with `kubectl get storageclass`. If no storage class has `(default)` after it, then you need to either specify a storage class override or declare a default storage class for your Kubernetes cluster. + +For non-production environments, you may want to use the `hostpath` storage class. See the [SCF instructions](https://github.com/SUSE/scf/wiki/How-to-Install-SCF#choosing-a-storage-class) for details on setting this up. Note that you will need to make this storage class the default storage class, e.g. + +``` +kubectl patch storageclass <your-class-name> -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}' +``` + +Where `<your-class-name>` would be `hostpath` if you follow the SCF instructions. + + +### Specifying a custom Storage Class + +If no default storage class has been defined in the Kubernetes cluster. The Stratos helm chart will fail to deploy successfully. To check if a `default` storage class exists, you can list your configured storage classes with `kubectl`. If no storage class has `(default)` after it, then you need to either specify a storage class override or declare a default storage class for your Kubernetes cluster. + +#### Providing Storage Class override +``` +kubectl get storageclass +NAME TYPE +ssd kubernetes.io/host-path +persistent kubernetes.io/host-path +``` + +For instance to use the storage class `persistent` to deploy Console persistent volume claims, store the following to a file called `override.yaml`. + +``` +--- +storageClass: persistent +``` + +If you want MariaDB to use a specific storage class (which can be different to that used for the other components), then specify the following: +``` +--- +storageClass: persistent +mariadb: + persistence: + storageClass: persistent +``` + +Run Helm with the override: + +``` +kubectl create namespace console +helm install my-console stratos/console --namespace=console -f override.yaml +``` + +#### Create a default Storage Class + +Alternatively, you can configure a storage class with `storageclass.kubernetes.io/is-default-class` set to `true`. For instance the following storage class will be declared as the default. If you don't have the `hostpath` provisioner available in your local cluster, please follow the instructions on [link](https://github.com/kubernetes-incubator/external-storage/tree/master/docs/demo/hostpath-provisioner), to deploy one. + +If the hostpath provisioner is available, save the file to `storageclass.yaml` + +``` +--- +kind: StorageClass +apiVersion: storage.k8s.io/v1beta1 +metadata: + name: default + annotations: + storageclass.kubernetes.io/is-default-class: "true" +provisioner: kubernetes.io/host-path # Or whatever the local hostpath provisioner is called +``` + +To create it in your kubernetes cluster, execute the following. + +``` +kubectl create -f storageclass.yaml +``` + +See [Storage Class documentation](https://kubernetes.io/docs/tasks/administer-cluster/change-default-storage-class/) for more information. diff --git a/website/docs/deploy/overview.md b/website/docs/deploy/overview.md new file mode 100644 index 0000000000..f3bf6e39d8 --- /dev/null +++ b/website/docs/deploy/overview.md @@ -0,0 +1,44 @@ +--- +id: overview +title: Deploying Stratos +sidebar_label: Overview +--- + +Stratos can be deployed in the following environments: + +1. Cloud Foundry, as an application. See [guide](cloud-foundry/cloud-foundry) +2. Kubernetes, using a Helm chart. See [guide](kubernetes) +3. Docker, single container deploying all components. See [guide](all-in-one) + +> Note: that not all features are enabled in every environment - the Kubernetes deployment supports all features, but Cloud Foundry and Docker deployments do not support some features. + +> Note: Some features are marked as 'Tech Preview' and are only available if tech preview features are enabled when deploying. + +## Deployed in Cloud Foundry as an application + +In this case, Stratos is deployed in a manner optimized for the management of a single Cloud Foundry instance. The 'Endpoints Dashboard' that allows multiple Cloud Foundry endpoints to be registered is not deployed. An extra component is deployed that detects that the Console is running as Cloud Foundry which does the following: + +- Automatically detects the Cloud Foundry endpoint and located the UAA Endpoint to use for authentication +- Authenticates directly against the UAA for the Clound Foundry where the Console is deployed and assumes that Cloud Foundry admins are also Console admins (the UAA Scope 'cloud_controller.admin' is used to identify admins) +- Uses a SQLite database rather than Postgres +- Automatically connects to the Cloud Foundry endpoint when a user logs in to simplify the user flow when using the Console in this case + +In this case, the front-end web application static resources are served by the API Server back-end rather than a separate web server. + +By defaut, a non-persistent SQLite database is used - by automatically registering the cloud foundry endpoint and connecting to it on login, all data stored in the database can be treated as ephimeral, since it will be re-created next time a user logs in. Cloud Foundry Session Affinity is used to ensure that when scaling up the Console Application to multiple instances, the user is also directed to the instance which will know about them and their endpoints (since each Application instance will have its own local SQLite store). + +Alternatively, Stratos can be configured [with a persistent Cloud Foundry database service](cloud-foundry/db-migration), which enables features requiring persistence such as user favorites. + +## Deployed in Kubernetes + +In this case, a Helm chart is used to deploy the Console into a Kubernetes environment. + +At the outer-level there are two services - the external service that provides the Console itself and a private service that provides a Postgres DB to the Console service. + +The Console service is provided by a deployment consisting of two containers, one providing the static front-end web application resources, served by an nginx instance, the other providing the API Server back-end. + +## Deployed in Docker using a single container + +In this case, a single Docker image is run in a container that hosts all services together. SQLite is used as the database, so any endpoint metadata registered is lost when the container is destroyed. + +This deployment is recommended only for trying out the Console and for development. diff --git a/docs/troubleshooting.md b/website/docs/deploy/troubleshooting.md similarity index 78% rename from docs/troubleshooting.md rename to website/docs/deploy/troubleshooting.md index 138d8f7aec..c40f7065da 100644 --- a/docs/troubleshooting.md +++ b/website/docs/deploy/troubleshooting.md @@ -1,7 +1,11 @@ -# Troubleshooting +--- +id: troubleshooting +title: General Troubleshooting +sidebar_label: Troubleshooting +--- ## Deploying as a Cloud Foundry Application -See the custom troubleshooting guide [here](../deploy/cloud-foundry#troubleshooting). +See the custom troubleshooting guide [here](cloud-foundry/cf-troubleshooting). ## Usernames appear as long random characters when connected to IBM Cloud diff --git a/website/docs/developer/backend.md b/website/docs/developer/backend.md new file mode 100644 index 0000000000..d8b2e7bc15 --- /dev/null +++ b/website/docs/developer/backend.md @@ -0,0 +1,124 @@ +--- +title: Backend Development +sidebar_label: Overview +--- + +Jetstream is the back-end for Stratos. It is written in Go. + +We use Go Modules for dependency management. + +### Pre-requisites + +You will need the following installed/available: + +* go 1.12 or later. + +*For authentication, **either*** + +* A UAA instance +* A local user account + +### Building the back-end + + +#### Build + +From the `src/jetstream` folder, build the Stratos back-end with: + +``` +npm run build-backend +``` + +The back-end executable is named `jetstream` and should be created within the `src/jetstream` folder. + +### Configuration + +Configuration can either be done via +- Environment Variable and/or Config File +- In the UI when you first use a front end with this backend + +In all cases the configuration is saved to the database on first run. Any subsequent changes require the db to be reset. For the default sqlite +db provider this can be done by deleting `src/jetstream/console-database.db` + +#### Configure by Environment Variables and/or Config File + +By default, the configuration in file `./src/jetstream/config.dev` will be used. These can be changed by environment variables +or an overrides file. + +##### Environment variable + +If you wish to use a local user account, ensure you have set the following environment variables: + +- `AUTH_ENDPOINT_TYPE=local` +- `LOCAL_USER` - The username for the local user +- `LOCAL_USER_PASSWORD` - The password for the local user +- `LOCAL_USER_SCOPE=stratos.admin` - This gives the local user admin permissions. Currently other roles are not available. + +If you have a custom uaa, ensure you have set the following environment variables: + +- `UAA_ENDPOINT`- the URL of your UAA + - If you have an existing CF and want to use the same UAA use the `authorization_endpoint` value from `[cf url]/v2/info` + For example for PCF Dev, use: `UAA_ENDPOINT=https://login.local.pcfdev.io`. +- `CONSOLE_CLIENT` - the Client ID to use when authenticating against your UAA (defaults to: 'cf') +- `CONSOLE_CLIENT_SECRET` - the Client ID to use when authenticating against your UAA (defaults to empty) +- `CONSOLE_ADMIN_SCOPE` - an existing UAA scope that will be used to identify users as `Stratos Admins` + +> To use a pre-built Stratos UAA container execute `docker run --name=uaa --rm -p 8080:8080 -P splatform/stratos-uaa`. The UAA will be + available at `http://localhost:8080` with a `CONSOLE_CLIENT` value of `console` + +##### Config File + +To easily persist configuration settings copy `src/jetstream/config.dev` to `src/jetstream/config.properties`. The backend will load its +configuration from this file in preference to the default config file, if it exists. You can also modify individual configuration settings +by setting the corresponding environment variable. + +##### To configure a local user account via config file + +In `src/jetstream/config.properties` uncomment the following lines: + +``` +AUTH_ENDPOINT_TYPE=local +LOCAL_USER=localuser +LOCAL_USER_PASSWORD=localuserpass +LOCAL_USER_SCOPE=stratos.admin +``` + +Load the Stratos UI and proceed to log in using the configured credentials. + +#### To configure UAA via Stratos + +1. Go through the `Config File` step above and comment out the `UAA_ENDPOINT` with a `#` in the new `config.properties` file. +1. If any previous configuration attempt has been made reset your database as described above. +1. Continue these steps from [Run](#run). + - You should see the line `Will add setup route and middleware` in the logs +1. Load the Stratos UI as usual and you should be immediately directed to the setup wizard + +The setup wizard that allows you to enter the values normally fetched from environment variables or files. The UI will assist you through +this process, validating that the UAA address and credentials are correct. It will also provide a list of possible scopes for the Stratos Admin + +#### Run + +Execute the following file from `src/jetstream` + +``` +./jetstream +``` + +You should see the log as the backend starts up. You can press CTRL+C to stop the backend. + + +#### Automatically register and connect to an existing endpoint +To automatically register a Cloud Foundry add the environment variable/config setting below: + +``` +AUTO_REG_CF_URL=<api url of cf> +``` + +Jetstream will then attempt to auto-connect to it with the credentials supplied when logging into Stratos. + +#### Running Jetstream in a container + +We recommend running Stratos using the Docker All-In-One image. + +* Follow instructions in the deploy/all-in-one docs + diff --git a/website/docs/developer/contributing.md b/website/docs/developer/contributing.md new file mode 100644 index 0000000000..5c4df05a69 --- /dev/null +++ b/website/docs/developer/contributing.md @@ -0,0 +1,162 @@ +--- +title: Contributing +sidebar_label: Contributing +--- + + +Stratos is an open project and welcomes contributions. These guidelines are provided to help you understand how the project works and to make contributing smooth and fun for everybody involved. + +There are two main forms of contribution: reporting issues and performing code changes. + +## Reporting Issues + +If you find a problem with Stratos, report it using [GitHub issues](https://github.com/cloudfoundry/stratos/issues/new). + +Before reporting a new issue, please take a moment to check whether it has already been reported +[here](https://github.com/cloudfoundry/stratos/issues). If this is the case, please: + +- Read all the comments to confirm that it's the same issue you're having. +- Refrain from adding "same thing here" or "+1" comments. Just hit the + "subscribe" button to get notifications for this issue. +- Add a comment only if you can provide helpful information that has not been + provided in the discussion yet. + +When creating a new issue, make sure you include: + +1. As much detail as possible about your setup/environment +1. Steps to reproduce the issue/bug +1. What you expected to happen +1. What happened instead + +This information will help to determine the cause and prepare a fix as fast as possible. + +## Code Changes + +Code contributions come in various forms and sizes, from simple bug fixes to implementation +of new features. Before making any non-trivial change, get in touch with the Stratos developers first. This can prevent wasted effort later. + +To send your code change, use GitHub pull requests. The workflow is as follows: + + 1. Fork the project. + + 1. Create a branch based on `master`. + + 1. Implement your change, including tests and documentation. + + 1. Run tests to make sure your change didn't break anything. + + 1. Publish the branch and create a pull request. + + 1. Stratos developers will review your change and possibly point out issues. + Adapt the code under their guidance until all issues are resolved. + + 1. Finally, the pull request will get merged or rejected. + +See also [GitHub's guide on contributing](https://help.github.com/articles/fork-a-repo). + +If you want to do multiple unrelated changes, use separate branches and pull +requests. + +### Commits + +Each commit in the pull request should do only one thing, which is clearly +described by its commit message. Especially avoid mixing formatting changes and +functional changes into one commit. When writing commit messages, adhere to +[widely used +conventions](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). + +When the commit fixes a bug, put a message in the body of the commit message +pointing to the number of the issue (e.g. "Fixes #123"). + +### Pull requests and branches + +All work happens in branches. The master branch is only used as the target for pull +requests. + +During code review you often need to update pull requests. Usually you do that +by pushing additional commits. + +In some cases where the commit history of a pull request gets too cumbersome to +review or you need bigger changes in the way you approach a problem which needs +changing of commits you already did it's more practical to create a new pull +request. This new pull request often will contain squashed versions of the +previous pull request. Use that to clarify the changes contained in a pull +request and to make review easier. + +When you replace a pull request by another one, add a message in the +description of the new pull request on GitHub referencing the pull request it +replaces (e.g. "Supersedes #123"). + +Never force push commits. This changes history, can lead to data loss, and +causes trouble for people who have checked out the changes which are overwritten +by a force push. Don't waste time with thinking about if the force push in this +one particular case would be ok, just don't do it. + +### Check for assigned people + +We use Github Issues for submitting known issues (e.g. bugs, features, +etc.). Some issues will have someone assigned, meaning that there's already +someone that takes responsibility for fixing the issue. This is not done to +discourage contributions, rather to not step in the work that has already been +done by the assignee. If you want to work on a known issue with someone already +assigned to it, please contact the assignee first (e.g. by +mentioning the assignee in a new comment on the specific issue). This way you +can contribute with ideas, or even with code if the assignee decides that you +can step in. + +If you plan to work on a non assigned issue, please add a comment on the issue +to prevent duplicated work. + +### Sign your work + +The sign-off is a simple line at the end of the explanation for the change. Your +signature certifies that you wrote the change or otherwise have the right to pass +it on as an open-source change. The rules are pretty simple: if you can certify +the below (from [developercertificate.org](http://developercertificate.org/)): + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +Then you just add a line to each git commit message: + + Signed-off-by: Joe Smith <joe.smith@email.com> + +Use your real name (sorry, no pseudonyms or anonymous contributions.) + +If you set your `user.name` and `user.email` git configs, you can sign your +commit automatically with `git commit -s`. diff --git a/docs/developers-guide-e2e-tests.md b/website/docs/developer/developers-guide-e2e-tests.md similarity index 87% rename from docs/developers-guide-e2e-tests.md rename to website/docs/developer/developers-guide-e2e-tests.md index dbac463428..03d17b1643 100644 --- a/docs/developers-guide-e2e-tests.md +++ b/website/docs/developer/developers-guide-e2e-tests.md @@ -1,4 +1,8 @@ -# E2E Tests +--- +id: developers-guide-e2e-tests +title: E2E Tests +sidebar_label: E2E Tests +--- The Stratos E2E test suite exercises the Stratos UI using protractor/web-driver. @@ -6,7 +10,7 @@ The tests require a Stratos instance to be running (front-end and back-end) and Developers' should be aware that: -- The E2E tests are destructive on the Stratos system being tested - since they test endpoint registration, they will un-register any existing endpoints +- The E2E tests are destructive on the Stratos system being tested - since they test endpoint registration, they will un-register any existing endpoints. - The E2E tests will create orgs, spaces, applications, routes, etc in the Cloud Foundry instance that is specified. Tests should automatically tidy up afterwards unless stopped abruptly. ## Pre-requisites @@ -24,9 +28,9 @@ The tests require an instance of Cloud Foundry with the following: - A number of other Cloud Foundry entities To meet the above requirements we recommend running the Stratos CF E2E set up script which is kept up to date with the latest test requirements. -More information can be found [below](#Running-the-E2E-Set-up-Script) +More information can be found [below](#running-the-e2e-set-up-script). -Before running the E2E tests, you need to create a file named `secrets.yaml` in the root of the Stratos folder. An example template is included in [src/test-e2e/secrets.yaml.example](../src/test-e2e/secrets.yaml.example) - copy this to `secrets.yaml` and edit accordingly. +Before running the E2E tests, you need to create a file named `secrets.yaml` in the root of the Stratos folder. An example template is included in [src/test-e2e/secrets.yaml.example](https://github.com/cloudfoundry/stratos/blob/master/src/test-e2e/secrets.yaml.example) - copy this to `secrets.yaml` and edit accordingly. If you want to run the tests in headless Chrome, add the following to the secrets file: @@ -35,7 +39,7 @@ headless: true ``` ## Running the E2E Set up Script -The script can be found in [deploy/tools/init-cf-for-e2e.sh](../deploy/tools/init-cf-for-e2e.sh) +The script can be found in [deploy/tools/init-cf-for-e2e.sh](https://github.com/cloudfoundry/stratos/blob/master/deploy/tools/init-cf-for-e2e.sh) ### Minimum Requirements - CF CLI @@ -107,13 +111,17 @@ Given the output of the script, the following template can be used to update the ### Tidying up test generated CF entities If tests are stopped before completing or fail to clean old test artifacts will exist in the CF. To clean some of these please see the script -at [deploy/ci/automation/e2e-clean-remnants.sh](../deploy/ci/automation/e2e-clean-remnants.sh) +at [deploy/ci/automation/e2e-clean-remnants.sh](https://github.com/cloudfoundry/stratos/blob/master/deploy/ci/automation/e2e-clean-remnants.sh) ## Running the tests To run the tests against an instance of Stratos execute ``` -npm run e2e -- --dev-server-target= --base-url=<URL of stratos +STRATOS_E2E_BASE_URL=<URL of stratos> ng e2e --dev-server-target= --base-url=<URL of stratos> +``` +If running Stratos on `https://127.0.0.1:4200` then instead execute +``` +npm run e2e-dev ``` diff --git a/website/docs/developer/developers-guide-env-tech.md b/website/docs/developer/developers-guide-env-tech.md new file mode 100644 index 0000000000..0b374c6c01 --- /dev/null +++ b/website/docs/developer/developers-guide-env-tech.md @@ -0,0 +1,103 @@ +--- +id: developers-guide-env-tech +title: Developer Links + Environment +sidebar_label: Dev Links & Env +--- + +## Links + +Below is a collection of links that are can be useful when tackling some of the technology involved with Stratos + +### ES6 + +* [http://stack.formidable.com/es6-interactive-guide](http://stack.formidable.com/es6-interactive-guide) +* [http://es6-features.org](http://es6-features.org) + +### TypeScript + +* [https://learnxinyminutes.com/docs/typescript/](https://learnxinyminutes.com/docs/typescript/) (cheat sheet) +* [https://www.tutorialspoint.com/typescript/](https://www.tutorialspoint.com/typescript/) +* [https://tutorialzine.com/2016/07/learn-typescript-in-30-minutes](https://tutorialzine.com/2016/07/learn-typescript-in-30-minutes) + +### Angular + +#### Angular + +* [https://www.youtube.com/watch?v=KhzGSHNhnbI](https://www.youtube.com/watch?v=KhzGSHNhnbI) (very basic 1h video of angular covering basic app features in a demo) +* [https://angular.io/guide/architecture](https://angular.io/guide/architecture) (official angular intro) +* [https://angular.io/tutorial](https://angular.io/tutorial) (official angular tutorial. Basic to start with but good introduction to routing, http requests, promises, observables and observable event stream later on) + +#### Angular CLI + +* [https://cli.angular.io/reference.pdf](https://cli.angular.io/reference.pdf) (cheat sheet) +* [https://github.com/angular/angular-cli](https://github.com/angular/angular-cli) + +#### Example Apps + +* https://github.com/aviabird/angularspree (covers everything) + +### Redux + +* [http://redux.js.org](http://redux.js.org) +* [https://gist.github.com/btroncone/a6e4347326749f938510](https://gist.github.com/btroncone/a6e4347326749f938510) +* [https://code-cartoons.com/a-cartoon-intro-to-redux-3afb775501a6](https://code-cartoons.com/a-cartoon-intro-to-redux-3afb775501a6) +* [https://www.youtube.com/watch?v=WIqbzHdEPVM](https://www.youtube.com/watch?v=WIqbzHdEPVM) +* [https://egghead.io/courses/getting-started-with-redux](https://egghead.io/courses/getting-started-with-redux) + +#### Observables + +* [http://reactivex.io/documentation/observable.html](http://reactivex.io/documentation/observable.html) + +## VS Code Plug-ins + +There's no mandated IDE, but if you choose Visual Studio Code here's some helpful plug-ins + +### Really Helpful + +* TSLint +* TS Hero +* Git Lens +* gitignore +* Move TS - Move files and automatically update imports + +### Helpful + +* Beautify +* Document This +* Git History +* TODO Parser + * see icon bottom left for todo's in current file + * Add the following config to settings to exclude some folders + + ``` + "TodoParser": { + "folderExclude": ["node_modules", ".vscode"] + } + ``` + + * F1 + `Parse TODOs (all files)` to see all TODOs +* Code Spell Checker + * List of words to exclude coming soon +* Eclipse Keymap + + +## Guides + +### ExpressionChangedAfterItHasBeenCheckedError Error + +#### Links + +* https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4 +* https://github.com/angular/angular/issues/17572 +* https://github.com/angular/angular/issues/6005 + +#### In summary + +* AVOID + * setTimeout + * forcing change detecting +* TRY TO USE + * observeOn(asapScheduler) in observable chain + * ngAfterViewInit +* Generally + * Avoid changing state in child components that will affect a binding in parent component/s \ No newline at end of file diff --git a/website/docs/developer/frontend-tests.md b/website/docs/developer/frontend-tests.md new file mode 100644 index 0000000000..d5380411b6 --- /dev/null +++ b/website/docs/developer/frontend-tests.md @@ -0,0 +1,73 @@ +--- +title: Frontend Tests +sidebar_label: Tests +--- + +## Test + +### Lint + +Run `npm run lint` to execute tslint lint checking. + +### Unit tests + +Run `npm test` to execute the unit tests via [Karma](https://karma-runner.github.io). Coverage information can be found in `./coverage` + +To execute an individual package run `ng test <package name>`. To execute the tests again automatically on code changes add `--watch=true` + +> **NOTE** npm test will search for chrome on your path. If this is not so please set an env var CHROME_BIN pointing to your executable +(chromium is fine too). + +### End-to-end tests + +Run `npm run e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). + +Run `npm run e2e-dev` to execute end-to-end tests against a locally running instance on `https://localhost:4200` + +More information on the E2E tests and pre-requisites for running them is available here - [E2E Tests](developers-guide-e2e-tests.md). + +### Code Climate + +We use [Code Climate](https://codeclimate.com/github/cloudfoundry-incubator/stratos) to check for general code quality issues. This executes against Pull +Requests on creation/push. + +#### Running Code Climate locally +> Generally we would not advise doing this and just rely on the code climate gate to run when pull requests are submitted + +To run locally see instructions [here](https://github.com/codeclimate/codeclimate) to install Code Climate CLI +and engine via docker. Once set ensure you're in the root of the project and execute the following (it may take a while) + +``` +codeclimate analyze +``` + +> **NOTE** Unfortunately this highlights all current issues and not those that are the diff between any master and feature branch. Analyze +can be ran against a single/sub set of files, again with all current issues, but a little more digestible. + +``` +codeclimate analyze <path to file/s> +``` + +In a feature branch to compare files that have changed to master, for instance, use the following + +``` +git checkout feature-branch-A +codeclimate analyze $(git diff --name-only master) +``` + +You can also run the above command via npm + +``` +npm run climate +``` + +### Stratos Continue Integration +For each new pull request and any subsequent pushes to it the following actions are executed +- Code quality analysis via Code Climate - https://codeclimate.com/ +- Jenkins CI run, covering.. + - Frontend lint check + - Backend lint check + - Frontend unit tests + - Backend unit tests + - End to end tests +- Security anaylsis via Snyk - https://snyk.io/ diff --git a/website/docs/developer/frontend.md b/website/docs/developer/frontend.md new file mode 100644 index 0000000000..572dd2032a --- /dev/null +++ b/website/docs/developer/frontend.md @@ -0,0 +1,73 @@ +--- +title: Frontend Development +sidebar_label: Overview +--- + +## Introduction to the stack + +Before making changes to the frontend code you should be familiar with + +1. Angular +1. Typescript / ES6 +1. Redux / NGRX / Observables +1. Node / NPM + +There are a some introduction style resources [here](/docs/developer/developers-guide-env-tech.md). There's also some advice on helpful [VS code plugins](/docs/developer/developers-guide-env-tech#vs-code-plug-ins). If you feel comfortable with these and are happy with your dev environment please skip straight to +[Set up Dependencies](#set-up-dependencies). + +## Set up Dependencies + +* Set up a Stratos backend. Both backend and frontend exist in this same repo. Follow the [Backend Development](/docs/developer/introduction#build--run-locally) set up guide. +* Install [NodeJs](https://nodejs.org) (if not already install) (minimum node version 12.13.0) +* Install [Angular CLI](https://cli.angular.io/) (if not already install) - `npm install -g @angular/cli` + + +## Run the frontend + +1. Run `npm install` +1. Run `npm start` for a dev server. (the app will automatically reload if you change any of the source files) + * If this times out please use `npm run start-high-mem` instead + * To change the port from the default 4200, add `-- --port [new port number]` + * To stop the automatic reload every time a resource changes add `-- --live-reload false` + * To do both the above use `-- --live-reload false --port [new port number]` +1. Navigate to `https://localhost:4200/`. The credentials to log in will be dependent on the Jetstream the console points at. Please refer + to the guides used when setting up the backend for more information + +## Build + +> The normal dev cycle does not require a direct build. + +Run `npm run build` to build the project. + +The build artefacts will be stored in the `dist/` directory. This will output a production build of the application. + +## Creating angular items via angular cli + +To create a new angular component run `ng generate component component-name`. You can use a similar command to create other types of angular +items `ng generate <directive|pipe|service|class|guard|interface|enum|module> <name>`. + +## Theming + +We use the angular material theming mechanism. See [here](https://material.angular.io/guide/theming-your-components) for more information about theming new components added to stratos. + + +## Additional Information + +### Extensions + +Documentation on extensions can be found [here](/docs/extensions/introduction). From a developers perspective extensions are managed by npm packages. +The default set are in `./src/frontend/packages`, any package added directly here will be automatically included by the build. + +At build time the Stratos Devkit (`./src/frontend/packages/devkit`) will ensure all packages are imported correctly and theming, both component and console level, are applied correctly. +The devkit is automatically built in `postinstall` after `npm install` is ran. To directly build it `npm run dev-setup` can be executed. + +### Configuration + +Configuration information can be found in two places + +* `./proxy.conf.js` + * Informs the frontend where the backend is +* `./src/frontend/packages/core/src/environments/environment.ts` for developer vs production like config + * This contains more general settings for the frontend and does not usually need to be changed +* `config.properties` + * Backend configuration diff --git a/website/docs/developer/introduction.md b/website/docs/developer/introduction.md new file mode 100644 index 0000000000..8741bb4125 --- /dev/null +++ b/website/docs/developer/introduction.md @@ -0,0 +1,37 @@ +--- +title: Developing the Stratos Console +sidebar_label: Getting Started +--- + +## Introduction + +Stratos comprises of two main components: + +- A front-end UI that runs in your web browser. This is written in [Typescript](https://www.typescriptlang.org/) and uses the [Angular](https://angular.io/) framework. +- A back-end that provides a web-based API to the front-end. This is written in [Go](https://golang.org/). + +Depending on what you are contributing, you will need to develop with the front-end, back-end or both. + +## Build & Run Locally + +For a quick-start to get Stratos front and back ends built and running locally on a development system, follow the steps below. + +You will need to have `go` and `nodejs` installed in your development environment. + +``` +git clone https://github.com/cloudfoundry/stratos.git +cd stratos +npm install +npm run build +npm run build-backend +cd src/jetstream +./jetstream +``` + +This will build both the frontend and backend and run the backend in a mode where it will also serve the static resources for the frontend. + +You can open a web browser and navigate to (https://127.0.0.1:5443) and login with username `admin` and password `admin`. + +> To develop the frontend we recommend reading through the [frontend](/docs/developer/frontend) doc. This includes a faster way to run Stratos and see your changes. + +> Additional back end docs are available [here](/docs/developer/backend) before making any changes to the code. diff --git a/docs/backend-plugins.md b/website/docs/extensions/backend.md similarity index 93% rename from docs/backend-plugins.md rename to website/docs/extensions/backend.md index 4aba144c68..59e54cef44 100644 --- a/docs/backend-plugins.md +++ b/website/docs/extensions/backend.md @@ -1,4 +1,7 @@ -# Backend Plugins +--- +title: Backend Plugins +sidebar_label: Backend Plugins +--- This document provides a brief outline for extending the Stratos backend (Jetstream). @@ -15,9 +18,9 @@ Currently, to create an extension to the backend: 1. Build Jetstream -> Note: There are a few plugins in the `src/jetstream/plugins` folder that should help serve as examples of how to write a plugin. +> There are a few plugins in the `src/jetstream/plugins` folder that should help serve as examples of how to write a plugin. -> Note: Jetstream uses the [Echo web server](https://echo.labstack.com/) from Labstack - some familiarity with this is required when developing backend plugins. +> Jetstream uses the [Echo web server](https://echo.labstack.com/) from Labstack - some familiarity with this is required when developing backend plugins. ## Plugin Interface All plugins must implement the interface `interfaces.StratosPlugin` - this is defined in `src/jetstream/repository/interfaces/plugin.go`. diff --git a/website/docs/extensions/frontend.md b/website/docs/extensions/frontend.md new file mode 100644 index 0000000000..6bed192aae --- /dev/null +++ b/website/docs/extensions/frontend.md @@ -0,0 +1,459 @@ +--- +title: Frontend Extensions +sidebar_label: Frontend Extensions +--- + +Stratos exposes the following extension points: + +- Adding new items to the side navigation menu +- Adding new tabs to the Application, Cloud Foundry, Organization and Space views +- Adding new action buttons to the Application Wall, Application, Cloud Foundry, Organization and Space and Endpoint views +- Replace the loading page +- Replace the login page + +We use Decorators to annotate components to indicate that they are Stratos extensions. + +An example illustrating the various front-end extension points of Stratos is included in the folder `src/frontend/packages/example-extensions`. + +To run Stratos with these customizations see [here](/docs/extensions/introduction#acme). + +For a walk-through of extending Stratos, see [Example: Adding a Custom Tab](#example-adding-a-custom-tab). + +## Including modules and routes +To include code and angular routing in your component there needs to be two access points +- A core module that declares your extension components. +- A core routing module that calls `RouterModule.forRoot`. This is the root for all routes in your package. + +These modules should be made available externally to Stratos by the following steps + +1. Exported in the package's `public-api.ts`, for example `src/frontend/packages/example-extensions/src/public-api.ts`. The public api should be added to the applications `tsconfig.json` for example `src/tsconfig.json` +1. Reference as, or imported by, the two modules defined in the `stratos` section in the package's `package.json`, for example `src/frontend/packages/example-extensions/package.json` + ``` + "stratos": { + ... + "module": "ExampleModule", + "routingModule": "ExampleRoutingModule" + ... + } + ``` + +At build time these are then added to a dynamically created module `src/frontend/packages/core/src/_custom-import.module.ts` and included in the output. + +## Extension Points + +### Side Navigation + +New items can be added to the Side Navigation menu with extensions. + +To do so, annotate the routes for your extension with custom metadata, which Stratos will then pick up and add to the side menu. + +A full example is in `src/frontend/packages/example-extensions/src/example-routing.module.ts` and `src/frontend/packages/example-extensions/src/nav-extension`. + +Your route should have the following metadata in the `data` field of the route: + +``` + stratosNavigation: { + text: '<TITLE>', + matIcon: '<ICON NAME>' + } +``` + +Where `<TITLE>` is the text label to show in the side navigation and `<ICON NAME>` is the icon to use. + +> The routing module must be, or referenced by, the core routing module as described [above](/docs/extensions/frontend#including-modules-and-routes) + +An example routing module would be: + +``` +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +const customRoutes: Routes = [{ + path: 'example', + loadChildren: () => import('./nav-extension/nav-extension.module').then(m => m.NavExtensionModule), + data: { + stratosNavigation: { + text: 'Example', + matIcon: 'extension' + } + } +}]; + +@NgModule({ + imports: [ + RouterModule.forRoot(customRoutes), + ], + declarations: [] +}) +export class ExampleRoutingModule { } + +``` + +This approach ensures that the Angular compiler creates a separate chunk for the extension at compile time which can be lazy loaded at run +time. + +### Custom Tabs + +Tabs can be added to the following views in Stratos: + +- The Application view that shows the detail of an application +- The Cloud Foundry view that shows detail for a Cloud Foundry +- The Cloud Foundry Org view that shows detail for a Cloud Foundry organization +- The Cloud Foundry Space view that shows detail for a Cloud Foundry space + +A step by step guide on how to create a custom tab can be found [below](/docs/extensions/frontend#example-adding-a-custom-tab). + +For example: + +![Example Application tab extension](../../images/extensions/app-tab-example.png) + +The approach for all of these is the same: + +1. Create a new component that will provide the tab contents +1. Ensure that your component is included in the `EntryComponent` section of your custom module +1. Decorate the component with the `StratosTab` decorator, for example: + ``` + @StratosTab({ + icon: 'extension', + type: StratosTabType.Application, + label: 'Example App Tab', + link: 'example' + }) + ``` + Where: + - < ICON > is the material design icon name for the icon + - < TYPE > indicates where the tab should appear and can be: + - StratosTabType.Application - Application View + - StratosTabType.CloudFoundry - Cloud Foundry view + - StratosTabType.CloudFoundryOrg - Cloud Foundry Org view + - StratosTabType.CloudFoundrySpace - Cloud Foundry Space view + - < LABEL > is the text label to use for the tab + - < LINK > is the name to use for the route (this must only contain characters permitted in URLs) + An example is included in the file `src/frontend/packages/example-extensions/src/app-tab-extension/app-tab-extension.component.ts`. +1. Declare the component to avoid Angular tree shaking + - In the same module that the component is 'declared' in add the following to `imports` + ``` + ExtensionService.declare([ + <component name>, + ]) + ``` +> The module referencing the component, or another referencing it, must be imported by the core module as described [above](/docs/extensions/frontend#including-modules-and-routes). + +### Custom Actions + +Actions can be added to the following views in Stratos: + +- The Application Wall view that shows all applications +- The Application view that shows the detail of an application +- The Cloud Foundry view that shows detail for a Cloud Foundry +- The Cloud Foundry Org view that shows detail for a Cloud Foundry organization +- The Cloud Foundry Space view that shows detail for a Cloud Foundry space +- The Endpoints view that shows all endpoints + +An action is a icon button that appears at the top-right of a View. For example: + +![Example Application action extension](../../images/extensions/appwall-action-example.png) + +The approach for all of these is the same: + +1. Create a new component that will provide the contents to show when the action is clicked +1. Ensure that your component is included in the `EntryComponent` section of your custom module +1. Decorate the component with the `StratosAction` decorator, for example: + ``` + @StratosAction({ + type: StratosActionType.Applications, + label: '<LABEL>', + link: '<LINK>', + icon: '<ICON> + }) + ``` + Where: + - < TYPE > indicates where the action should appear and can be: + - StratosActionType.Applications - Application Wall View + - StratosActionType.Application - Application View + - StratosActionType.CloudFoundry - Cloud Foundry view + - StratosActionType.CloudFoundryOrg - Cloud Foundry Org view + - StratosActionType.CloudFoundrySpace - Cloud Foundry Space view + - StratosActionType.Endpoints - Endpoints view + - < ICON > is the icon to show + - < LABEL > is the text label to use for the tooltip of the icon (optional) + - < LINK > is the name to use for the route (this must only contain characters permitted in URLs) + An example is included in the file `src/frontend/packages/example-extensions/src/app-action-extension/app-action-extension.component.ts`. +1. Declare the component to avoid Angular tree shaking + - In the same module that the component is 'declared' in add the following to `imports`. + ``` + ExtensionService.declare([ + <component name>, + ]) + ``` + +> The module referencing the component, or another referencing it, must be imported by the core module as described in [above](/docs/extensions/frontend#including-modules-and-routes) + +### Loading Indicator + +On slower connections, it can take a few seconds to load the main Javascript resources for Stratos. + +In order to give the user some initial feedback that Stratos is loading, a loading indicator is included in the `index.html` file. This gets shown as early as possible, as soon as this main html file has loaded. Once the main code has been fetched, the view refreshes to show the application. + +A default loading indicator is provided that can be changed. To do so, create the following two in your extension or theme package: + +- `loading.css` - CSS styles to be included in a style block in the head of the index page +- `loading.html` - HTML markup to be included the the index page to render the loading indicator + +Then reference them to your package's `package.json` + +``` + "stratos": { + ... + "theme": { + "loadingCss": "loader/loading.css", + "loadingHtml": "loader/loading.html" + } + ... + }, +``` + +The files for the default indicator can be found in the `src/frontend/packages/theme/loader` folder. + +An example of a different loading indicator is included with the ACME sample in `src/frontend/packages/example-theme/loader`. + +The customization task will insert the appropriate CSS and HTML files into the main index.html file when it runs. + +Take a look at the template for the `index.html` file in `src/frontend/packages/core/misc/custom/index.html`. The CSS file is inserted where the marker `/** @@LOADING_CSS@@ **/` is and the HTML file where `<!-- @@LOADING_HTML@@ -->` is. + +### Login Page + +The log in page can be replaced by another Angular component. This can extend the original log in component and provide the same functionality, +see `src/frontend/packages/example-extensions/src/acme-login`. + +1. Create a new log in component that will contain the same form and fields. The component should have the decorator `@StratosLoginComponent()` + ``` + @StratosLoginComponent() + @Component({ + selector: 'app-acme-login', + templateUrl: './acme-login.component.html', + styleUrls: ['./acme-login.component.scss'], + encapsulation: ViewEncapsulation.None + }) + export class AcmeLoginComponent extends LoginPageComponent { + ... + ``` +1. The new component should be declared and set as an entry point, as well as imported via the extension service, in a module + ``` + imports: [ + ... + ExtensionService.declare([ + AcmeLoginComponent, + ]) + ... + ], + // FIXME: Ensure that anything lazy loaded/in kube endpoint pages is not included here - #3675 + declarations: [ + ... + AcmeLoginComponent, + ... + ], + entryComponents: [ + ... + AcmeLoginComponent, + ... + ] + }) + ``` + +### Other Points + +#### Customization Service +A customization service provides a number of smaller extension points. + +|Property | Description| +|--|--| +|hasEula| True if there's a EULA to show. When set to true the asset `/core/eula.html` must exist. For information about custom package assets see the images section [here](/docs/extensions/theming#new-images). | +|copyright| Text shown at the bottom of the side nav| +|logoText| Text shown with the side nav logo| +|aboutInfoComponent| Replace the component used in the Stratos `About` page| +|supportInfoComponent| Replace the component used to provide support information int he Stratos `About` page| +|noEndpointsComponent| Replace the component used in the Endpoints page when there are no registered endpoints| +|alwaysShowNavForEndpointTypes| True to always show the side nav menu items even if an Endpoint for that type is not connected. For example set to `false` to hide CF based menu items such as `Application` if no CF is connected| + +To utilize these define them in a `CustomizationsMetadata` object and apply them using the Angular service `CustomizationService` + +``` + constructor(cs: CustomizationService) { + const customizations: CustomizationsMetadata = { + copyright: '© 2020 Me', + hasEula: true, + aboutInfoComponent: MyAboutInfoComponent, + noEndpointsComponent: MyWelcomeComponent, + alwaysShowNavForEndpointTypes: (typ) => false, + } + cs.set(customizations); + } + +``` + +#### stratos.yaml +A few 'higher up' extension points can be found in `./stratos.yaml`. For example the [SUSE stratos.yaml](https://github.com/SUSE/stratos/blob/master/stratos.yaml) is below. + +``` +title: SUSE Stratos Console +productVersion: 2.0.0 +``` + +|Property|Description| +|--|--| +|title| Official product title, shown in `About` page and other custom places| +|productVersion| Use when building `helm` charts| + + +## Example: Adding a Custom Tab + +In this example, we will walk through extending the Stratos front-end. A new tab will be added to the Cloud Foundry Application page. + +This walk-through assumes that you have installed the Angular CLI globally - this can be done with `npm install -g @angular/cli`. + +### Create a new extensions package + +> Extension packages can contain many components and even a theme. The example here assumes a fresh start so a new package must be created + +1. Create the directory + ``` + mdkir src/frontend/packages/my-custom-module + ``` +1. Create an angular module in `my-custom-module` called `my-example.module.ts` + ``` + import { CommonModule } from '@angular/common'; + import { NgModule } from '@angular/core'; + + @NgModule({ + imports: [ + CommonModule + ], + declarations: [ + ] + }) + export class MyExampleModule { } + ``` +1. Create a `public-api.ts` in `my-custom-module` and reference it in your applications `tsconfig.json`. + + `public-api.ts` + ``` + export * from './my-example.module'; + ``` + `tsconfig.json` + ``` + "paths": { + ... + "@myexamples/extensions": ["frontend/packages/my-custom-module/public-api.ts"] + .... + } + ``` + +1. Create a `package.json` in `my-custom-module` + ``` + { + "name": "@myexamples/extensions", + "version": "0.0.1", + "peerDependencies": { + "@angular/common": "^6.0.0-rc.0 || ^6.0.0", + "@angular/core": "^6.0.0-rc.0 || ^6.0.0" + }, + "stratos": { + "module": "MyExampleModule" + } + } + ``` + +### Create a new Component for our Tab + +Create a new Angular component with the CLI: + +``` +cd src/frontend/packages/my-custom-module +ng generate component example-tab-extension +``` + +This will automatically declare the component in `MyExampleModule` + +### Add Decorator to make this Component an Extension + +In a text editor, open the file: + +``` +src/frontend/packages/my-custom-module/example-tab-extension/example-tab-extension.component.ts +``` + +Add the following decorator to the component at the top of the file: + +``` +import { StratosTab, StratosTabType } from '@stratosui/core'; + +@StratosTab({ + icon: 'done', + type: StratosTabType.Application, + label: 'Example App Tab', + link: 'example' +}) +``` + +The file should now look like this: + +``` +import { Component, OnInit } from '@angular/core'; +import { StratosTab, StratosTabType } from '@stratosui/core'; + +@StratosTab({ + icon: 'done', + type: StratosTabType.Application, + label: 'Example App Tab', + link: 'example' +}) +@Component({ + selector: 'app-example-tab-extension', + templateUrl: './example-tab-extension.component.html', + styleUrls: ['./example-tab-extension.component.scss'] +}) +export class ExampleTabExtensionComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} +``` + +Save the file. + +### Update the module + +The component must now be marked as an entry component and imported in such a way angular tree shaking is avoided. + +To do this, in a text editor, open the file `src/frontend/packages/my-custom-module/my-example.module.ts` update +- the file imports section +- the module import array +- the entry component array + +``` +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { ExtensionService } from '@stratosui/core'; + +import { ExampleTabExtensionComponent } from './example-tab-extension/example-tab-extension.component'; + +@NgModule({ + imports: [ + CommonModule, + ExtensionService.declare([ + ExampleTabExtensionComponent, + ]) + ], + declarations: [ExampleTabExtensionComponent], + entryComponents: [ExampleTabExtensionComponent] +}) +export class MyExampleModule { } +``` + +### Run it + +You should now be able to run Stratos [locally](/docs/developer/introduction) and see this new tab on the application page for an application. diff --git a/website/docs/extensions/introduction.md b/website/docs/extensions/introduction.md new file mode 100644 index 0000000000..b36474090c --- /dev/null +++ b/website/docs/extensions/introduction.md @@ -0,0 +1,61 @@ +--- +title: Extending Stratos +sidebar_label: Introduction +--- + +Stratos can be customized in a number of ways. Colors and fonts can be updated to add unique branding, additional menu items can be added in a number of places, custom EULAs can be used, new Stratos Jetstream (backend) endpoints, and much more. + +> For those with existing customization migrating to 4.0 please see our [upgrade guide](/docs/extensions/v4-migration). + +## Approach + +In order to customize Stratos, you will need to fork the Stratos GitHub repository and apply customizations in your fork. Our aim is to minimize any merge conflicts that might occur when re-basing your fork with the upstream Stratos repository. + +### Frontend + +Frontend customizations are placed in angular packages in the folder named `src/frontend/packages`. In the future you will be able to host these packages in npm and bring them into Stratos in the usual npm dependency way. There are no additional processes or build steps required for Stratos to then integrate these packages. All steps will be automatically applied under the hood during `npm install` and when `ng build`/`ng serve` runs. + +Information on custom theming can be found in the [theming page](/docs/extensions/theming). + +Information on additional functionality can be found in the [extensions page](/docs/extensions/frontend). + +### Backend (Jetstream) + +Jetstream customizations are written in go and can be added in `src/jetstream/plugins`. In the future we hope to combine this work with the frontend changes, such that all functionality for +a feature can be applied to Stratos in a similar way. + +More information can be found in the [custom plugins page](/docs/extensions/backend). + +## Examples + +### ACME +The ACME example contains a number of [theming](https://github.com/cloudfoundry/stratos/tree/master/src/frontend/packages/example-theme) and [functionality](https://github.com/cloudfoundry/stratos/tree/master/src/frontend/packages/example-extensions) customization. + +To run Stratos with these customizations + +1. Include the example packages (by default these are excluded). Do this by... + 1. Creating the file `stratos.yaml` in the root of the repo + 2. Adding the following content to `stratos.yaml` + ``` + packages: + include: + - '@stratosui/core' + - '@stratosui/shared' + - '@stratosui/store' + - '@stratosui/cloud-foundry' + - '@stratosui/cf-autoscaler' + - '@stratosui/theme' + - '@example/extensions' + - '@example/theme' + ``` +1. Run Stratos the usual way, for more information see the [Developer Guide](/docs/developer/introduction). + +### SUSE + +More advanced, real world examples can be found the in SUSE Stratos repository, again containing [theming](https://github.com/SUSE/stratos/tree/master/src/frontend/packages/suse-theme) and [functionality](https://github.com/SUSE/stratos/tree/master/src/frontend/packages/suse-extensions) customizations. + +To run Stratos with these customizations simply start the console the usual way from that repo, for more information see the [Developer Guide](/docs/developer/introduction). + +## Further Reading + +Detailed instructions on how to customize the [theme](/docs/extensions/theming), [frontend functionality](/docs/extensions/frontend) and [backend](/docs/extensions/backend) can be found in this section. diff --git a/website/docs/extensions/theming.md b/website/docs/extensions/theming.md new file mode 100644 index 0000000000..50d26d74da --- /dev/null +++ b/website/docs/extensions/theming.md @@ -0,0 +1,258 @@ +--- +title: Theming Stratos +sidebar_label: Theming Stratos +--- + +Stratos provides a mechanism for customizing the theme, including: + +- Changing colors +- Changing image assets (favorite icon, login background and logo) +- Using additional fonts +- Adding/Overriding styles +- Applying theme colors to components + +Theme's are best encapsulated in a new npm package. It should contain the usual `package.json` file with a `stratos` section which will contain some of the theme customizations. + +An example of the type of package that can be created can be found in the [ACME example](https://github.com/cloudfoundry/stratos/tree/master/src/frontend/packages/example-theme). To run Stratos with these customizations see [here](/docs/extensions/introduction#acme). + +## Colors +Stratos uses Material Design and the [angular-material](https://material.angular.io/) library and uses the same approach to theming. + +To create your own theme you will need to create a color pallet and provide it to Stratos as a material theme. This theme can also contain +additional colors that customize core parts of the page (header, side navigator menu, avatar, etc). + +To do this create the file `_index.scss` in the root of your theme package. This should contain a Stratos scss function that will return a `theme` +object via the Stratos helper function `stratos-theme-helper`, for [example](https://github.com/cloudfoundry/stratos/blob/master/src/frontend/packages/example-theme/_index.scss). + +### Core Pallet +In most cases you will probably want to generate a palette for the primary color for your version of Stratos - an example `custom.scss` for this is shown below: + +``` +$acme-primary: (50: #e8eaf6, 100: #c5cbe9, 200: #9fa8da, 300: #7985cb, 400: #5c6bc0, 500: #3f51b5, 600: #394aae, 700: #3140a5, 800: #29379d, 900: #1b278d, A100: #c6cbff, A200: #939dff, A400: #606eff, A700: #4757ff, contrast: ( 50: #000000, 100: #000000, 200: #000000, 300: #000000, 400: #ffffff, 500: #ffffff, 600: #ffffff, 700: #ffffff, 800: #ffffff, 900: #ffffff, A100: #000000, A200: #000000, A400: #ffffff, A700: #ffffff, )); +$mat-red: ( 50: #ffebee, 100: #ffcdd2, 200: #ef9a9a, 300: #e57373, 400: #ef5350, 500: #f44336, 600: #e53935, 700: #d32f2f, 800: #c62828, 900: #b71c1c, A100: #ff8a80, A200: #ff5252, A400: #ff1744, A700: #d50000, contrast: ( 50: $black-87-opacity, 100: $black-87-opacity, 200: $black-87-opacity, 300: $black-87-opacity, 400: $black-87-opacity, 500: white, 600: white, 700: white, 800: $white-87-opacity, 900: $white-87-opacity, A100: $black-87-opacity, A200: white, A400: white, A700: white, )); + +// Create palettes +$acme-theme-primary: mat-palette($acme-primary); +$acme-theme-warn: mat-palette($mat-red); + +// Create a theme from the palette (secondary theme is the same as the primary in this example) +$stratos-theme: mat-light-theme($acme-theme-primary, $acme-theme-primary, $acme-theme-warn); + +// Create a similar theme but make it for dark mode +$stratos-dark-theme: mat-dark-theme($acme-theme-primary, $acme-theme-primary, $acme-theme-warn); + +``` + +### Stratos Colors + +Additional Stratos colors can be customized by supplying more colors to the `theme` object. Defining these colors helps reduce the amount of custom css the theme has to use. + +> You do not have to specify all of these - defaults will be used if they are not set. + +|Property|Description| +|---|---| +|app-background-color| Base color to show in the background of the application | +|app-background-text-color| Color of text when placed on the basic background | +|side-nav| See [below](/docs/extensions/theming#side-nav-colors) | +|status| See [below](/docs/extensions/theming#status-colors)| +|subdued-color| Lighter color meant to be a subdued version of the primary color | +|ansi-colors| See [below](/docs/extensions/theming#ansi-colors)| +|header-background-color| Background color for the main stratos header| +|header-foreground-color| Foreground color for the main stratos | +|stratos-title-show-text| Boolean - Show `Stratos` or provided title with the large logo in the about page, default log in page, etc | +|header-background-span|Does the header background color span across the top, or is the sidenav background color used for the top-left portion| +|underflow-background-color|Background colors to use for things like the about page header (underflow)| +|underflow-foreground-color|Background colors to use for things like the about page header (underflow)| +|link-color| Color for hyperlinks| +|link-active-color| Color of visited hyperlinks| +|user-avatar-background-color| Background color of the default avatar in the main header| +|user-avatar-foreground-color| Foreground color of the default avatar in the main header| +|user-avatar-underflow-invert-colors| Boolean - Invert the user-avatar colors| +|user-avatar-header-invert-colors| Boolean - Invert the user-avatar colors in the main header| +|intro-screen-background-color| Color of the background to introduction style screens (log in, log out, etc)| + + +#### Side Nav Colors +Colors that define how the top level navigation menu on the left of Stratos appears. + +|Property|Description| +|---|---| +|background| Background color of the side nav| +|text| Color of text when menu item is not selected| +|active| Color of background of a selected item| +|active-text| Color of text of a selected item| +|hover| Color of background of an item when hovered on| +|hover-text| Color of text of an item when hovered on| + +#### Status Colors +Colors that are associated with the standard levels of statuses. + +|Property|Description| +|---|---| +|success|| +|info|| +|warning|| +|danger|| +|tentative|| +|busy|| +|text|| + +#### Ansi Colors +Terminal style set of colors to show for logging output. + +|Property|Description| +|---|---| +|default-foreground|| +|default-background|| +|black|| +|red|| +|green|| +|yellow|| +|blue|| +|magenta|| +|cyan|| +|white|| + + +### Dark theme + +By default the theme will not contain a dark mode and the UX for enabled/disabling it will be hidden. + +In order to add a dark mode to your theme an additional `dark` color theme should be provided by the `stratos-theme` theme function in your +theme's `_index.scss`, for example +in [_index.scss](https://github.com/cloudfoundry/stratos/blob/master/src/frontend/packages/example-theme/_index.scss). + +``` +@function stratos-theme() { + $theme: stratos-theme-helper($stratos-theme); + @return ( + default: create-custom-theme($stratos-theme), + dark: create-dark-theme() + ); +} +``` + +Within the dark theme the default theme's additional Stratos colors can be overwritten. + + +## Images + +### Replace Stratos Images + + + + +Images that Stratos uses can be overwritten by a theme, for example the logo and favicon. To do so the new images should be added to the +package and then referenced in the theme's `package.json` including the path of the image they overwrite. Below are some prominent examples. + + +|File name|Path|Description| +|---|---|---| +|`favicon.ico`|`favicon.ico`|Favorite icon to use| +|`logo.png`|`core/assets/logo.png`|Logo to use on login screen and about page| +|`nav-logo.png`|`core/assets/nav-logo.png`|Logo to use in the top-left side navigation for the application logo| + + +> The `nav-logo.png` logo should have a height of 36px and a maximum width of 180 pixels. + +> Files written to `core/assets` should be done in one line. In the example below both logo.png and nav-logo.png and done via the one +> line + +Example `package.json` + +``` + "stratos": { + ... + "assets": { + "assets/core": "core/assets", + "assets/favicon.ico": "favicon.ico", + }, + ... + }, +``` + +### New Images + +When images are used by custom components they should also be referenced in the theme's `package.json`. Whole folders can be included and should +be written to `core/assets`. When references by the components they use that path `src="/core/assets/custom_image.png"` + +``` + "stratos": { + ... + "assets": { + "local/assets": "core/assets", + }, + ... + }, +``` + +## Fonts +Any custom fonts used by the theme or extensions can be provided in a similar way to images, the files should be added to the theme's package +and then referenced in the theme's `package.json`. + +``` + "stratos": { + "assets": { + ... + "assets/fonts": "suse/fonts" + ... + }, +``` + +## Styles + +We don't generally recommend modifying styles, since from version to version of Stratos, we may change the styles used slightly which can mean any modifications you made will need updating. Should you wish to do so, you can modify these in the same `_index_.scss`. For example the Acme[_index.scss](https://github.com/cloudfoundry/stratos/blob/master/src/frontend/packages/example-theme/_index.scss) imports a file that adds a text color + +``` +body.stratos { + .favorite-list { + .app-no-content-container { + color: $acme-dark-blue; + } + } +} +``` + +Note that the class `stratos` has been placed on the `BODY` tag of the Stratos application to assist with css selector specificity. + +## Components + +Angular components in your packages can be themed, which provides them access to the theme's colors. To do this, in your extensions package that contains the components (this may be different to your theme package), add a `_all-theme.scss` file containing a scss function. This function +should be referenced in the package's package.json and is called by the Stratos extension mechanism. + +The `_all-theme.scss` and `package.json` content may look like below + +``` +@mixin apply-theme-suse-extensions($stratos-theme) { + + $theme: map-get($stratos-theme, theme); + $app-theme: map-get($stratos-theme, app-theme); + +} +``` + +``` + "stratos": { + ... + "theming": "sass/_all-theme#apply-theme-suse-extensions", + ... + } +``` + +Component theme files can then be defined and their own scss functions being called from `_all-theme.scss` + +``` +@mixin apply-theme-suse-extensions($stratos-theme) { + ... + @include kube-analysis-report-theme($theme, $app-theme); + ... +} +``` + +``` +@mixin kube-analysis-report-theme($theme, $app-theme) { + $backgrounds: map-get($theme, background); + $background: mat-color($backgrounds, card); + $background-color: map-get($app-theme, app-background-color); +} +``` diff --git a/website/docs/extensions/v4-migration.md b/website/docs/extensions/v4-migration.md new file mode 100644 index 0000000000..1317327c26 --- /dev/null +++ b/website/docs/extensions/v4-migration.md @@ -0,0 +1,46 @@ +--- +title: Migrating to V4 +sidebar_label: Migrating to V4 +--- + +In version 4 of Stratos there are breaking customization changes from previous versions. These changes allow a much improved approach to +extensions by opening the door to npm style plugins. + +Prominent changes include +- `custom-src` has been removed along with the symlink approach of including files. Custom code is now added as npm packages in `src/frontend/packages`. +Modules and routes are now exposed in a more standard package way. More info [here](/docs/extensions/frontend#including-modules-and-routes). +Some existing components will not be included in some production style builds without also declaring them to the extension service, see +usages of `ExtensionService.declare`. +- Material Design theming approach has been expanded to include many common colors, this removes the need to apply custom styles in a lot of cases. More info [here](/docs/extensions/theming#colors). +- Dark theme is applied in a different way. More info [here](/docs/extensions/theming#dark-theme). +- Image assets are replaced in a different way. More info [here](/docs/extensions/theming#images). +- Custom component can now be themed, so theme colors can be accessed from within and applied. More info [here](/docs/extensions/theming#components). +- A new 'loading' indicator has been added that you may wish to customize, more info [here](/docs/extensions/frontend#loading-indicator). + +## Basic Migration Steps +To aid in migrating we've provided these instructions. + +1. Before updating to the latest code... + - Run `npm run customize-reset` to remove all previously created sym links. + - Read through the customization documentation below to get a better understanding of the new process. +1. Update your codebase with the desired v4 code. +1. Run `npm install` (only required first time, this will ensure you have the required version of Angular and the new devkit is built). +1. Change directory to `./build/tools/v4-migration` and run the migration script `./migrate.sh`. + - This will copy your customizations from `custom-src` to a new Angular package `src/frontend/packages/custom_extensions`. +1. Check that the new package exports your custom module and if applicable your custom-routing module. + - The migrate script should do this in `src/frontend/packages/custom_extensions/src/public-api.ts`. +1. Check that your ts config file defines the public api file. + - `src/tsconfig.json` file's `compilerOptions/paths` section should contain something like `"@custom/extensions": ["frontend/packages/custom_extensions/src/public-api.ts"]`. +1. Check that your new package's `package.json` defines your custom module and if applicable custom-routing module. + - See `src/frontend/packages/suse-extensions/package.json` file's `stratos` section. + - Note your `routingModule` entry label should not have a preceding `_`. +1. Build Stratos in your usual way, for instance `npm run build`. + - It could be that this fails due to TypeScript import issues, if so go through these and fix. + - During build time the custom packages will be discovered and output, see section starting `Building with these extensions`. These should contain the modules your require. +1. Run Stratos your usual way. Ensure you can navigate to all your custom parts. +1. Once you are happy everything works as intended remove the old `./custom-src` directory and commit you changes. + +## Further Guidance +Our ACME demo (`src/frontend/packages/example-extensions` and `src/frontend/packages/example-theme`) and SUSE repo ([theme](https://github.com/SUSE/stratos/tree/master/src/frontend/packages/suse-theme) and [extensions](https://github.com/SUSE/stratos/tree/master/src/frontend/packages/suse-extensions)) have both been updated and are fully compatible with the 4.0 changes. Both are a good source for examples. + +If there any questions or issues please reach out to us either on our Github [repo](https://github.com/cloudfoundry/stratos) or Slack room [#stratos](https://cloudfoundry.slack.com/?redir=%2Fmessages%2Fstratos). diff --git a/website/docs/introduction.md b/website/docs/introduction.md new file mode 100644 index 0000000000..f9706d79a5 --- /dev/null +++ b/website/docs/introduction.md @@ -0,0 +1,63 @@ +--- +id: introduction +title: Introduction +sidebar_label: Introduction +--- + +Stratos is an Open Source Web-based UI for Cloud Foundry and Kubernetes. + +> Note: The Kubernetes additions to Stratos are currently maintained by SUSE and we are investigating the process of upstreaming these to the cloudfoundry GitHub repository. These would then become a standard part of Stratos build and install. + +For Cloud Foundry, it allows users and administrators to both manage applications running in the Cloud Foundry cluster and perform cluster management tasks. + +For Kubernetes, it provides Developers with views of their Kubernetes resources, the ability to view and deploy Helm Charts and view Workloads. + +![Stratos Application view](../images/screenshots/app-summary.png) + +## Quick Start + +To get started quickly, we recommend following the steps to deploy the Stratos Console as a Cloud Foundry Application - see [here](deploy/cloud-foundry/cloud-foundry). + +If you have [docker](https://www.docker.com/community-edition) installed, you can quickly deploy Stratos using the all-in-one container: +``` +$ docker run -p 4443:443 splatform/stratos:latest +``` + +Once that has finished, you can then access Stratos by visiting https://localhost:4443. + +## Deploying Stratos + +Stratos can be deployed in the following environments: + +1. Cloud Foundry, as an application. See [guide](deploy/cloud-foundry/cloud-foundry) +2. Kubernetes, using a Helm chart. See [guide](deploy/kubernetes) +3. Docker, single container deploying all components. See [guide](deploy/all-in-one) + +## Troubleshooting + +Please see our [Troubleshooting](deploy/troubleshooting) page. + +## Further Reading + +Get an [Overview](overview.md) of Stratos, its components and the different ways in which it can be deployed. + +Browse through features and issues in the project's [issues](https://github.com/cloudfoundry/stratos/issues) page. + +What kind of code is in Stratos? We've integrated [Code Climate](https://codeclimate.com) for some code quality and maintainability metrics. Take a stroll around the [project page](https://codeclimate.com/github/cloudfoundry/stratos) + +## Contributing + +We very much welcome developers who would like to get involved and contribute to the development of the Stratos project. Please refer to the [Contributing guide](developer/contributing.md) for more information. + +For information to help getting started with development, please read the [Developer's Guide](developer/introduction). + +## Support and feedback + +We have a channel (#stratos) on the Cloud Foundy Slack where you can ask questions, get support or give us feedback. We'd love to hear from you if you are using Stratos. + +You can join the Cloud Foundry Slack here - https://slack.cloudfoundry.org/ - and then join the #stratos channel. + +## License + +The work done has been licensed under Apache License 2.0. The license file can be found [here](license). + diff --git a/website/docs/license.md b/website/docs/license.md new file mode 100644 index 0000000000..5fb10ab03a --- /dev/null +++ b/website/docs/license.md @@ -0,0 +1,184 @@ +--- +id: license +title: License +sidebar_label: License +--- + +Stratos is licensed under the Apache 2.0 Software License, shown below: + +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. +``` diff --git a/website/docs/overview.md b/website/docs/overview.md new file mode 100644 index 0000000000..a0c2c39d92 --- /dev/null +++ b/website/docs/overview.md @@ -0,0 +1,29 @@ +--- +id: overview +title: Stratos Overview +sidebar_label: Overview +--- + +Stratos provides a web-based UI to allow developers and administrators to manage their applications and cloud foundry deployment(s). + +It is designed to manage one or more Cloud Foundry deployments. It does so by managing "endpoints", where each endpoint is a reference to a Cloud Foundry deployment. The notion of an endpoint is not specific to Cloud Foundry, allowing Stratos to connect to other service types in the future. + +Stratos stores endpoint metadata in a relational database. Administrators of Stratos are able to register (add) new endpoints to the Console. All users are able to then connect to these endpoints using their credentials, ensuring that they get the appropriate level of access when interacting with Cloud Foundry. + +The high-level architecture of Stratos is shown in the diagram below: + +![Stratos High-Level Architecture](../images/high-level-arch.png) + +The main components: + +* Web UI - Single-page AngularJS web application providing the front-end that runs in the user's browser. +* API Server - Provides the back-end APIs that support the front-end UI. This API takes care of authentication, endpoint management and proxying API requests from the front-end to the desired back-end API. +* Endpoint Datastore - Relational database that stores the registered endpoints and the encrypted user access tokens. + +## Authentication + +Stratos authenticates users using a Cloud Foundry UAA service. It must be configured with the details necessary to communicate with a UAA. When the user logs in to the Console, their login will be validated with the UAA. The Console uses a scope to identify Console administrators from regular users. + +Administrators use the 'Endpoints Dashboard' within the Console to add new endpoints to the Console. All users are then able to connect to these endpoints by providing their credentials. The Console will use these credentials to communicate with the UAA for the given endpoint (typically Cloud Foundry) and obtain a refresh and access token. These tokens are encrypted and stored in the Endpoint Datastore. + +When a user interacts with the Console and API requests need to be made to a given Cloud Foundry endpoint, these are sent to the API Server along with a custom http header which indicated which endpoint(s) the requests should be send to. The API Server will forward the request to the appropriate endpoints, first looking up the access and refresh tokens required to communicate with the endpoint(s). If any access token has expired, it will use the refresh token to obtain a new access token. diff --git a/docs/status_updates.md b/website/docs/status_updates.md similarity index 100% rename from docs/status_updates.md rename to website/docs/status_updates.md diff --git a/website/docs/talks.md b/website/docs/talks.md new file mode 100644 index 0000000000..fcfe03a4e8 --- /dev/null +++ b/website/docs/talks.md @@ -0,0 +1,13 @@ +--- +id: talks +title: Talks and Presentations +sidebar_label: Talks +--- + +|Date|Title|Video| +|---|---|---| +|18 Sep 2019|**Multi-cloud Management: Stratos and Kubernetes**|[Video](https://www.youtube.com/watch?v=mIWy1_672No)| +|13 Sep 2019|**Stratos Project Update: The Future of the Stratos Management UI**|[Video](https://www.youtube.com/watch?v=oGN0v7Qpdi4)| +|16 Oct 2018|**Stratos: A Cloud Foundry UI - Past, Present and Future**|[Video](https://www.youtube.com/watch?v=oikYcGAMEdE)| +|27 Apr 2018|**Stratos UI - An Open Source UI for Cloud Foundry**|[Video](https://www.youtube.com/watch?v=zc_wbsu66lE)| +|18 Oct 2017|**Lightning Talk: Cloud Native Application Management with the Stratos Console UI**|[Video](https://www.youtube.com/watch?v=duGpDkdrFmw)| diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js new file mode 100644 index 0000000000..989742ae37 --- /dev/null +++ b/website/docusaurus.config.js @@ -0,0 +1,96 @@ +module.exports = { + title: 'STRATOS', + tagline: 'Open-Source Multi-Cluster UI for Cloud Foundry and Kubernetes', + url: 'https://stratos.app', + baseUrl: '/', + favicon: 'img/favicon.ico', + organizationName: 'cloudfoundry', + projectName: 'stratos', + themeConfig: { + disableDarkMode: true, + navbar: { + title: 'STRATOS', + logo: { + alt: 'Stratos', + src: 'img/logo.png', + }, + links: [ + { + to: 'docs/', + activeBasePath: 'docs', + label: 'Docs', + position: 'right', + }, + // {to: 'blog', label: 'Blog', position: 'left'}, + { + href: 'https://github.com/cloudfoundry/stratos', + label: 'GitHub', + position: 'right', + }, + ], + }, + footer: { + style: 'dark', + links: [ + { + title: 'Docs', + items: [ + { + label: 'Getting Started', + to: 'docs/', + }, + { + label: 'Deploying Stratos', + to: 'docs/deploy/overview', + }, + ], + }, + { + title: 'Community', + items: [ + { + label: 'Slack', + href: 'https://cloudfoundry.slack.com/?redir=%2Fmessages%2Fstratos', + }, + { + label: 'GitHub', + href: 'https://github.com/cloudfoundry/stratos', + }, + ], + }, + + { + title: 'More', + items: [ + { + label: 'Presentations and Talks', + to: 'docs/talks', + }, + ], + }, + ], + copyright: `Copyright © ${new Date().getFullYear()} Cloud Foundry Foundation`, + }, + }, + presets: [ + [ + '@docusaurus/preset-classic', + { + docs: { + // It is recommended to set document id as docs home page (`docs/` path). + homePageId: 'introduction', + sidebarPath: require.resolve('./sidebars.js'), + // Please change this to your repo. + editUrl: + 'https://github.com/cloudfoundry/stratos/edit/master/website/', + }, + theme: { + customCss: require.resolve('./src/css/custom.css'), + }, + }, + ], + ], + stylesheets: [ + //'https://fonts.googleapis.com/icon?family=Material+Icons' + ] +}; diff --git a/website/package-lock.json b/website/package-lock.json new file mode 100644 index 0000000000..b25d4a0c8c --- /dev/null +++ b/website/package-lock.json @@ -0,0 +1,13192 @@ +{ + "name": "website", + "version": "0.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/compat-data": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.10.4.tgz", + "integrity": "sha512-t+rjExOrSVvjQQXNp5zAIYDp00KjdvGl/TpDX5REPr0S9IAIPQMTilcfG6q8c0QFmj9lSTVySV2VTsyggvtNIw==", + "requires": { + "browserslist": "^4.12.0", + "invariant": "^2.2.4", + "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "@babel/core": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.4.tgz", + "integrity": "sha512-3A0tS0HWpy4XujGc7QtOIHTeNwUgWaZc/WuS5YQrfhU67jnVmsD6OGPc1AKHH0LJHQICGncy3+YUjIhVlfDdcA==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.4", + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helpers": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "@babel/generator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.4.tgz", + "integrity": "sha512-toLIHUIAgcQygFZRAQcsLQV3CBuX6yOIru1kJk/qqqvcRmZrYe6WavZTSG+bB8MxhnL9YPf+pKQfuiP161q7ng==", + "requires": { + "@babel/types": "^7.10.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", + "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", + "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", + "requires": { + "@babel/helper-explode-assignable-expression": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-builder-react-jsx": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.4.tgz", + "integrity": "sha512-5nPcIZ7+KKDxT1427oBivl9V9YTal7qk0diccnh7RrcgrT/pGFOjgGw1dgryyx1GvHEpXVfoDF6Ak3rTiWh8Rg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-builder-react-jsx-experimental": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.10.4.tgz", + "integrity": "sha512-LyacH/kgQPgLAuaWrvvq1+E7f5bLyT8jXCh7nM67sRsy2cpIGfgWJ+FCnAKQXfY+F0tXUaN6FqLkp4JiCzdK8Q==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-module-imports": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz", + "integrity": "sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==", + "requires": { + "@babel/compat-data": "^7.10.4", + "browserslist": "^4.12.0", + "invariant": "^2.2.4", + "levenary": "^1.1.1", + "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.4.tgz", + "integrity": "sha512-9raUiOsXPxzzLjCXeosApJItoMnX3uyT4QdM2UldffuGApNrF8e938MwNpDCK9CPoyxrEoCgT+hObJc3mZa6lQ==", + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz", + "integrity": "sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-regex": "^7.10.4", + "regexpu-core": "^4.7.0" + } + }, + "@babel/helper-define-map": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.4.tgz", + "integrity": "sha512-nIij0oKErfCnLUCWaCaHW0Bmtl2RO9cN7+u2QT8yqTywgALKlyUVOvHDElh+b5DwVC6YB1FOYFOTWcN/+41EDA==", + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/types": "^7.10.4", + "lodash": "^4.17.13" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.4.tgz", + "integrity": "sha512-4K71RyRQNPRrR85sr5QY4X3VwG4wtVoXZB9+L3r1Gp38DhELyHCtovqydRi7c1Ovb17eRGiQ/FD5s8JdU0Uy5A==", + "requires": { + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", + "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.4.tgz", + "integrity": "sha512-m5j85pK/KZhuSdM/8cHUABQTAslV47OjfIB9Cc7P+PvlAoBzdb79BGNfw8RhT5Mq3p+xGd0ZfAKixbrUZx0C7A==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.4.tgz", + "integrity": "sha512-Er2FQX0oa3nV7eM1o0tNCTx7izmQtwAQsIiaLRWtavAAEcskb0XJ5OjJbVrYXWOTr8om921Scabn4/tzlx7j1Q==", + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4", + "lodash": "^4.17.13" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" + }, + "@babel/helper-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.4.tgz", + "integrity": "sha512-inWpnHGgtg5NOF0eyHlC0/74/VkdRITY9dtTpB2PrxKKn+AkVMRiZz/Adrx+Ssg+MLDesi2zohBW6MVq6b4pOQ==", + "requires": { + "lodash": "^4.17.13" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.4.tgz", + "integrity": "sha512-86Lsr6NNw3qTNl+TBcF1oRZMaVzJtbWTyTko+CQL/tvNvcGYEFKbLXDPxtW0HKk3McNOk4KzY55itGWCAGK5tg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-wrap-function": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", + "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" + }, + "@babel/helper-wrap-function": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz", + "integrity": "sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==", + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helpers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", + "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", + "requires": { + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "@babel/parser": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.4.tgz", + "integrity": "sha512-8jHII4hf+YVDsskTF6WuMB3X4Eh+PsUkC2ljq22so5rHvH+T8BzyL94VOdyFLNR8tBSVXOTbNHOKpR4TfRxVtA==" + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.4.tgz", + "integrity": "sha512-MJbxGSmejEFVOANAezdO39SObkURO5o/8b6fSH6D1pi9RZQt+ldppKPXfqgUWpSQ9asM6xaSaSJIaeWMDRP0Zg==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4", + "@babel/plugin-syntax-async-generators": "^7.8.0" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz", + "integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz", + "integrity": "sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-dynamic-import": "^7.8.0" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz", + "integrity": "sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.0" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz", + "integrity": "sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz", + "integrity": "sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.4.tgz", + "integrity": "sha512-6vh4SqRuLLarjgeOf4EaROJAHjvu9Gl+/346PbDH9yWbJyfnJ/ah3jmYKYtswEyCoWZiidvVHjHshd4WgjB9BA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.10.4" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz", + "integrity": "sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.4.tgz", + "integrity": "sha512-ZIhQIEeavTgouyMSdZRap4VPPHqJJ3NEs2cuHs5p0erH+iz6khB0qfgU8g7UuJkG88+fBMy23ZiU+nuHvekJeQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-optional-chaining": "^7.8.0" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz", + "integrity": "sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz", + "integrity": "sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz", + "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.4.tgz", + "integrity": "sha512-KCg9mio9jwiARCB7WAcQ7Y1q+qicILjoK8LP/VkPkEKaf5dkaZZK1EcTe91a3JJlZ3qy6L5s9X52boEYi8DM9g==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz", + "integrity": "sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.10.4.tgz", + "integrity": "sha512-oSAEz1YkBCAKr5Yiq8/BNtvSAPwkp/IyUnwZogd8p+F0RuYQQrLeRUzIQhueQTTBy/F+a40uS7OFKxnkRvmvFQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz", + "integrity": "sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz", + "integrity": "sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==", + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz", + "integrity": "sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.4.tgz", + "integrity": "sha512-J3b5CluMg3hPUii2onJDRiaVbPtKFPLEaV5dOPY5OeAbDi1iU/UbbFFTgwb7WnanaDy7bjU35kc26W3eM5Qa0A==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "lodash": "^4.17.13" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz", + "integrity": "sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-define-map": "^7.10.4", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz", + "integrity": "sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz", + "integrity": "sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz", + "integrity": "sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz", + "integrity": "sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz", + "integrity": "sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==", + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz", + "integrity": "sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz", + "integrity": "sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==", + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz", + "integrity": "sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz", + "integrity": "sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.4.tgz", + "integrity": "sha512-3Fw+H3WLUrTlzi3zMiZWp3AR4xadAEMv6XRCYnd5jAlLM61Rn+CRJaZMaNvIpcJpQ3vs1kyifYvEVPFfoSkKOA==", + "requires": { + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz", + "integrity": "sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==", + "requires": { + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.4.tgz", + "integrity": "sha512-Tb28LlfxrTiOTGtZFsvkjpyjCl9IoaRI52AEU/VIwOwvDQWtbNJsAqTXzh+5R7i74e/OZHH2c2w2fsOqAfnQYQ==", + "requires": { + "@babel/helper-hoist-variables": "^7.10.4", + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz", + "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==", + "requires": { + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", + "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz", + "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz", + "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.4.tgz", + "integrity": "sha512-RurVtZ/D5nYfEg0iVERXYKEgDFeesHrHfx8RT05Sq57ucj2eOYAP6eu5fynL4Adju4I/mP/I6SO0DqNWAXjfLQ==", + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz", + "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-react-constant-elements": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.10.4.tgz", + "integrity": "sha512-cYmQBW1pXrqBte1raMkAulXmi7rjg3VI6ZLg9QIic8Hq7BtYXaWuZSxsr2siOMI6SWwpxjWfnwhTUrd7JlAV7g==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-react-display-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.10.4.tgz", + "integrity": "sha512-Zd4X54Mu9SBfPGnEcaGcOrVAYOtjT2on8QZkLKEq1S/tHexG39d9XXGZv19VfRrDjPJzFmPfTAqOQS1pfFOujw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-react-jsx": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.4.tgz", + "integrity": "sha512-L+MfRhWjX0eI7Js093MM6MacKU4M6dnCRa/QPDwYMxjljzSCzzlzKzj9Pk4P3OtrPcxr2N3znR419nr3Xw+65A==", + "requires": { + "@babel/helper-builder-react-jsx": "^7.10.4", + "@babel/helper-builder-react-jsx-experimental": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.10.4" + } + }, + "@babel/plugin-transform-react-jsx-development": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.10.4.tgz", + "integrity": "sha512-RM3ZAd1sU1iQ7rI2dhrZRZGv0aqzNQMbkIUCS1txYpi9wHQ2ZHNjo5TwX+UD6pvFW4AbWqLVYvKy5qJSAyRGjQ==", + "requires": { + "@babel/helper-builder-react-jsx-experimental": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.10.4" + } + }, + "@babel/plugin-transform-react-jsx-self": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.10.4.tgz", + "integrity": "sha512-yOvxY2pDiVJi0axdTWHSMi5T0DILN+H+SaeJeACHKjQLezEzhLx9nEF9xgpBLPtkZsks9cnb5P9iBEi21En3gg==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.10.4" + } + }, + "@babel/plugin-transform-react-jsx-source": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.10.4.tgz", + "integrity": "sha512-FTK3eQFrPv2aveerUSazFmGygqIdTtvskG50SnGnbEUnRPcGx2ylBhdFIzoVS1ty44hEgcPoCAyw5r3VDEq+Ug==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.10.4" + } + }, + "@babel/plugin-transform-react-pure-annotations": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.10.4.tgz", + "integrity": "sha512-+njZkqcOuS8RaPakrnR9KvxjoG1ASJWpoIv/doyWngId88JoFlPlISenGXjrVacZUIALGUr6eodRs1vmPnF23A==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz", + "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==", + "requires": { + "regenerator-transform": "^0.14.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", + "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.4.tgz", + "integrity": "sha512-8ULlGv8p+Vuxu+kz2Y1dk6MYS2b/Dki+NO6/0ZlfSj5tMalfDL7jI/o/2a+rrWLqSXvnadEqc2WguB4gdQIxZw==", + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "resolve": "^1.8.1", + "semver": "^5.5.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz", + "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.4.tgz", + "integrity": "sha512-1e/51G/Ni+7uH5gktbWv+eCED9pP8ZpRhZB3jOaI3mmzfvJTWHkuyYTv0Z5PYtyM+Tr2Ccr9kUdQxn60fI5WuQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", + "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-regex": "^7.10.4" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.4.tgz", + "integrity": "sha512-4NErciJkAYe+xI5cqfS8pV/0ntlY5N5Ske/4ImxAVX7mk9Rxt2bwDTGv1Msc2BRJvWQcmYEC+yoMLdX22aE4VQ==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz", + "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-typescript": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.10.4.tgz", + "integrity": "sha512-3WpXIKDJl/MHoAN0fNkSr7iHdUMHZoppXjf2HJ9/ed5Xht5wNIsXllJXdityKOxeA3Z8heYRb1D3p2H5rfCdPw==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-typescript": "^7.10.4" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz", + "integrity": "sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz", + "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/preset-env": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.10.4.tgz", + "integrity": "sha512-tcmuQ6vupfMZPrLrc38d0sF2OjLT3/bZ0dry5HchNCQbrokoQi4reXqclvkkAT5b+gWc23meVWpve5P/7+w/zw==", + "requires": { + "@babel/compat-data": "^7.10.4", + "@babel/helper-compilation-targets": "^7.10.4", + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-proposal-async-generator-functions": "^7.10.4", + "@babel/plugin-proposal-class-properties": "^7.10.4", + "@babel/plugin-proposal-dynamic-import": "^7.10.4", + "@babel/plugin-proposal-json-strings": "^7.10.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", + "@babel/plugin-proposal-numeric-separator": "^7.10.4", + "@babel/plugin-proposal-object-rest-spread": "^7.10.4", + "@babel/plugin-proposal-optional-catch-binding": "^7.10.4", + "@babel/plugin-proposal-optional-chaining": "^7.10.4", + "@babel/plugin-proposal-private-methods": "^7.10.4", + "@babel/plugin-proposal-unicode-property-regex": "^7.10.4", + "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-class-properties": "^7.10.4", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0", + "@babel/plugin-syntax-top-level-await": "^7.10.4", + "@babel/plugin-transform-arrow-functions": "^7.10.4", + "@babel/plugin-transform-async-to-generator": "^7.10.4", + "@babel/plugin-transform-block-scoped-functions": "^7.10.4", + "@babel/plugin-transform-block-scoping": "^7.10.4", + "@babel/plugin-transform-classes": "^7.10.4", + "@babel/plugin-transform-computed-properties": "^7.10.4", + "@babel/plugin-transform-destructuring": "^7.10.4", + "@babel/plugin-transform-dotall-regex": "^7.10.4", + "@babel/plugin-transform-duplicate-keys": "^7.10.4", + "@babel/plugin-transform-exponentiation-operator": "^7.10.4", + "@babel/plugin-transform-for-of": "^7.10.4", + "@babel/plugin-transform-function-name": "^7.10.4", + "@babel/plugin-transform-literals": "^7.10.4", + "@babel/plugin-transform-member-expression-literals": "^7.10.4", + "@babel/plugin-transform-modules-amd": "^7.10.4", + "@babel/plugin-transform-modules-commonjs": "^7.10.4", + "@babel/plugin-transform-modules-systemjs": "^7.10.4", + "@babel/plugin-transform-modules-umd": "^7.10.4", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.10.4", + "@babel/plugin-transform-new-target": "^7.10.4", + "@babel/plugin-transform-object-super": "^7.10.4", + "@babel/plugin-transform-parameters": "^7.10.4", + "@babel/plugin-transform-property-literals": "^7.10.4", + "@babel/plugin-transform-regenerator": "^7.10.4", + "@babel/plugin-transform-reserved-words": "^7.10.4", + "@babel/plugin-transform-shorthand-properties": "^7.10.4", + "@babel/plugin-transform-spread": "^7.10.4", + "@babel/plugin-transform-sticky-regex": "^7.10.4", + "@babel/plugin-transform-template-literals": "^7.10.4", + "@babel/plugin-transform-typeof-symbol": "^7.10.4", + "@babel/plugin-transform-unicode-escapes": "^7.10.4", + "@babel/plugin-transform-unicode-regex": "^7.10.4", + "@babel/preset-modules": "^0.1.3", + "@babel/types": "^7.10.4", + "browserslist": "^4.12.0", + "core-js-compat": "^3.6.2", + "invariant": "^2.2.2", + "levenary": "^1.1.1", + "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "@babel/preset-modules": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz", + "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/preset-react": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.10.4.tgz", + "integrity": "sha512-BrHp4TgOIy4M19JAfO1LhycVXOPWdDbTRep7eVyatf174Hff+6Uk53sDyajqZPu8W1qXRBiYOfIamek6jA7YVw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-transform-react-display-name": "^7.10.4", + "@babel/plugin-transform-react-jsx": "^7.10.4", + "@babel/plugin-transform-react-jsx-development": "^7.10.4", + "@babel/plugin-transform-react-jsx-self": "^7.10.4", + "@babel/plugin-transform-react-jsx-source": "^7.10.4", + "@babel/plugin-transform-react-pure-annotations": "^7.10.4" + } + }, + "@babel/preset-typescript": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.10.4.tgz", + "integrity": "sha512-SdYnvGPv+bLlwkF2VkJnaX/ni1sMNetcGI1+nThF1gyv6Ph8Qucc4ZZAjM5yZcE/AKRXIOTZz7eSRDWOEjPyRQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-transform-typescript": "^7.10.4" + } + }, + "@babel/runtime": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.4.tgz", + "integrity": "sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.4.tgz", + "integrity": "sha512-aSy7p5THgSYm4YyxNGz6jZpXf+Ok40QF3aA2LyIONkDHpAcJzDUqlCKXv6peqYUs2gmic849C/t2HKw2a2K20Q==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.4", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.4.tgz", + "integrity": "sha512-UTCFOxC3FsFHb7lkRMVvgLzaRVamXuAs2Tz4wajva4WxtVY82eZeaUBtC2Zt95FU9TiznuC0Zk35tsim8jeVpg==", + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "@csstools/convert-colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", + "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==" + }, + "@docusaurus/core": { + "version": "2.0.0-alpha.58", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-2.0.0-alpha.58.tgz", + "integrity": "sha512-7VW7IQKTxTIeCXxzraLdTqRtYSmEG2ZJDt07z26NjK+17XyNQc0IoJs7M3xhjzaeP0s/CpYe5Yl+pgp6TIXqoA==", + "requires": { + "@babel/core": "^7.9.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.9.0", + "@babel/preset-env": "^7.9.0", + "@babel/preset-react": "^7.9.4", + "@babel/preset-typescript": "^7.9.0", + "@babel/runtime": "^7.9.2", + "@docusaurus/utils": "^2.0.0-alpha.58", + "@endiliey/static-site-generator-webpack-plugin": "^4.0.0", + "@svgr/webpack": "^5.4.0", + "babel-loader": "^8.1.0", + "babel-plugin-dynamic-import-node": "^2.3.0", + "cache-loader": "^4.1.0", + "chalk": "^3.0.0", + "chokidar": "^3.3.0", + "commander": "^4.0.1", + "copy-webpack-plugin": "^5.0.5", + "core-js": "^2.6.5", + "css-loader": "^3.4.2", + "del": "^5.1.0", + "eta": "^1.1.1", + "express": "^4.17.1", + "fs-extra": "^8.1.0", + "globby": "^10.0.1", + "html-minifier-terser": "^5.0.5", + "html-tags": "^3.1.0", + "html-webpack-plugin": "^4.0.4", + "import-fresh": "^3.2.1", + "lodash.has": "^4.5.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "mini-css-extract-plugin": "^0.8.0", + "nprogress": "^0.2.0", + "null-loader": "^3.0.0", + "optimize-css-assets-webpack-plugin": "^5.0.3", + "pnp-webpack-plugin": "^1.6.4", + "portfinder": "^1.0.25", + "postcss-loader": "^3.0.0", + "postcss-preset-env": "^6.7.0", + "react-dev-utils": "^10.2.1", + "react-helmet": "^6.0.0-beta", + "react-loadable": "^5.5.0", + "react-loadable-ssr-addon": "^0.2.3", + "react-router": "^5.1.2", + "react-router-config": "^5.1.1", + "react-router-dom": "^5.1.2", + "semver": "^6.3.0", + "shelljs": "^0.8.4", + "std-env": "^2.2.1", + "terser-webpack-plugin": "^2.3.5", + "wait-file": "^1.0.5", + "webpack": "^4.41.2", + "webpack-bundle-analyzer": "^3.6.1", + "webpack-dev-server": "^3.11.0", + "webpack-merge": "^4.2.2", + "webpackbar": "^4.0.0" + } + }, + "@docusaurus/mdx-loader": { + "version": "2.0.0-alpha.58", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-alpha.58.tgz", + "integrity": "sha512-g/uqzaoaRkwuPmjOB7/p58vNFLev9yE3FdkVfD19mSHyaMaKAzWGUiJxkaxPFtxCs4mwFWk7tJKSLY+B8MjS2Q==", + "requires": { + "@babel/parser": "^7.9.4", + "@babel/traverse": "^7.9.0", + "@mdx-js/mdx": "^1.5.8", + "@mdx-js/react": "^1.5.8", + "escape-html": "^1.0.3", + "fs-extra": "^8.1.0", + "github-slugger": "^1.3.0", + "gray-matter": "^4.0.2", + "loader-utils": "^1.2.3", + "mdast-util-to-string": "^1.1.0", + "remark-emoji": "^2.1.0", + "stringify-object": "^3.3.0", + "unist-util-visit": "^2.0.2" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + } + } + }, + "@docusaurus/plugin-content-blog": { + "version": "2.0.0-alpha.58", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-alpha.58.tgz", + "integrity": "sha512-2Pn15pwPdnmKLAvNd4bk2XclnGtGYCpfAECypm9tBql6W6MShy0qOHH3JGLY8dQIMySsCkuC3jCl26EwXNgARw==", + "requires": { + "@docusaurus/mdx-loader": "^2.0.0-alpha.58", + "@docusaurus/utils": "^2.0.0-alpha.58", + "feed": "^4.1.0", + "fs-extra": "^8.1.0", + "globby": "^10.0.1", + "loader-utils": "^1.2.3", + "lodash.kebabcase": "^4.1.1", + "reading-time": "^1.2.0", + "remark-admonitions": "^1.2.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + } + } + }, + "@docusaurus/plugin-content-docs": { + "version": "2.0.0-alpha.58", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-alpha.58.tgz", + "integrity": "sha512-+PHqften1daJXBxUZTN5pMIRP7NwGU7reO4QX0iy7Qp1Wqs63KPY/nSeM7dtxKCYtU+yWkLogvFlicARVqRU9A==", + "requires": { + "@docusaurus/mdx-loader": "^2.0.0-alpha.58", + "@docusaurus/utils": "^2.0.0-alpha.58", + "execa": "^3.4.0", + "fs-extra": "^8.1.0", + "globby": "^10.0.1", + "import-fresh": "^3.2.1", + "loader-utils": "^1.2.3", + "lodash.flatmap": "^4.5.0", + "lodash.groupby": "^4.6.0", + "lodash.pick": "^4.4.0", + "lodash.pickby": "^4.6.0", + "remark-admonitions": "^1.2.1", + "shelljs": "^0.8.4" + }, + "dependencies": { + "execa": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz", + "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "p-finally": "^2.0.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "requires": { + "pump": "^3.0.0" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "requires": { + "path-key": "^3.0.0" + } + }, + "p-finally": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", + "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==" + } + } + }, + "@docusaurus/plugin-content-pages": { + "version": "2.0.0-alpha.58", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-alpha.58.tgz", + "integrity": "sha512-4LJn2CB83ZCkM4+0qNQWdm4gA2Og3QzmUfSqYOifTmsVsHuLSAqa5zRkkSSsZ244r+ya4e7nmS7nMd7kfK+v4w==", + "requires": { + "@docusaurus/types": "^2.0.0-alpha.58", + "@docusaurus/utils": "^2.0.0-alpha.58", + "globby": "^10.0.1" + } + }, + "@docusaurus/plugin-debug": { + "version": "2.0.0-alpha.58", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-alpha.58.tgz", + "integrity": "sha512-w7sEUzuavp5N4TxgJus1TLFwRkypvg9+mRYZN+mgV05WF3ft+aDTToWeYNZq/8FoC1IEwkezOpPpaWtG4UWM7Q==", + "requires": { + "@docusaurus/types": "^2.0.0-alpha.58", + "@docusaurus/utils": "^2.0.0-alpha.58" + } + }, + "@docusaurus/plugin-google-analytics": { + "version": "2.0.0-alpha.58", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-alpha.58.tgz", + "integrity": "sha512-R4I8j+XJkOl7fz8+lRnQKr8YP4zrG6dWBNZ66JquUwL6jmaavq1VRVavm7/s5Wr/vYLcpEWjynHViwDdVLegoQ==" + }, + "@docusaurus/plugin-google-gtag": { + "version": "2.0.0-alpha.58", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-alpha.58.tgz", + "integrity": "sha512-t+B6K2/EvRofygDK5edeQAg2l069aU7H3sViG/3USpJSaY9bWNWRKjZk6BEOypC+mCW6g5HQgewQZ/bTkV+aDA==" + }, + "@docusaurus/plugin-sitemap": { + "version": "2.0.0-alpha.58", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-alpha.58.tgz", + "integrity": "sha512-OS3XZG1S/USyZxSZ4e2pputW3sOl/hxvK+EAs51eOi/fYyVgO4BmFpcsoP17a5d1Cnxf8gumOkS6bLRDaG8KyQ==", + "requires": { + "@docusaurus/types": "^2.0.0-alpha.58", + "sitemap": "^3.2.2" + } + }, + "@docusaurus/preset-classic": { + "version": "2.0.0-alpha.58", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-2.0.0-alpha.58.tgz", + "integrity": "sha512-XGXC5NcAkRUKpm4aho6moThPtLarGSouWW1xfYMQlT4dY6RG/FFt8n5viMXzGwOfA/H9B/s0sZpqHDw8U4FUCg==", + "requires": { + "@docusaurus/plugin-content-blog": "^2.0.0-alpha.58", + "@docusaurus/plugin-content-docs": "^2.0.0-alpha.58", + "@docusaurus/plugin-content-pages": "^2.0.0-alpha.58", + "@docusaurus/plugin-debug": "^2.0.0-alpha.58", + "@docusaurus/plugin-google-analytics": "^2.0.0-alpha.58", + "@docusaurus/plugin-google-gtag": "^2.0.0-alpha.58", + "@docusaurus/plugin-sitemap": "^2.0.0-alpha.58", + "@docusaurus/theme-classic": "^2.0.0-alpha.58", + "@docusaurus/theme-search-algolia": "^2.0.0-alpha.58" + } + }, + "@docusaurus/theme-classic": { + "version": "2.0.0-alpha.58", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.0.0-alpha.58.tgz", + "integrity": "sha512-GvpzpsL3jTxm/3sPna7wOJaAauCha+uLZ33x/XOMKrfN02E2BLkaRphRyCliw1vvLXB3rJPlHyvBdKCTOVXaNw==", + "requires": { + "@mdx-js/mdx": "^1.5.8", + "@mdx-js/react": "^1.5.8", + "clsx": "^1.1.1", + "copy-text-to-clipboard": "^2.2.0", + "infima": "0.2.0-alpha.12", + "parse-numeric-range": "^0.0.2", + "prism-react-renderer": "^1.1.0", + "prismjs": "^1.20.0", + "prop-types": "^15.7.2", + "react-router-dom": "^5.1.2", + "react-toggle": "^4.1.1" + } + }, + "@docusaurus/theme-search-algolia": { + "version": "2.0.0-alpha.58", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-alpha.58.tgz", + "integrity": "sha512-Iug5mET733Yx46E04BfJjz4+AvxzalRo8G4ZmOjePTMiKQpE1ee39Ypbwj77c8XxEadOcZC4mJtfxB3L1RqlBA==", + "requires": { + "algoliasearch": "^3.24.5", + "algoliasearch-helper": "^3.1.1", + "clsx": "^1.1.1", + "docsearch.js": "^2.6.3", + "eta": "^1.1.1" + } + }, + "@docusaurus/types": { + "version": "2.0.0-alpha.58", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-2.0.0-alpha.58.tgz", + "integrity": "sha512-OSkxoLwWJMhEHa4s6SXVCjfGwYm21yF6ZggoUo6kz+qqslTgF/JcPCVF9Y1Hf6bJJxUisi+ZHrHKEC6k4pphPA==", + "requires": { + "@types/webpack": "^4.41.0", + "commander": "^4.0.1", + "querystring": "0.2.0" + } + }, + "@docusaurus/utils": { + "version": "2.0.0-alpha.58", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.0.0-alpha.58.tgz", + "integrity": "sha512-hBhdQCyVT15mH7RE5yuDBuhwbUnM4beKq2JLvRuZS4FoNu7T2S4OGusUAtTnNZEruoUv2QTkt+GrsRDKYi2fCA==", + "requires": { + "escape-string-regexp": "^2.0.0", + "fs-extra": "^8.1.0", + "gray-matter": "^4.0.2", + "lodash.camelcase": "^4.3.0", + "lodash.kebabcase": "^4.1.1" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" + } + } + }, + "@endiliey/static-site-generator-webpack-plugin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@endiliey/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.0.tgz", + "integrity": "sha512-3MBqYCs30qk1OBRC697NqhGouYbs71D1B8hrk/AFJC6GwF2QaJOQZtA1JYAaGSe650sZ8r5ppRTtCRXepDWlng==", + "requires": { + "bluebird": "^3.7.1", + "cheerio": "^0.22.0", + "eval": "^0.1.4", + "url": "^0.11.0", + "webpack-sources": "^1.4.3" + } + }, + "@hapi/address": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", + "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==" + }, + "@hapi/bourne": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-1.3.2.tgz", + "integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==" + }, + "@hapi/hoek": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", + "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==" + }, + "@hapi/joi": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.1.1.tgz", + "integrity": "sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ==", + "requires": { + "@hapi/address": "2.x.x", + "@hapi/bourne": "1.x.x", + "@hapi/hoek": "8.x.x", + "@hapi/topo": "3.x.x" + } + }, + "@hapi/topo": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", + "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==", + "requires": { + "@hapi/hoek": "^8.3.0" + } + }, + "@mdx-js/mdx": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.6.6.tgz", + "integrity": "sha512-Q1j/RtjNbRZRC/ciaOqQLplsJ9lb0jJhDSvkusmzCsCX+NZH7YTUvccWf7l6zKW1CAiofJfqZdZtXkeJUDZiMw==", + "requires": { + "@babel/core": "7.9.6", + "@babel/plugin-syntax-jsx": "7.8.3", + "@babel/plugin-syntax-object-rest-spread": "7.8.3", + "@mdx-js/util": "^1.6.6", + "babel-plugin-apply-mdx-type-prop": "^1.6.6", + "babel-plugin-extract-import-names": "^1.6.6", + "camelcase-css": "2.0.1", + "detab": "2.0.3", + "hast-util-raw": "5.0.2", + "lodash.uniq": "4.5.0", + "mdast-util-to-hast": "9.1.0", + "remark-footnotes": "1.0.0", + "remark-mdx": "^1.6.6", + "remark-parse": "8.0.2", + "remark-squeeze-paragraphs": "4.0.0", + "style-to-object": "0.3.0", + "unified": "9.0.0", + "unist-builder": "2.0.3", + "unist-util-visit": "2.0.2" + }, + "dependencies": { + "@babel/core": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.6.tgz", + "integrity": "sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.6", + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helpers": "^7.9.6", + "@babel/parser": "^7.9.6", + "@babel/template": "^7.8.6", + "@babel/traverse": "^7.9.6", + "@babel/types": "^7.9.6", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz", + "integrity": "sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "unist-util-is": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz", + "integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==" + }, + "unist-util-visit": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.2.tgz", + "integrity": "sha512-HoHNhGnKj6y+Sq+7ASo2zpVdfdRifhTgX2KTU3B/sO/TTlZchp7E3S4vjRzDJ7L60KmrCPsQkVK3lEF3cz36XQ==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + } + } + } + }, + "@mdx-js/react": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.6.6.tgz", + "integrity": "sha512-zOOdNreHUNSFQ0dg3wYYg9sOGg2csf7Sk8JGBigeBq+4Xk4LO0QdycGAmgKNfeme+SyBV5LBIPjt1NNsScyWEQ==" + }, + "@mdx-js/util": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@mdx-js/util/-/util-1.6.6.tgz", + "integrity": "sha512-PKTHVgMHnK5p+kcMWWNnZuoR7O19VmHiOujmVcyN50hya7qIdDb5vvsYC+dwLxApEXiABhLozq0dlIwFeS3yjg==" + }, + "@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "requires": { + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "requires": { + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==" + }, + "@nodelib/fs.walk": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "requires": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "^1.6.0" + } + }, + "@svgr/babel-plugin-add-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==" + }, + "@svgr/babel-plugin-remove-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==" + }, + "@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", + "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==" + }, + "@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", + "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==" + }, + "@svgr/babel-plugin-svg-dynamic-title": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", + "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==" + }, + "@svgr/babel-plugin-svg-em-dimensions": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", + "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==" + }, + "@svgr/babel-plugin-transform-react-native-svg": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", + "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==" + }, + "@svgr/babel-plugin-transform-svg-component": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.4.0.tgz", + "integrity": "sha512-zLl4Fl3NvKxxjWNkqEcpdSOpQ3LGVH2BNFQ6vjaK6sFo2IrSznrhURIPI0HAphKiiIwNYjAfE0TNoQDSZv0U9A==" + }, + "@svgr/babel-preset": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.4.0.tgz", + "integrity": "sha512-Gyx7cCxua04DBtyILTYdQxeO/pwfTBev6+eXTbVbxe4HTGhOUW6yo7PSbG2p6eJMl44j6XSequ0ZDP7bl0nu9A==", + "requires": { + "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", + "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", + "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", + "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", + "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", + "@svgr/babel-plugin-transform-svg-component": "^5.4.0" + } + }, + "@svgr/core": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.4.0.tgz", + "integrity": "sha512-hWGm1DCCvd4IEn7VgDUHYiC597lUYhFau2lwJBYpQWDirYLkX4OsXu9IslPgJ9UpP7wsw3n2Ffv9sW7SXJVfqQ==", + "requires": { + "@svgr/plugin-jsx": "^5.4.0", + "camelcase": "^6.0.0", + "cosmiconfig": "^6.0.0" + } + }, + "@svgr/hast-util-to-babel-ast": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.4.0.tgz", + "integrity": "sha512-+U0TZZpPsP2V1WvVhqAOSTk+N+CjYHdZx+x9UBa1eeeZDXwH8pt0CrQf2+SvRl/h2CAPRFkm+Ey96+jKP8Bsgg==", + "requires": { + "@babel/types": "^7.9.5" + } + }, + "@svgr/plugin-jsx": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.4.0.tgz", + "integrity": "sha512-SGzO4JZQ2HvGRKDzRga9YFSqOqaNrgLlQVaGvpZ2Iht2gwRp/tq+18Pvv9kS9ZqOMYgyix2LLxZMY1LOe9NPqw==", + "requires": { + "@babel/core": "^7.7.5", + "@svgr/babel-preset": "^5.4.0", + "@svgr/hast-util-to-babel-ast": "^5.4.0", + "svg-parser": "^2.0.2" + } + }, + "@svgr/plugin-svgo": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.4.0.tgz", + "integrity": "sha512-3Cgv3aYi1l6SHyzArV9C36yo4kgwVdF3zPQUC6/aCDUeXAofDYwE5kk3e3oT5ZO2a0N3lB+lLGvipBG6lnG8EA==", + "requires": { + "cosmiconfig": "^6.0.0", + "merge-deep": "^3.0.2", + "svgo": "^1.2.2" + } + }, + "@svgr/webpack": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.4.0.tgz", + "integrity": "sha512-LjepnS/BSAvelnOnnzr6Gg0GcpLmnZ9ThGFK5WJtm1xOqdBE/1IACZU7MMdVzjyUkfFqGz87eRE4hFaSLiUwYg==", + "requires": { + "@babel/core": "^7.9.0", + "@babel/plugin-transform-react-constant-elements": "^7.9.0", + "@babel/preset-env": "^7.9.5", + "@babel/preset-react": "^7.9.4", + "@svgr/core": "^5.4.0", + "@svgr/plugin-jsx": "^5.4.0", + "@svgr/plugin-svgo": "^5.4.0", + "loader-utils": "^2.0.0" + } + }, + "@types/anymatch": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", + "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==" + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + }, + "@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/html-minifier-terser": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz", + "integrity": "sha512-iYCgjm1dGPRuo12+BStjd1HiVQqhlRhWDOQigNxn023HcjnhsiFz9pc6CzJj4HwDCSQca9bxTL4PxJDbkdm3PA==" + }, + "@types/json-schema": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", + "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==" + }, + "@types/mdast": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.3.tgz", + "integrity": "sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw==", + "requires": { + "@types/unist": "*" + } + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" + }, + "@types/node": { + "version": "14.0.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.22.tgz", + "integrity": "sha512-emeGcJvdiZ4Z3ohbmw93E/64jRzUHAItSHt8nF7M4TGgQTiWqFVGB8KNpLGFmUHmHLvjvBgFwVlqNcq+VuGv9g==" + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + }, + "@types/q": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", + "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" + }, + "@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==" + }, + "@types/tapable": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.6.tgz", + "integrity": "sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==" + }, + "@types/uglify-js": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.9.3.tgz", + "integrity": "sha512-KswB5C7Kwduwjj04Ykz+AjvPcfgv/37Za24O2EDzYNbwyzOo8+ydtvzUfZ5UMguiVu29Gx44l1A6VsPPcmYu9w==", + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "@types/unist": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", + "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==" + }, + "@types/webpack": { + "version": "4.41.21", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.21.tgz", + "integrity": "sha512-2j9WVnNrr/8PLAB5csW44xzQSJwS26aOnICsP3pSGCEdsu6KYtfQ6QJsVUKHWRnm1bL7HziJsfh5fHqth87yKA==", + "requires": { + "@types/anymatch": "*", + "@types/node": "*", + "@types/tapable": "*", + "@types/uglify-js": "*", + "@types/webpack-sources": "*", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "@types/webpack-sources": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-1.4.0.tgz", + "integrity": "sha512-c88dKrpSle9BtTqR6ifdaxu1Lvjsl3C5OsfvuUbUwdXymshv1TkufUAXBajCCUM/f/TmnkZC/Esb03MinzSiXQ==", + "requires": { + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + } + } + }, + "@webassemblyjs/ast": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "requires": { + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", + "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==" + }, + "@webassemblyjs/helper-api-error": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==" + }, + "@webassemblyjs/helper-buffer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", + "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==" + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", + "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", + "requires": { + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", + "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==" + }, + "@webassemblyjs/helper-module-context": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", + "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", + "requires": { + "@webassemblyjs/ast": "1.9.0" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==" + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", + "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", + "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", + "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", + "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==" + }, + "@webassemblyjs/wasm-edit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", + "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/helper-wasm-section": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-opt": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", + "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", + "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", + "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", + "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/floating-point-hex-parser": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-code-frame": "1.9.0", + "@webassemblyjs/helper-fsm": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", + "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==" + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" + }, + "address": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.1.2.tgz", + "integrity": "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==" + }, + "agentkeepalive": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-2.2.0.tgz", + "integrity": "sha1-xdG9SxKQCPEWPyNvhuX66iAm4u8=" + }, + "aggregate-error": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", + "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==" + }, + "ajv-keywords": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.1.tgz", + "integrity": "sha512-KWcq3xN8fDjSB+IMoh2VaXVhRI0BBGxoYp3rx7Pkb6z0cFjYR9Q9l4yZqqals0/zsioCmocC5H6UvsGD4MoIBA==" + }, + "algoliasearch": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-3.35.1.tgz", + "integrity": "sha512-K4yKVhaHkXfJ/xcUnil04xiSrB8B8yHZoFEhWNpXg23eiCnqvTZw1tn/SqvdsANlYHLJlKl0qi3I/Q2Sqo7LwQ==", + "requires": { + "agentkeepalive": "^2.2.0", + "debug": "^2.6.9", + "envify": "^4.0.0", + "es6-promise": "^4.1.0", + "events": "^1.1.0", + "foreach": "^2.0.5", + "global": "^4.3.2", + "inherits": "^2.0.1", + "isarray": "^2.0.1", + "load-script": "^1.0.0", + "object-keys": "^1.0.11", + "querystring-es3": "^0.2.1", + "reduce": "^1.0.1", + "semver": "^5.1.0", + "tunnel-agent": "^0.6.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "algoliasearch-helper": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.1.2.tgz", + "integrity": "sha512-HfCVvmKH6+5OU9/SaHLdhvr39DBObA02z62RsfPhFDftzgQM6pJB2JoPyGpIteHW4RAYh8bPLiB8l4hajuy6fA==", + "requires": { + "events": "^1.1.1" + }, + "dependencies": { + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + } + } + }, + "alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=" + }, + "ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==" + }, + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "requires": { + "type-fest": "^0.11.0" + } + }, + "ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=" + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "requires": { + "lodash": "^4.17.14" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + }, + "autocomplete.js": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/autocomplete.js/-/autocomplete.js-0.36.0.tgz", + "integrity": "sha512-jEwUXnVMeCHHutUt10i/8ZiRaCb0Wo+ZyKxeGsYwBDtw6EJHqEeDrq4UwZRD8YBSvp3g6klP678il2eeiVXN2Q==", + "requires": { + "immediate": "^3.2.3" + } + }, + "autoprefixer": { + "version": "9.8.5", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.5.tgz", + "integrity": "sha512-C2p5KkumJlsTHoNv9w31NrBRgXhf6eCMteJuHZi2xhkgC+5Vm40MEtCKPhc0qdgAOhox0YPy1SQHTAky05UoKg==", + "requires": { + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001097", + "colorette": "^1.2.0", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", + "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==" + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "babel-loader": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz", + "integrity": "sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==", + "requires": { + "find-cache-dir": "^2.1.0", + "loader-utils": "^1.4.0", + "mkdirp": "^0.5.3", + "pify": "^4.0.1", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + } + } + }, + "babel-plugin-apply-mdx-type-prop": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.6.6.tgz", + "integrity": "sha512-rUzVvkQa8/9M63OZT6qQQ1bS8P0ozhXp9e5uJ3RwRJF5Me7s4nZK5SYhyNHYc0BkAflWnCOGMP3oPQUfuyB8tg==", + "requires": { + "@babel/helper-plugin-utils": "7.8.3", + "@mdx-js/util": "^1.6.6" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + } + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "requires": { + "object.assign": "^4.1.0" + } + }, + "babel-plugin-extract-import-names": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.6.6.tgz", + "integrity": "sha512-UtMuiQJnhVPAGE2+pDe7Nc9NVEmDdqGTN74BtRALgH+7oag88RpxFLOSiA+u5mFkFg741wW9Ut5KiyJpksEj/g==", + "requires": { + "@babel/helper-plugin-utils": "7.8.3" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + } + } + }, + "bail": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bfj": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz", + "integrity": "sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw==", + "requires": { + "bluebird": "^3.5.5", + "check-types": "^8.0.3", + "hoopy": "^0.1.4", + "tryer": "^1.0.1" + } + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==" + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "bn.js": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.2.tgz", + "integrity": "sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==" + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "requires": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + }, + "dependencies": { + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" + } + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "browserify-sign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.0.tgz", + "integrity": "sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA==", + "requires": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.2", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.13.0.tgz", + "integrity": "sha512-MINatJ5ZNrLnQ6blGvePd/QOz9Xtu+Ne+x29iQSCHfkU5BugKVJwZKn/iiL8UbpIpa3JhviKjz+XxMo0m2caFQ==", + "requires": { + "caniuse-lite": "^1.0.30001093", + "electron-to-chromium": "^1.3.488", + "escalade": "^3.0.1", + "node-releases": "^1.1.58" + } + }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==" + }, + "buffer-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-json/-/buffer-json-2.0.0.tgz", + "integrity": "sha512-+jjPFVqyfF1esi9fvfUs3NqM0pH1ziZ36VP4hmA/y/Ssfo/5w5xHKfTw9BwQjoJ1w/oVtpLomqwUHKdefGyuHw==" + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "cache-loader": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cache-loader/-/cache-loader-4.1.0.tgz", + "integrity": "sha512-ftOayxve0PwKzBF/GLsZNC9fJBXl8lkZE3TOsjkboHfVHVkL39iUEs1FO07A33mizmci5Dudt38UZrrYXDtbhw==", + "requires": { + "buffer-json": "^2.0.0", + "find-cache-dir": "^3.0.0", + "loader-utils": "^1.2.3", + "mkdirp": "^0.5.1", + "neo-async": "^2.6.1", + "schema-utils": "^2.0.0" + }, + "dependencies": { + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "requires": { + "find-up": "^4.0.0" + } + } + } + }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" + }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "requires": { + "callsites": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" + } + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, + "camel-case": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.1.tgz", + "integrity": "sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q==", + "requires": { + "pascal-case": "^3.1.1", + "tslib": "^1.10.0" + } + }, + "camelcase": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", + "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==" + }, + "camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" + }, + "caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "requires": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001099", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001099.tgz", + "integrity": "sha512-sdS9A+sQTk7wKoeuZBN/YMAHVztUfVnjDi4/UV3sDE8xoh7YR12hKW+pIdB3oqKGwr9XaFL2ovfzt9w8eUI5CA==" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "ccount": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.5.tgz", + "integrity": "sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw==" + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==" + }, + "character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==" + }, + "character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==" + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "check-types": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz", + "integrity": "sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==" + }, + "cheerio": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", + "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash.assignin": "^4.0.9", + "lodash.bind": "^4.1.4", + "lodash.defaults": "^4.0.1", + "lodash.filter": "^4.4.0", + "lodash.flatten": "^4.2.0", + "lodash.foreach": "^4.3.0", + "lodash.map": "^4.4.0", + "lodash.merge": "^4.4.0", + "lodash.pick": "^4.2.1", + "lodash.reduce": "^4.4.0", + "lodash.reject": "^4.4.0", + "lodash.some": "^4.4.0" + } + }, + "chokidar": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", + "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "classnames": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", + "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" + }, + "clean-css": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", + "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==" + }, + "clipboard": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz", + "integrity": "sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==", + "optional": true, + "requires": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "clone-deep": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", + "integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=", + "requires": { + "for-own": "^0.1.3", + "is-plain-object": "^2.0.1", + "kind-of": "^3.0.2", + "lazy-cache": "^1.0.3", + "shallow-clone": "^0.1.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "clsx": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", + "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==" + }, + "coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "collapse-white-space": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", + "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==" + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", + "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colorette": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", + "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==" + }, + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==" + }, + "consola": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/consola/-/consola-2.14.0.tgz", + "integrity": "sha512-A2j1x4u8d6SIVikhZROfpFJxQZie+cZOfQMyI/tu2+hWXe8iAv7R6FW6s6x04/7zBCst94lPddztot/d6GJiuQ==" + }, + "console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==" + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + }, + "copy-text-to-clipboard": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-2.2.0.tgz", + "integrity": "sha512-WRvoIdnTs1rgPMkgA2pUOa/M4Enh2uzCwdKsOMYNAJiz/4ZvEJgmbF4OmninPmlFdAWisfeh0tH+Cpf7ni3RqQ==" + }, + "copy-webpack-plugin": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz", + "integrity": "sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg==", + "requires": { + "cacache": "^12.0.3", + "find-cache-dir": "^2.1.0", + "glob-parent": "^3.1.0", + "globby": "^7.1.1", + "is-glob": "^4.0.1", + "loader-utils": "^1.2.3", + "minimatch": "^3.0.4", + "normalize-path": "^3.0.0", + "p-limit": "^2.2.1", + "schema-utils": "^1.0.0", + "serialize-javascript": "^2.1.2", + "webpack-log": "^2.0.0" + }, + "dependencies": { + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "globby": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", + "requires": { + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + } + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" + }, + "core-js-compat": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", + "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==", + "requires": { + "browserslist": "^4.8.5", + "semver": "7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" + } + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", + "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "css-blank-pseudo": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", + "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==", + "requires": { + "postcss": "^7.0.5" + } + }, + "css-color-names": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=" + }, + "css-declaration-sorter": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", + "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", + "requires": { + "postcss": "^7.0.1", + "timsort": "^0.3.0" + } + }, + "css-has-pseudo": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz", + "integrity": "sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ==", + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^5.0.0-rc.4" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "css-loader": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz", + "integrity": "sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==", + "requires": { + "camelcase": "^5.3.1", + "cssesc": "^3.0.0", + "icss-utils": "^4.1.1", + "loader-utils": "^1.2.3", + "normalize-path": "^3.0.0", + "postcss": "^7.0.32", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^3.0.2", + "postcss-modules-scope": "^2.2.0", + "postcss-modules-values": "^3.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^2.7.0", + "semver": "^6.3.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + } + } + }, + "css-prefers-color-scheme": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz", + "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==", + "requires": { + "postcss": "^7.0.5" + } + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" + }, + "css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "requires": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" + }, + "cssdb": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", + "integrity": "sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ==" + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" + }, + "cssnano": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", + "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", + "requires": { + "cosmiconfig": "^5.0.0", + "cssnano-preset-default": "^4.0.7", + "is-resolvable": "^1.0.0", + "postcss": "^7.0.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + } + } + }, + "cssnano-preset-default": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", + "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", + "requires": { + "css-declaration-sorter": "^4.0.1", + "cssnano-util-raw-cache": "^4.0.1", + "postcss": "^7.0.0", + "postcss-calc": "^7.0.1", + "postcss-colormin": "^4.0.3", + "postcss-convert-values": "^4.0.1", + "postcss-discard-comments": "^4.0.2", + "postcss-discard-duplicates": "^4.0.2", + "postcss-discard-empty": "^4.0.1", + "postcss-discard-overridden": "^4.0.1", + "postcss-merge-longhand": "^4.0.11", + "postcss-merge-rules": "^4.0.3", + "postcss-minify-font-values": "^4.0.2", + "postcss-minify-gradients": "^4.0.2", + "postcss-minify-params": "^4.0.2", + "postcss-minify-selectors": "^4.0.2", + "postcss-normalize-charset": "^4.0.1", + "postcss-normalize-display-values": "^4.0.2", + "postcss-normalize-positions": "^4.0.2", + "postcss-normalize-repeat-style": "^4.0.2", + "postcss-normalize-string": "^4.0.2", + "postcss-normalize-timing-functions": "^4.0.2", + "postcss-normalize-unicode": "^4.0.1", + "postcss-normalize-url": "^4.0.1", + "postcss-normalize-whitespace": "^4.0.2", + "postcss-ordered-values": "^4.1.2", + "postcss-reduce-initial": "^4.0.3", + "postcss-reduce-transforms": "^4.0.2", + "postcss-svgo": "^4.0.2", + "postcss-unique-selectors": "^4.0.1" + } + }, + "cssnano-util-get-arguments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", + "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=" + }, + "cssnano-util-get-match": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", + "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=" + }, + "cssnano-util-raw-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", + "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", + "requires": { + "postcss": "^7.0.0" + } + }, + "cssnano-util-same-parent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", + "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==" + }, + "csso": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.3.tgz", + "integrity": "sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ==", + "requires": { + "css-tree": "1.0.0-alpha.39" + }, + "dependencies": { + "css-tree": { + "version": "1.0.0-alpha.39", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.39.tgz", + "integrity": "sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==", + "requires": { + "mdn-data": "2.0.6", + "source-map": "^0.6.1" + } + }, + "mdn-data": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz", + "integrity": "sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "cyclist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", + "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } + }, + "default-gateway": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", + "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", + "requires": { + "execa": "^1.0.0", + "ip-regex": "^2.1.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "del": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/del/-/del-5.1.0.tgz", + "integrity": "sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==", + "requires": { + "globby": "^10.0.1", + "graceful-fs": "^4.2.2", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.1", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "slash": "^3.0.0" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", + "optional": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "detab": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detab/-/detab-2.0.3.tgz", + "integrity": "sha512-Up8P0clUVwq0FnFjDclzZsy9PadzRn5FFxrr47tQQvMHqyiFYVbpH8oXDzWtF0Q7pYy3l+RPmtBl+BsFF6wH0A==", + "requires": { + "repeat-string": "^1.5.4" + } + }, + "detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==" + }, + "detect-port-alt": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", + "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", + "requires": { + "address": "^1.0.1", + "debug": "^2.6.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "requires": { + "path-type": "^3.0.0" + }, + "dependencies": { + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + } + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=" + }, + "dns-packet": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", + "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "requires": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "requires": { + "buffer-indexof": "^1.0.0" + } + }, + "docsearch.js": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/docsearch.js/-/docsearch.js-2.6.3.tgz", + "integrity": "sha512-GN+MBozuyz664ycpZY0ecdQE0ND/LSgJKhTLA0/v3arIS3S1Rpf2OJz6A35ReMsm91V5apcmzr5/kM84cvUg+A==", + "requires": { + "algoliasearch": "^3.24.5", + "autocomplete.js": "0.36.0", + "hogan.js": "^3.0.2", + "request": "^2.87.0", + "stack-utils": "^1.0.1", + "to-factory": "^1.0.0", + "zepto": "^1.2.0" + } + }, + "dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "requires": { + "utila": "~0.4" + } + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==" + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "dot-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.3.tgz", + "integrity": "sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA==", + "requires": { + "no-case": "^3.0.3", + "tslib": "^1.10.0" + } + }, + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "requires": { + "is-obj": "^2.0.0" + } + }, + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "ejs": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", + "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==" + }, + "electron-to-chromium": { + "version": "1.3.496", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.496.tgz", + "integrity": "sha512-TXY4mwoyowwi4Lsrq9vcTUYBThyc1b2hXaTZI13p8/FRhY2CTaq5lK+DVjhYkKiTLsKt569Xes+0J5JsVXFurQ==" + }, + "elliptic": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" + }, + "emoticon": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-3.2.0.tgz", + "integrity": "sha512-SNujglcLTTg+lDAcApPNgEdudaqQFiAbJCqzjNxJkvN9vAwCGi0uu8IUVvx+f16h+V44KCY6Y2yboroc9pilHg==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "enhanced-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.2.0.tgz", + "integrity": "sha512-S7eiFb/erugyd1rLb6mQ3Vuq+EXHv5cpCkNqqIkYkBgN2QdFnyCZzFBleqwGEx4lgNGYij81BWnCrFNK7vxvjQ==", + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "envify": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/envify/-/envify-4.1.0.tgz", + "integrity": "sha512-IKRVVoAYr4pIx4yIWNsz9mOsboxlNXiu7TNBnem/K/uTHdkyzXWDzHCK7UTolqBbgaBz0tQHsD3YNls0uIIjiw==", + "requires": { + "esprima": "^4.0.0", + "through": "~2.3.4" + } + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "escalade": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.1.tgz", + "integrity": "sha512-DR6NO3h9niOT+MZs7bjxlj2a1k+POu5RN8CLTPX2+i78bRi9eLe7+0zXgUHMnGXWybYcL61E9hGhPKqedy8tQA==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "eta": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/eta/-/eta-1.2.2.tgz", + "integrity": "sha512-8H+zm3HfY2ELz5P4zzR3uJ1LQLnhTAe5gb0vR9ziKZGCLhQrRtqwIyzsOkf7pdBnH7gFPLRAaKZdv2nj9vu9cw==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "eval": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/eval/-/eval-0.1.4.tgz", + "integrity": "sha512-npGsebJejyjMRnLdFu+T/97dnigqIU0Ov3IGrZ8ygd1v7RL1vGkEKtvyWZobqUH1AQgKlg0Yqqe2BtMA9/QZLw==", + "requires": { + "require-like": ">= 0.1.1" + } + }, + "eventemitter3": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", + "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==" + }, + "events": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", + "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==" + }, + "eventsource": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", + "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", + "requires": { + "original": "^1.0.0" + } + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-glob": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", + "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fastq": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", + "integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==", + "requires": { + "reusify": "^1.0.4" + } + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "feed": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.1.tgz", + "integrity": "sha512-l28KKcK1J/u3iq5dRDmmoB2p7dtBfACC2NqJh4dI2kFptxH0asfjmOfcxqh5Sv8suAlVa73gZJ4REY5RrafVvg==", + "requires": { + "xml-js": "^1.6.11" + } + }, + "figgy-pudding": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", + "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==" + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "filesize": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.0.1.tgz", + "integrity": "sha512-u4AYWPgbI5GBhs6id1KdImZWn5yfyFrrQ8OWZdN7ZMfA8Bf4HcO0BGo9bmUIEV8yrp8I1xVfJ/dn90GtFNNJcg==" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "flatten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz", + "integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==" + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "follow-redirects": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.12.1.tgz", + "integrity": "sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg==" + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "requires": { + "for-in": "^1.0.1" + } + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "fork-ts-checker-webpack-plugin": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-3.1.1.tgz", + "integrity": "sha512-DuVkPNrM12jR41KM2e+N+styka0EgLkTnXmNcXdgOM37vtGeY+oCBK/Jx0hzSeEU6memFCtWb4htrHPMDfwwUQ==", + "requires": { + "babel-code-frame": "^6.22.0", + "chalk": "^2.4.1", + "chokidar": "^3.3.0", + "micromatch": "^3.1.10", + "minimatch": "^3.0.4", + "semver": "^5.6.0", + "tapable": "^1.0.0", + "worker-rpc": "^0.1.0" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "gensync": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "github-slugger": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.3.0.tgz", + "integrity": "sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q==", + "requires": { + "emoji-regex": ">=6.0.0 <=6.1.1" + }, + "dependencies": { + "emoji-regex": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.1.tgz", + "integrity": "sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4=" + } + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=" + }, + "global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "requires": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "requires": { + "global-prefix": "^3.0.0" + } + }, + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "requires": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "dependencies": { + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "globby": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", + "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + }, + "dependencies": { + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "requires": { + "path-type": "^4.0.0" + } + }, + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + } + } + }, + "good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", + "optional": true, + "requires": { + "delegate": "^3.1.2" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "gray-matter": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.2.tgz", + "integrity": "sha512-7hB/+LxrOjq/dd8APlK0r24uL/67w7SkYnfwhNFwg/VDIGWGmduTDYf3WNstLW2fbbmRwrDGCVSJ2isuf2+4Hw==", + "requires": { + "js-yaml": "^3.11.0", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + } + }, + "gzip-size": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", + "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", + "requires": { + "duplexer": "^0.1.1", + "pify": "^4.0.1" + } + }, + "handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hast-to-hyperscript": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-7.0.4.tgz", + "integrity": "sha512-vmwriQ2H0RPS9ho4Kkbf3n3lY436QKLq6VaGA1pzBh36hBi3tm1DO9bR+kaJIbpT10UqaANDkMjxvjVfr+cnOA==", + "requires": { + "comma-separated-tokens": "^1.0.0", + "property-information": "^5.3.0", + "space-separated-tokens": "^1.0.0", + "style-to-object": "^0.2.1", + "unist-util-is": "^3.0.0", + "web-namespaces": "^1.1.2" + }, + "dependencies": { + "style-to-object": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.2.3.tgz", + "integrity": "sha512-1d/k4EY2N7jVLOqf2j04dTc37TPOv/hHxZmvpg8Pdh8UYydxeu/C1W1U4vD8alzf5V2Gt7rLsmkr4dxAlDm9ng==", + "requires": { + "inline-style-parser": "0.1.1" + } + } + } + }, + "hast-util-from-parse5": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-5.0.3.tgz", + "integrity": "sha512-gOc8UB99F6eWVWFtM9jUikjN7QkWxB3nY0df5Z0Zq1/Nkwl5V4hAAsl0tmwlgWl/1shlTF8DnNYLO8X6wRV9pA==", + "requires": { + "ccount": "^1.0.3", + "hastscript": "^5.0.0", + "property-information": "^5.0.0", + "web-namespaces": "^1.1.2", + "xtend": "^4.0.1" + } + }, + "hast-util-parse-selector": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.4.tgz", + "integrity": "sha512-gW3sxfynIvZApL4L07wryYF4+C9VvH3AUi7LAnVXV4MneGEgwOByXvFo18BgmTWnm7oHAe874jKbIB1YhHSIzA==" + }, + "hast-util-raw": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-5.0.2.tgz", + "integrity": "sha512-3ReYQcIHmzSgMq8UrDZHFL0oGlbuVGdLKs8s/Fe8BfHFAyZDrdv1fy/AGn+Fim8ZuvAHcJ61NQhVMtyfHviT/g==", + "requires": { + "hast-util-from-parse5": "^5.0.0", + "hast-util-to-parse5": "^5.0.0", + "html-void-elements": "^1.0.0", + "parse5": "^5.0.0", + "unist-util-position": "^3.0.0", + "web-namespaces": "^1.0.0", + "xtend": "^4.0.0", + "zwitch": "^1.0.0" + } + }, + "hast-util-to-parse5": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-5.1.2.tgz", + "integrity": "sha512-ZgYLJu9lYknMfsBY0rBV4TJn2xiwF1fXFFjbP6EE7S0s5mS8LIKBVWzhA1MeIs1SWW6GnnE4In6c3kPb+CWhog==", + "requires": { + "hast-to-hyperscript": "^7.0.0", + "property-information": "^5.0.0", + "web-namespaces": "^1.0.0", + "xtend": "^4.0.0", + "zwitch": "^1.0.0" + } + }, + "hastscript": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-5.1.2.tgz", + "integrity": "sha512-WlztFuK+Lrvi3EggsqOkQ52rKbxkXL3RwB6t5lwoa8QLMemoWfBuL43eDrwOamJyR7uKQKdmKYaBH1NZBiIRrQ==", + "requires": { + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + }, + "hex-color-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", + "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" + }, + "history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hogan.js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/hogan.js/-/hogan.js-3.0.2.tgz", + "integrity": "sha1-TNnhq9QpQUbnZ55B14mHMrAse/0=", + "requires": { + "mkdirp": "0.3.0", + "nopt": "1.0.10" + }, + "dependencies": { + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=" + } + } + }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + }, + "hoopy": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", + "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==" + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "hsl-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", + "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=" + }, + "hsla-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", + "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=" + }, + "html-comment-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", + "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==" + }, + "html-entities": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.1.tgz", + "integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==" + }, + "html-minifier-terser": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", + "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", + "requires": { + "camel-case": "^4.1.1", + "clean-css": "^4.2.3", + "commander": "^4.1.1", + "he": "^1.2.0", + "param-case": "^3.0.3", + "relateurl": "^0.2.7", + "terser": "^4.6.3" + } + }, + "html-tags": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", + "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==" + }, + "html-void-elements": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz", + "integrity": "sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==" + }, + "html-webpack-plugin": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.3.0.tgz", + "integrity": "sha512-C0fzKN8yQoVLTelcJxZfJCE+aAvQiY2VUf3UuKrR4a9k5UMWYOtpDLsaXwATbcVCnI05hUS7L9ULQHWLZhyi3w==", + "requires": { + "@types/html-minifier-terser": "^5.0.0", + "@types/tapable": "^1.0.5", + "@types/webpack": "^4.41.8", + "html-minifier-terser": "^5.0.1", + "loader-utils": "^1.2.3", + "lodash": "^4.17.15", + "pretty-error": "^2.1.1", + "tapable": "^1.1.3", + "util.promisify": "1.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + } + } + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=" + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } + } + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-middleware": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", + "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "requires": { + "http-proxy": "^1.17.0", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" + }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==" + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "icss-utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", + "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", + "requires": { + "postcss": "^7.0.14" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" + }, + "immediate": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", + "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==" + }, + "immer": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz", + "integrity": "sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg==" + }, + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "requires": { + "import-from": "^2.1.0" + } + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "requires": { + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + } + } + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=" + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" + }, + "infima": { + "version": "0.2.0-alpha.12", + "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.12.tgz", + "integrity": "sha512-in5n36oE2sdiB/1rzuzdmKyuNRMVUO9P+qUidUG8leHeDU+WMQ7oTP7MXSqtAAxduiPb7HHi0/ptQLLUr/ge4w==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + }, + "inquirer": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.4.tgz", + "integrity": "sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==", + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^2.4.2", + "cli-cursor": "^3.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", + "run-async": "^2.2.0", + "rxjs": "^6.5.3", + "string-width": "^4.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "internal-ip": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", + "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", + "requires": { + "default-gateway": "^4.2.0", + "ipaddr.js": "^1.9.0" + } + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-absolute-url": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=" + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==" + }, + "is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "requires": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + } + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==" + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==" + }, + "is-color-stop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", + "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", + "requires": { + "css-color-names": "^0.0.4", + "hex-color-regex": "^1.1.0", + "hsl-regex": "^1.0.0", + "hsla-regex": "^1.0.0", + "rgb-regex": "^1.0.1", + "rgba-regex": "^1.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==" + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" + }, + "is-docker": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz", + "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==" + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==" + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==" + }, + "is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "requires": { + "is-path-inside": "^2.1.0" + }, + "dependencies": { + "is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "requires": { + "path-is-inside": "^1.0.2" + } + } + } + }, + "is-path-inside": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", + "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==" + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", + "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=" + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" + }, + "is-root": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", + "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==" + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-svg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", + "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", + "requires": { + "html-comment-regex": "^1.1.0" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-whitespace-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", + "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==" + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "is-word-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", + "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==" + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jest-worker": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.5.0.tgz", + "integrity": "sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==", + "requires": { + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json3": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", + "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==" + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "requires": { + "minimist": "^1.2.5" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "killable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==" + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, + "last-call-webpack-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz", + "integrity": "sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w==", + "requires": { + "lodash": "^4.17.5", + "webpack-sources": "^1.1.0" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" + }, + "levenary": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", + "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", + "requires": { + "leven": "^3.1.0" + } + }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" + }, + "load-script": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz", + "integrity": "sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ=" + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==" + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" + }, + "lodash.assignin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", + "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=" + }, + "lodash.bind": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", + "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + }, + "lodash.chunk": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz", + "integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw=" + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "lodash.filter": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", + "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=" + }, + "lodash.flatmap": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz", + "integrity": "sha1-74y/QI9uSCaGYzRTBcaswLd4cC4=" + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, + "lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" + }, + "lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E=" + }, + "lodash.has": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", + "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha1-hImxyw0p/4gZXM7KRI/21swpXDY=" + }, + "lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=" + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "lodash.padstart": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.padstart/-/lodash.padstart-4.6.1.tgz", + "integrity": "sha1-0uPuv/DZ05rVD1y9G1KnvOa7YRs=" + }, + "lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" + }, + "lodash.pickby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz", + "integrity": "sha1-feoh2MGNdwOifHBMFdO4SmfjOv8=" + }, + "lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" + }, + "lodash.reject": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", + "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=" + }, + "lodash.some": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", + "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=" + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" + }, + "lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "requires": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "lodash.toarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", + "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=" + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" + }, + "loglevel": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz", + "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lower-case": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz", + "integrity": "sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ==", + "requires": { + "tslib": "^1.10.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "requires": { + "object-visit": "^1.0.0" + } + }, + "markdown-escapes": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", + "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==" + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "mdast-squeeze-paragraphs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz", + "integrity": "sha512-zxdPn69hkQ1rm4J+2Cs2j6wDEv7O17TfXTJ33tl/+JPIoEmtV9t2ZzBM5LPHE8QlHsmVD8t3vPKCyY3oH+H8MQ==", + "requires": { + "unist-util-remove": "^2.0.0" + } + }, + "mdast-util-definitions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-3.0.1.tgz", + "integrity": "sha512-BAv2iUm/e6IK/b2/t+Fx69EL/AGcq/IG2S+HxHjDJGfLJtd6i9SZUS76aC9cig+IEucsqxKTR0ot3m933R3iuA==", + "requires": { + "unist-util-visit": "^2.0.0" + } + }, + "mdast-util-to-hast": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-9.1.0.tgz", + "integrity": "sha512-Akl2Vi9y9cSdr19/Dfu58PVwifPXuFt1IrHe7l+Crme1KvgUT+5z+cHLVcQVGCiNTZZcdqjnuv9vPkGsqWytWA==", + "requires": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.3", + "collapse-white-space": "^1.0.0", + "detab": "^2.0.0", + "mdast-util-definitions": "^3.0.0", + "mdurl": "^1.0.0", + "trim-lines": "^1.0.0", + "unist-builder": "^2.0.0", + "unist-util-generated": "^1.0.0", + "unist-util-position": "^3.0.0", + "unist-util-visit": "^2.0.0" + } + }, + "mdast-util-to-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz", + "integrity": "sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==" + }, + "mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "merge-deep": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.2.tgz", + "integrity": "sha512-T7qC8kg4Zoti1cFd8Cr0M+qaZfOwjlPDEdZIIPPB2JZctjaPM4fX+i7HOId69tAti2fvO6X5ldfYUONDODsrkA==", + "requires": { + "arr-union": "^3.1.0", + "clone-deep": "^0.2.4", + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "microevent.ts": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz", + "integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==" + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "requires": { + "dom-walk": "^0.1.0" + } + }, + "mini-create-react-context": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz", + "integrity": "sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==", + "requires": { + "@babel/runtime": "^7.5.5", + "tiny-warning": "^1.0.3" + } + }, + "mini-css-extract-plugin": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.2.tgz", + "integrity": "sha512-a3Y4of27Wz+mqK3qrcd3VhYz6cU0iW5x3Sgvqzbj+XmlrSizmvu8QQMl5oMYJjgHOC4iyt+w7l4umP+dQeW3bw==", + "requires": { + "loader-utils": "^1.1.0", + "normalize-url": "1.9.1", + "schema-utils": "^1.0.0", + "webpack-sources": "^1.1.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "requires": { + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-pipeline": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.3.tgz", + "integrity": "sha512-cFOknTvng5vqnwOpDsZTWhNll6Jf8o2x+/diplafmxpuIymAjzoOolZG0VvQf3V2HgqzJNhnuKHYp2BqDgz8IQ==", + "requires": { + "minipass": "^3.0.0" + } + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "requires": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "dependencies": { + "for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=" + } + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "requires": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + } + }, + "multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "no-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.3.tgz", + "integrity": "sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw==", + "requires": { + "lower-case": "^2.0.1", + "tslib": "^1.10.0" + } + }, + "node-emoji": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", + "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==", + "requires": { + "lodash.toarray": "^4.4.0" + } + }, + "node-forge": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", + "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==" + }, + "node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + } + } + }, + "node-releases": { + "version": "1.1.59", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.59.tgz", + "integrity": "sha512-H3JrdUczbdiwxN5FuJPyCHnGHIFqQ0wWxo+9j1kAXAzqNMAHlo+4I/sYYxpyK0irQ73HgdiyzD32oqQDcU2Osw==" + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "requires": { + "abbrev": "1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=" + }, + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + }, + "dependencies": { + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + } + } + }, + "nprogress": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", + "integrity": "sha1-y480xTIT2JVyP8urkH6UIq28r7E=" + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "requires": { + "boolbase": "~1.0.0" + } + }, + "null-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-3.0.0.tgz", + "integrity": "sha512-hf5sNLl8xdRho4UPBOOeoIwT3WhjYcMUQm0zj44EhD6UscMAz72o2udpoDFBgykucdEDGIcd6SXbc/G6zssbzw==", + "requires": { + "loader-utils": "^1.2.3", + "schema-utils": "^1.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + }, + "object-is": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", + "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "requires": { + "isobject": "^3.0.1" + } + }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "open": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/open/-/open-7.0.4.tgz", + "integrity": "sha512-brSA+/yq+b08Hsr4c8fsEW2CRzk1BmfN3SAK/5VCHQ9bdoZJ4qa/+AfR0xHjlbbZUyPkUHs1b8x1RqdyZdkVqQ==", + "requires": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + } + }, + "opener": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", + "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==" + }, + "opn": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "requires": { + "is-wsl": "^1.1.0" + }, + "dependencies": { + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + } + } + }, + "optimize-css-assets-webpack-plugin": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.3.tgz", + "integrity": "sha512-q9fbvCRS6EYtUKKSwI87qm2IxlyJK5b4dygW1rKUBT6mMDhdG5e5bZT63v6tnJR9F9FB/H5a0HTmtw+laUBxKA==", + "requires": { + "cssnano": "^4.1.10", + "last-call-webpack-plugin": "^3.0.0" + } + }, + "original": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", + "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "requires": { + "url-parse": "^1.4.3" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-retry": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", + "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", + "requires": { + "retry": "^0.12.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "parallel-transform": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", + "requires": { + "cyclist": "^1.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "param-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.3.tgz", + "integrity": "sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA==", + "requires": { + "dot-case": "^3.0.3", + "tslib": "^1.10.0" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-asn1": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", + "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "requires": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + }, + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "parse-numeric-range": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-0.0.2.tgz", + "integrity": "sha1-tPCdQTx6282Yf26SM8e0shDJOOQ=" + }, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "pascal-case": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.1.tgz", + "integrity": "sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA==", + "requires": { + "no-case": "^3.0.3", + "tslib": "^1.10.0" + } + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==" + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + }, + "pbkdf2": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "requires": { + "find-up": "^3.0.0" + } + }, + "pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "requires": { + "find-up": "^3.0.0" + } + }, + "pnp-webpack-plugin": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz", + "integrity": "sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==", + "requires": { + "ts-pnp": "^1.1.6" + } + }, + "portfinder": { + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz", + "integrity": "sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ==", + "requires": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.1" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + }, + "postcss": { + "version": "7.0.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", + "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-attribute-case-insensitive": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz", + "integrity": "sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA==", + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^6.0.2" + } + }, + "postcss-calc": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.2.tgz", + "integrity": "sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ==", + "requires": { + "postcss": "^7.0.27", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.0.2" + } + }, + "postcss-color-functional-notation": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz", + "integrity": "sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-color-gray": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz", + "integrity": "sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw==", + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-color-hex-alpha": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz", + "integrity": "sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw==", + "requires": { + "postcss": "^7.0.14", + "postcss-values-parser": "^2.0.1" + } + }, + "postcss-color-mod-function": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz", + "integrity": "sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ==", + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-color-rebeccapurple": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz", + "integrity": "sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-colormin": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", + "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", + "requires": { + "browserslist": "^4.0.0", + "color": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-convert-values": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", + "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-custom-media": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz", + "integrity": "sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==", + "requires": { + "postcss": "^7.0.14" + } + }, + "postcss-custom-properties": { + "version": "8.0.11", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz", + "integrity": "sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA==", + "requires": { + "postcss": "^7.0.17", + "postcss-values-parser": "^2.0.1" + } + }, + "postcss-custom-selectors": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz", + "integrity": "sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==", + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-dir-pseudo-class": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz", + "integrity": "sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==", + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-discard-comments": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", + "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-duplicates": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", + "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-empty": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", + "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-overridden": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", + "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-double-position-gradients": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz", + "integrity": "sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==", + "requires": { + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-env-function": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz", + "integrity": "sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-focus-visible": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz", + "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-focus-within": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz", + "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-font-variant": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.0.tgz", + "integrity": "sha512-M8BFYKOvCrI2aITzDad7kWuXXTm0YhGdP9Q8HanmN4EF1Hmcgs1KK5rSHylt/lUJe8yLxiSwWAHdScoEiIxztg==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-gap-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz", + "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-image-set-function": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz", + "integrity": "sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-initial": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.2.tgz", + "integrity": "sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA==", + "requires": { + "lodash.template": "^4.5.0", + "postcss": "^7.0.2" + } + }, + "postcss-lab-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz", + "integrity": "sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg==", + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-load-config": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", + "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", + "requires": { + "cosmiconfig": "^5.0.0", + "import-cwd": "^2.0.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + } + } + }, + "postcss-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", + "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", + "requires": { + "loader-utils": "^1.1.0", + "postcss": "^7.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^1.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "postcss-logical": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-3.0.0.tgz", + "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-media-minmax": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz", + "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-merge-longhand": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", + "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", + "requires": { + "css-color-names": "0.0.4", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "stylehacks": "^4.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-merge-rules": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", + "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "cssnano-util-same-parent": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0", + "vendors": "^1.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-minify-font-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", + "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-minify-gradients": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", + "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "is-color-stop": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-minify-params": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", + "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", + "requires": { + "alphanum-sort": "^1.0.0", + "browserslist": "^4.0.0", + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "uniqs": "^2.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-minify-selectors": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", + "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", + "requires": { + "alphanum-sort": "^1.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-modules-extract-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", + "requires": { + "postcss": "^7.0.5" + } + }, + "postcss-modules-local-by-default": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz", + "integrity": "sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ==", + "requires": { + "icss-utils": "^4.1.1", + "postcss": "^7.0.16", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.0.0" + } + }, + "postcss-modules-scope": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", + "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^6.0.0" + } + }, + "postcss-modules-values": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", + "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", + "requires": { + "icss-utils": "^4.0.0", + "postcss": "^7.0.6" + } + }, + "postcss-nesting": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz", + "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-normalize-charset": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", + "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-normalize-display-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", + "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-positions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", + "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-repeat-style": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", + "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-string": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", + "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", + "requires": { + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-timing-functions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", + "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-unicode": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", + "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", + "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", + "requires": { + "is-absolute-url": "^2.0.0", + "normalize-url": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==" + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-normalize-whitespace": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", + "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-ordered-values": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", + "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-overflow-shorthand": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz", + "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-page-break": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz", + "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-place": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz", + "integrity": "sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-preset-env": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz", + "integrity": "sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg==", + "requires": { + "autoprefixer": "^9.6.1", + "browserslist": "^4.6.4", + "caniuse-lite": "^1.0.30000981", + "css-blank-pseudo": "^0.1.4", + "css-has-pseudo": "^0.10.0", + "css-prefers-color-scheme": "^3.1.1", + "cssdb": "^4.4.0", + "postcss": "^7.0.17", + "postcss-attribute-case-insensitive": "^4.0.1", + "postcss-color-functional-notation": "^2.0.1", + "postcss-color-gray": "^5.0.0", + "postcss-color-hex-alpha": "^5.0.3", + "postcss-color-mod-function": "^3.0.3", + "postcss-color-rebeccapurple": "^4.0.1", + "postcss-custom-media": "^7.0.8", + "postcss-custom-properties": "^8.0.11", + "postcss-custom-selectors": "^5.1.2", + "postcss-dir-pseudo-class": "^5.0.0", + "postcss-double-position-gradients": "^1.0.0", + "postcss-env-function": "^2.0.2", + "postcss-focus-visible": "^4.0.0", + "postcss-focus-within": "^3.0.0", + "postcss-font-variant": "^4.0.0", + "postcss-gap-properties": "^2.0.0", + "postcss-image-set-function": "^3.0.1", + "postcss-initial": "^3.0.0", + "postcss-lab-function": "^2.0.1", + "postcss-logical": "^3.0.0", + "postcss-media-minmax": "^4.0.0", + "postcss-nesting": "^7.0.0", + "postcss-overflow-shorthand": "^2.0.0", + "postcss-page-break": "^2.0.0", + "postcss-place": "^4.0.1", + "postcss-pseudo-class-any-link": "^6.0.0", + "postcss-replace-overflow-wrap": "^3.0.0", + "postcss-selector-matches": "^4.0.0", + "postcss-selector-not": "^4.0.0" + } + }, + "postcss-pseudo-class-any-link": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz", + "integrity": "sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==", + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-reduce-initial": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", + "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "postcss-reduce-transforms": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", + "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", + "requires": { + "cssnano-util-get-match": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-replace-overflow-wrap": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz", + "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-selector-matches": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz", + "integrity": "sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==", + "requires": { + "balanced-match": "^1.0.0", + "postcss": "^7.0.2" + } + }, + "postcss-selector-not": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz", + "integrity": "sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ==", + "requires": { + "balanced-match": "^1.0.0", + "postcss": "^7.0.2" + } + }, + "postcss-selector-parser": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", + "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", + "requires": { + "cssesc": "^3.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "postcss-svgo": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", + "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", + "requires": { + "is-svg": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "svgo": "^1.0.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + } + } + }, + "postcss-unique-selectors": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", + "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", + "requires": { + "alphanum-sort": "^1.0.0", + "postcss": "^7.0.0", + "uniqs": "^2.0.0" + } + }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" + }, + "postcss-values-parser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz", + "integrity": "sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg==", + "requires": { + "flatten": "^1.0.2", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + }, + "pretty-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", + "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", + "requires": { + "renderkid": "^2.0.1", + "utila": "~0.4" + } + }, + "pretty-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", + "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==" + }, + "prism-react-renderer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-1.1.1.tgz", + "integrity": "sha512-MgMhSdHuHymNRqD6KM3eGS0PNqgK9q4QF5P0yoQQvpB6jNjeSAi3jcSAz0Sua/t9fa4xDOMar9HJbLa08gl9ug==" + }, + "prismjs": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.20.0.tgz", + "integrity": "sha512-AEDjSrVNkynnw6A+B1DsFkd6AVdTnp+/WoUixFRULlCLZVRZlVQMVWio/16jv7G1FscUxQxOQhWwApgbnxr6kQ==", + "requires": { + "clipboard": "^2.0.0" + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "property-information": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.5.0.tgz", + "integrity": "sha512-RgEbCx2HLa1chNgvChcx+rrCWD0ctBmGSE0M7lVm1yyv4UbvbrWoXp/BkVLZefzjrRBGW8/Js6uh/BnlHXFyjA==", + "requires": { + "xtend": "^4.0.0" + } + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "requires": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" + }, + "querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "react": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", + "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + } + }, + "react-dev-utils": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.1.tgz", + "integrity": "sha512-XxTbgJnYZmxuPtY3y/UV0D8/65NKkmaia4rXzViknVnZeVlklSh8u6TnaEYPfAi/Gh1TP4mEOXHI6jQOPbeakQ==", + "requires": { + "@babel/code-frame": "7.8.3", + "address": "1.1.2", + "browserslist": "4.10.0", + "chalk": "2.4.2", + "cross-spawn": "7.0.1", + "detect-port-alt": "1.1.6", + "escape-string-regexp": "2.0.0", + "filesize": "6.0.1", + "find-up": "4.1.0", + "fork-ts-checker-webpack-plugin": "3.1.1", + "global-modules": "2.0.0", + "globby": "8.0.2", + "gzip-size": "5.1.1", + "immer": "1.10.0", + "inquirer": "7.0.4", + "is-root": "2.1.0", + "loader-utils": "1.2.3", + "open": "^7.0.2", + "pkg-up": "3.1.0", + "react-error-overlay": "^6.0.7", + "recursive-readdir": "2.2.2", + "shell-quote": "1.7.2", + "strip-ansi": "6.0.0", + "text-table": "0.2.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@nodelib/fs.stat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "browserslist": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.10.0.tgz", + "integrity": "sha512-TpfK0TDgv71dzuTsEAlQiHeWQ/tiPqgNZVdv046fvNtBZrjbv2O3TsWCDU0AWGJJKCF/KsjNdLzR9hXOsh/CfA==", + "requires": { + "caniuse-lite": "^1.0.30001035", + "electron-to-chromium": "^1.3.378", + "node-releases": "^1.1.52", + "pkg-up": "^3.1.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + } + } + }, + "dir-glob": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", + "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", + "requires": { + "arrify": "^1.0.1", + "path-type": "^3.0.0" + } + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=" + }, + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "fast-glob": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", + "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", + "requires": { + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.1.2", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.3", + "micromatch": "^3.1.10" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "globby": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz", + "integrity": "sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==", + "requires": { + "array-union": "^1.0.1", + "dir-glob": "2.0.0", + "fast-glob": "^2.0.2", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "react-dom": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", + "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" + } + }, + "react-error-overlay": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz", + "integrity": "sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==" + }, + "react-fast-compare": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", + "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" + }, + "react-helmet": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", + "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==", + "requires": { + "object-assign": "^4.1.1", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.1.1", + "react-side-effect": "^2.1.0" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "react-loadable": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-loadable/-/react-loadable-5.5.0.tgz", + "integrity": "sha512-C8Aui0ZpMd4KokxRdVAm2bQtI03k2RMRNzOB+IipV3yxFTSVICv7WoUr5L9ALB5BmKO1iHgZtWM8EvYG83otdg==", + "requires": { + "prop-types": "^15.5.0" + } + }, + "react-loadable-ssr-addon": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/react-loadable-ssr-addon/-/react-loadable-ssr-addon-0.2.3.tgz", + "integrity": "sha512-vPCqsmiafAMDcS9MLgXw3m4yMI40v1UeI8FTYJJkjf85LugKNnHf6D9yoDTzYwp8wEGF5viekwOD03ZPxSwnQQ==" + }, + "react-router": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", + "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.4.0", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "requires": { + "isarray": "0.0.1" + } + } + } + }, + "react-router-config": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/react-router-config/-/react-router-config-5.1.1.tgz", + "integrity": "sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==", + "requires": { + "@babel/runtime": "^7.1.2" + } + }, + "react-router-dom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", + "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.2.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, + "react-side-effect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.0.tgz", + "integrity": "sha512-IgmcegOSi5SNX+2Snh1vqmF0Vg/CbkycU9XZbOHJlZ6kMzTmi3yc254oB1WCkgA7OQtIAoLmcSFuHTc/tlcqXg==" + }, + "react-toggle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/react-toggle/-/react-toggle-4.1.1.tgz", + "integrity": "sha512-+wXlMcSpg8SmnIXauMaZiKpR+r2wp2gMUteroejp2UTSqGTVvZLN+m9EhMzFARBKEw7KpQOwzCyfzeHeAndQGw==", + "requires": { + "classnames": "^2.2.5" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "requires": { + "picomatch": "^2.2.1" + } + }, + "reading-time": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.2.0.tgz", + "integrity": "sha512-5b4XmKK4MEss63y0Lw0vn0Zn6G5kiHP88mUnD8UeEsyORj3sh1ghTH0/u6m1Ax9G2F4wUZrknlp6WlIsCvoXVA==" + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "requires": { + "resolve": "^1.1.6" + } + }, + "recursive-readdir": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", + "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", + "requires": { + "minimatch": "3.0.4" + } + }, + "reduce": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/reduce/-/reduce-1.0.2.tgz", + "integrity": "sha512-xX7Fxke/oHO5IfZSk77lvPa/7bjMh9BuCk4OOoX5XTXrM7s0Z+MkPfSDfz0q7r91BhhGSs8gii/VEN/7zhCPpQ==", + "requires": { + "object-keys": "^1.1.0" + } + }, + "regenerate": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz", + "integrity": "sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==" + }, + "regenerate-unicode-properties": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", + "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + }, + "regenerator-transform": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "regexp.prototype.flags": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "regexpu-core": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", + "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.2.0" + } + }, + "regjsgen": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", + "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==" + }, + "regjsparser": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", + "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + } + } + }, + "rehype-parse": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-6.0.2.tgz", + "integrity": "sha512-0S3CpvpTAgGmnz8kiCyFLGuW5yA4OQhyNTm/nwPopZ7+PI11WnGl1TTWTGv/2hPEe/g2jRLlhVVSsoDH8waRug==", + "requires": { + "hast-util-from-parse5": "^5.0.0", + "parse5": "^5.0.0", + "xtend": "^4.0.0" + } + }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=" + }, + "remark-admonitions": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/remark-admonitions/-/remark-admonitions-1.2.1.tgz", + "integrity": "sha512-Ji6p68VDvD+H1oS95Fdx9Ar5WA2wcDA4kwrrhVU7fGctC6+d3uiMICu7w7/2Xld+lnU7/gi+432+rRbup5S8ow==", + "requires": { + "rehype-parse": "^6.0.2", + "unified": "^8.4.2", + "unist-util-visit": "^2.0.1" + }, + "dependencies": { + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" + }, + "unified": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-8.4.2.tgz", + "integrity": "sha512-JCrmN13jI4+h9UAyKEoGcDZV+i1E7BLFuG7OsaDvTXI5P0qhHX+vZO/kOhz9jn8HGENDKbwSeB0nVOg4gVStGA==", + "requires": { + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^2.0.0", + "trough": "^1.0.0", + "vfile": "^4.0.0" + } + } + } + }, + "remark-emoji": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-2.1.0.tgz", + "integrity": "sha512-lDddGsxXURV01WS9WAiS9rO/cedO1pvr9tahtLhr6qCGFhHG4yZSJW3Ha4Nw9Uk1hLNmUBtPC0+m45Ms+xEitg==", + "requires": { + "emoticon": "^3.2.0", + "node-emoji": "^1.10.0", + "unist-util-visit": "^2.0.2" + } + }, + "remark-footnotes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/remark-footnotes/-/remark-footnotes-1.0.0.tgz", + "integrity": "sha512-X9Ncj4cj3/CIvLI2Z9IobHtVi8FVdUrdJkCNaL9kdX8ohfsi18DXHsCVd/A7ssARBdccdDb5ODnt62WuEWaM/g==" + }, + "remark-mdx": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-1.6.6.tgz", + "integrity": "sha512-BkR7SjP+3OvrCsWGlYy1tWEsZ8aQ86x+i7XWbW79g73Ws/cCaeVsEn0ZxAzzoTRH+PJWVU7Mbe64GdejEyKr2g==", + "requires": { + "@babel/core": "7.9.6", + "@babel/helper-plugin-utils": "7.8.3", + "@babel/plugin-proposal-object-rest-spread": "7.9.6", + "@babel/plugin-syntax-jsx": "7.8.3", + "@mdx-js/util": "^1.6.6", + "is-alphabetical": "1.0.4", + "remark-parse": "8.0.2", + "unified": "9.0.0" + }, + "dependencies": { + "@babel/core": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.6.tgz", + "integrity": "sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.6", + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helpers": "^7.9.6", + "@babel/parser": "^7.9.6", + "@babel/template": "^7.8.6", + "@babel/traverse": "^7.9.6", + "@babel/types": "^7.9.6", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.6.tgz", + "integrity": "sha512-Ga6/fhGqA9Hj+y6whNpPv8psyaK5xzrQwSPsGPloVkvmH+PqW1ixdnfJ9uIO06OjQNYol3PMnfmJ8vfZtkzF+A==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.9.5" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz", + "integrity": "sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "remark-parse": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.2.tgz", + "integrity": "sha512-eMI6kMRjsAGpMXXBAywJwiwAse+KNpmt+BK55Oofy4KvBZEqUDj6mWbGLJZrujoPIPPxDXzn3T9baRlpsm2jnQ==", + "requires": { + "ccount": "^1.0.0", + "collapse-white-space": "^1.0.2", + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-whitespace-character": "^1.0.0", + "is-word-character": "^1.0.0", + "markdown-escapes": "^1.0.0", + "parse-entities": "^2.0.0", + "repeat-string": "^1.5.4", + "state-toggle": "^1.0.0", + "trim": "0.0.1", + "trim-trailing-lines": "^1.0.0", + "unherit": "^1.0.4", + "unist-util-remove-position": "^2.0.0", + "vfile-location": "^3.0.0", + "xtend": "^4.0.1" + } + }, + "remark-squeeze-paragraphs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-squeeze-paragraphs/-/remark-squeeze-paragraphs-4.0.0.tgz", + "integrity": "sha512-8qRqmL9F4nuLPIgl92XUuxI3pFxize+F1H0e/W3llTk0UsjJaj01+RrirkMw7P21RKe4X6goQhYRSvNWX+70Rw==", + "requires": { + "mdast-squeeze-paragraphs": "^4.0.0" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "renderkid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.3.tgz", + "integrity": "sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA==", + "requires": { + "css-select": "^1.1.0", + "dom-converter": "^0.2", + "htmlparser2": "^3.3.0", + "strip-ansi": "^3.0.0", + "utila": "^0.4.0" + } + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=" + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-like": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", + "integrity": "sha1-rW8wwTvs15cBDEaK+ndcDAprR/o=" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "requires": { + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + } + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, + "rgb-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", + "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=" + }, + "rgba-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", + "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=" + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" + }, + "run-parallel": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==" + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "requires": { + "aproba": "^1.1.1" + } + }, + "rx": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", + "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=" + }, + "rxjs": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz", + "integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "scheduler": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", + "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "requires": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + } + }, + "section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "requires": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + } + }, + "select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=", + "optional": true + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=" + }, + "selfsigned": { + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz", + "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==", + "requires": { + "node-forge": "0.9.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serialize-javascript": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", + "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==" + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shallow-clone": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", + "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=", + "requires": { + "is-extendable": "^0.1.1", + "kind-of": "^2.0.1", + "lazy-cache": "^0.2.3", + "mixin-object": "^2.0.1" + }, + "dependencies": { + "kind-of": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", + "requires": { + "is-buffer": "^1.0.2" + } + }, + "lazy-cache": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", + "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=" + } + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "shell-quote": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", + "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==" + }, + "shelljs": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", + "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + } + } + }, + "sitemap": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-3.2.2.tgz", + "integrity": "sha512-TModL/WU4m2q/mQcrDgNANn0P4LwprM9MMvG4hu5zP4c6IIKs2YLTu6nXXnNr8ODW/WFtxKggiJ1EGn2W0GNmg==", + "requires": { + "lodash.chunk": "^4.2.0", + "lodash.padstart": "^4.6.1", + "whatwg-url": "^7.0.0", + "xmlbuilder": "^13.0.0" + } + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "sockjs": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.20.tgz", + "integrity": "sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA==", + "requires": { + "faye-websocket": "^0.10.0", + "uuid": "^3.4.0", + "websocket-driver": "0.6.5" + } + }, + "sockjs-client": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.4.0.tgz", + "integrity": "sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==", + "requires": { + "debug": "^3.2.5", + "eventsource": "^1.0.7", + "faye-websocket": "~0.11.1", + "inherits": "^2.0.3", + "json3": "^3.3.2", + "url-parse": "^1.4.3" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "requires": { + "websocket-driver": ">=0.5.1" + } + } + } + }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "requires": { + "is-plain-obj": "^1.0.0" + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + }, + "space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==" + }, + "spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "requires": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "requires": { + "extend-shallow": "^3.0.0" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==" + }, + "state-toggle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", + "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==" + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "std-env": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-2.2.1.tgz", + "integrity": "sha512-IjYQUinA3lg5re/YMlwlfhqNRTzMZMqE+pezevdcTaHceqx8ngEi1alX9nNCk9Sc81fy1fLDeQoaCzeiW1yBOQ==", + "requires": { + "ci-info": "^1.6.0" + } + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "requires": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "dependencies": { + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=" + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" + }, + "style-to-object": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", + "integrity": "sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==", + "requires": { + "inline-style-parser": "0.1.1" + } + }, + "stylehacks": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", + "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" + }, + "svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "requires": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "css-what": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.3.0.tgz", + "integrity": "sha512-pv9JPyatiPaQ6pf4OvD/dbfm0o5LviWmwxNWzblYf/1u9QZd0ihV+PMwy5jdQWQ3349kZmKEx9WXuSka2dM4cg==" + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + } + } + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" + }, + "terser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "terser-webpack-plugin": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.7.tgz", + "integrity": "sha512-xzYyaHUNhzgaAdBsXxk2Yvo/x1NJdslUaussK3fdpBbvttm1iIwU+c26dj9UxJcwk2c5UWt5F55MUTIA8BE7Dg==", + "requires": { + "cacache": "^13.0.1", + "find-cache-dir": "^3.3.1", + "jest-worker": "^25.4.0", + "p-limit": "^2.3.0", + "schema-utils": "^2.6.6", + "serialize-javascript": "^3.1.0", + "source-map": "^0.6.1", + "terser": "^4.6.12", + "webpack-sources": "^1.4.3" + }, + "dependencies": { + "cacache": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", + "integrity": "sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w==", + "requires": { + "chownr": "^1.1.2", + "figgy-pudding": "^3.5.1", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.2", + "infer-owner": "^1.0.4", + "lru-cache": "^5.1.1", + "minipass": "^3.0.0", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "p-map": "^3.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^2.7.1", + "ssri": "^7.0.0", + "unique-filename": "^1.1.1" + } + }, + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "requires": { + "find-up": "^4.0.0" + } + }, + "serialize-javascript": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", + "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==", + "requires": { + "randombytes": "^2.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "ssri": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", + "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==", + "requires": { + "figgy-pudding": "^3.5.1", + "minipass": "^3.1.1" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + }, + "timers-browserify": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", + "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", + "requires": { + "setimmediate": "^1.0.4" + } + }, + "timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" + }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", + "optional": true + }, + "tiny-invariant": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", + "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=" + }, + "to-factory": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-factory/-/to-factory-1.0.0.tgz", + "integrity": "sha1-hzivi9lxIK0dQEeXKtpVY7+UebE=" + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + } + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + } + } + }, + "trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" + }, + "trim-lines": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-1.1.3.tgz", + "integrity": "sha512-E0ZosSWYK2mkSu+KEtQ9/KqarVjA9HztOSX+9FDdNacRAq29RRV6ZQNgob3iuW8Htar9vAfEa6yyt5qBAHZDBA==" + }, + "trim-trailing-lines": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz", + "integrity": "sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA==" + }, + "trough": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", + "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==" + }, + "tryer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", + "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" + }, + "ts-pnp": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", + "integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==" + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "unherit": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", + "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", + "requires": { + "inherits": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", + "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==" + }, + "unicode-property-aliases-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", + "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==" + }, + "unified": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.0.0.tgz", + "integrity": "sha512-ssFo33gljU3PdlWLjNp15Inqb77d6JnJSfyplGJPT/a+fNRNyCBeveBAYJdO5khKdF6WVHa/yYCC7Xl6BDwZUQ==", + "requires": { + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^2.0.0", + "trough": "^1.0.0", + "vfile": "^4.0.0" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" + } + } + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" + }, + "uniqs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", + "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=" + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unist-builder": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz", + "integrity": "sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==" + }, + "unist-util-generated": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.5.tgz", + "integrity": "sha512-1TC+NxQa4N9pNdayCYA1EGUOCAO0Le3fVp7Jzns6lnua/mYgwHo0tz5WUAfrdpNch1RZLHc61VZ1SDgrtNXLSw==" + }, + "unist-util-is": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz", + "integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==" + }, + "unist-util-position": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-3.1.0.tgz", + "integrity": "sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==" + }, + "unist-util-remove": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-2.0.0.tgz", + "integrity": "sha512-HwwWyNHKkeg/eXRnE11IpzY8JT55JNM1YCwwU9YNCnfzk6s8GhPXrVBBZWiwLeATJbI7euvoGSzcy9M29UeW3g==", + "requires": { + "unist-util-is": "^4.0.0" + }, + "dependencies": { + "unist-util-is": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz", + "integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==" + } + } + }, + "unist-util-remove-position": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz", + "integrity": "sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA==", + "requires": { + "unist-util-visit": "^2.0.0" + } + }, + "unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "requires": { + "@types/unist": "^2.0.2" + } + }, + "unist-util-visit": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + }, + "dependencies": { + "unist-util-is": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz", + "integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==" + } + } + }, + "unist-util-visit-parents": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.0.tgz", + "integrity": "sha512-0g4wbluTF93npyPrp/ymd3tCDTMnP0yo2akFD2FIBAYXq/Sga3lwaU1D8OYKbtpioaI6CkDcQ6fsMnmtzt7htw==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0" + }, + "dependencies": { + "unist-util-is": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz", + "integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==" + } + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=" + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + } + } + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + } + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "url-parse": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", + "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + } + }, + "utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "vendors": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", + "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vfile": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.1.1.tgz", + "integrity": "sha512-lRjkpyDGjVlBA7cDQhQ+gNcvB1BGaTHYuSOcY3S7OhDmBtnzX95FhtZZDecSTDm6aajFymyve6S5DN4ZHGezdQ==", + "requires": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "replace-ext": "1.0.0", + "unist-util-stringify-position": "^2.0.0", + "vfile-message": "^2.0.0" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" + } + } + }, + "vfile-location": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.0.1.tgz", + "integrity": "sha512-yYBO06eeN/Ki6Kh1QAkgzYpWT1d3Qln+ZCtSbJqFExPl1S3y2qqotJQXoh6qEvl/jDlgpUJolBn3PItVnnZRqQ==" + }, + "vfile-message": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", + "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + } + }, + "vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" + }, + "wait-file": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/wait-file/-/wait-file-1.0.5.tgz", + "integrity": "sha512-udLpJY/eOxlrMm3+XD1RLuF2oT9B7J7wiyR5/9xrvQymS6YR6trWvVhzOldHrVbLwyiRmLj9fcvsjzpSXeZHkw==", + "requires": { + "@hapi/joi": "^15.1.0", + "fs-extra": "^8.1.0", + "rx": "^4.1.0" + } + }, + "watchpack": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz", + "integrity": "sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g==", + "requires": { + "chokidar": "^3.4.0", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.0" + } + }, + "watchpack-chokidar2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz", + "integrity": "sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==", + "optional": true, + "requires": { + "chokidar": "^2.1.8" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "optional": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "optional": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "optional": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "optional": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "optional": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "optional": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "optional": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "optional": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "optional": true + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "optional": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "optional": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "optional": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "optional": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "optional": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "optional": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "optional": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "web-namespaces": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-1.1.4.tgz", + "integrity": "sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==" + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + }, + "webpack": { + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz", + "integrity": "sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/wasm-edit": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "acorn": "^6.4.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^4.1.0", + "eslint-scope": "^4.0.3", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.3", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", + "schema-utils": "^1.0.0", + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.3", + "watchpack": "^1.6.1", + "webpack-sources": "^1.4.1" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "serialize-javascript": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", + "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==", + "requires": { + "randombytes": "^2.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "terser-webpack-plugin": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz", + "integrity": "sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA==", + "requires": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^3.1.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "webpack-bundle-analyzer": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.8.0.tgz", + "integrity": "sha512-PODQhAYVEourCcOuU+NiYI7WdR8QyELZGgPvB1y2tjbUpbmcQOt5Q7jEK+ttd5se0KSBKD9SXHCEozS++Wllmw==", + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1", + "bfj": "^6.1.1", + "chalk": "^2.4.1", + "commander": "^2.18.0", + "ejs": "^2.6.1", + "express": "^4.16.3", + "filesize": "^3.6.1", + "gzip-size": "^5.0.0", + "lodash": "^4.17.15", + "mkdirp": "^0.5.1", + "opener": "^1.5.1", + "ws": "^6.0.0" + }, + "dependencies": { + "acorn": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", + "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "filesize": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", + "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==" + } + } + }, + "webpack-dev-middleware": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz", + "integrity": "sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==", + "requires": { + "memory-fs": "^0.4.1", + "mime": "^2.4.4", + "mkdirp": "^0.5.1", + "range-parser": "^1.2.1", + "webpack-log": "^2.0.0" + }, + "dependencies": { + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" + } + } + }, + "webpack-dev-server": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz", + "integrity": "sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg==", + "requires": { + "ansi-html": "0.0.7", + "bonjour": "^3.5.0", + "chokidar": "^2.1.8", + "compression": "^1.7.4", + "connect-history-api-fallback": "^1.6.0", + "debug": "^4.1.1", + "del": "^4.1.1", + "express": "^4.17.1", + "html-entities": "^1.3.1", + "http-proxy-middleware": "0.19.1", + "import-local": "^2.0.0", + "internal-ip": "^4.3.0", + "ip": "^1.1.5", + "is-absolute-url": "^3.0.3", + "killable": "^1.0.1", + "loglevel": "^1.6.8", + "opn": "^5.5.0", + "p-retry": "^3.0.1", + "portfinder": "^1.0.26", + "schema-utils": "^1.0.0", + "selfsigned": "^1.10.7", + "semver": "^6.3.0", + "serve-index": "^1.9.1", + "sockjs": "0.3.20", + "sockjs-client": "1.4.0", + "spdy": "^4.0.2", + "strip-ansi": "^3.0.1", + "supports-color": "^6.1.0", + "url": "^0.11.0", + "webpack-dev-middleware": "^3.7.2", + "webpack-log": "^2.0.0", + "ws": "^6.2.1", + "yargs": "^13.3.2" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "requires": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "optional": true + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + } + } + }, + "is-absolute-url": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", + "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==" + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "requires": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + } + }, + "webpack-merge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", + "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", + "requires": { + "lodash": "^4.17.15" + } + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "webpackbar": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-4.0.0.tgz", + "integrity": "sha512-k1qRoSL/3BVuINzngj09nIwreD8wxV4grcuhHTD8VJgUbGcy8lQSPqv+bM00B7F+PffwIsQ8ISd4mIwRbr23eQ==", + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^2.4.2", + "consola": "^2.10.0", + "figures": "^3.0.0", + "pretty-time": "^1.1.0", + "std-env": "^2.2.1", + "text-table": "^0.2.0", + "wrap-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + } + } + } + } + }, + "websocket-driver": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", + "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=", + "requires": { + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" + }, + "whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "requires": { + "errno": "~0.1.7" + } + }, + "worker-rpc": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz", + "integrity": "sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg==", + "requires": { + "microevent.ts": "~0.1.1" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "requires": { + "sax": "^1.2.4" + } + }, + "xmlbuilder": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz", + "integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "yaml": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==" + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + } + } + }, + "zepto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/zepto/-/zepto-1.2.0.tgz", + "integrity": "sha1-4Se9nmb9hGvl6rSME5SIL3wOT5g=" + }, + "zwitch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", + "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==" + } + } +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 0000000000..d024da876c --- /dev/null +++ b/website/package.json @@ -0,0 +1,31 @@ +{ + "name": "website", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "docusaurus start", + "start-no-watch": "docusaurus start --no-open --poll", + "build": "docusaurus build", + "swizzle": "docusaurus swizzle", + "deploy": "./deploy.sh" + }, + "dependencies": { + "@docusaurus/core": "^2.0.0-alpha.58", + "@docusaurus/preset-classic": "^2.0.0-alpha.58", + "clsx": "^1.1.1", + "react": "^16.8.4", + "react-dom": "^16.8.4" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/website/sidebars.js b/website/sidebars.js new file mode 100644 index 0000000000..03b790c3e4 --- /dev/null +++ b/website/sidebars.js @@ -0,0 +1,54 @@ +module.exports = { + docs: { + Documentation: ['introduction', 'overview', 'license'], + 'Deploying Stratos': [ + 'deploy/overview', + { + "Cloud Foundry": [ + 'deploy/cloud-foundry/cloud-foundry', + 'deploy/cloud-foundry/db-migration', + 'deploy/cloud-foundry/cf-troubleshooting' + ], + }, + { + Kubernetes: [ + 'deploy/kubernetes', + 'deploy/kubernetes/helm-installation' + ], + }, + 'deploy/all-in-one', + 'deploy/access', + 'deploy/troubleshooting', + ], + 'Advanced Topics': [ + 'advanced/invite-user-guide', + 'advanced/sso', + 'advanced/bosh-metrics' + ], + 'Development': [ + 'developer/contributing', + 'developer/introduction', + { + Frontend: [ + 'developer/frontend', + 'developer/frontend-tests' + ] + }, + { + Backend: [ + 'developer/backend', + ] + }, + 'developer/developers-guide-e2e-tests', + 'developer/developers-guide-env-tech', + ], + 'Extending Stratos': [ + 'extensions/introduction', + 'extensions/v4-migration', + 'extensions/theming', + 'extensions/frontend', + 'extensions/backend', + ], + + }, +}; diff --git a/website/src/css/custom.css b/website/src/css/custom.css new file mode 100644 index 0000000000..1b5e6f88cd --- /dev/null +++ b/website/src/css/custom.css @@ -0,0 +1,191 @@ +/* stylelint-disable docusaurus/copyright-header */ +/** + * Any CSS included here will be global. The classic template + * bundles Infima by default. Infima is a CSS framework designed to + * work well for content-centric websites. + */ + +/* You can override the default Infima variables here. */ +:root { + --ifm-color-primary: #2196f3; + --ifm-color-primary-dark: #0d89ec; + --ifm-color-primary-darker: #0c81df; + --ifm-color-primary-darkest: #0a6bb7; + --ifm-color-primary-light: #3ba2f4; + --ifm-color-primary-lighter: #48a9f5; + --ifm-color-primary-lightest: #70bbf7; + --ifm-code-font-size: 95%; + --ifm-navbar-link-color: #fff; +} + +.docusaurus-highlight-code-line { + background-color: rgb(72, 77, 91); + display: block; + margin: 0 calc(-1 * var(--ifm-pre-padding)); + padding: 0 var(--ifm-pre-padding); +} + +img.feature-img { + filter: grayscale(100%); + height: 100px; + width: 100px; +} + +img.cf-logo { + width: auto; +} + +.navbar { + background-color: #333; +} + +.navbar-sidebar .navbar-sidebar__brand { + background-color: #333; +} + +.navbar__toggle { + color: #d0d0d0; +} + +.navbar__brand:hover { + color: var(--ifm-link-hover-color); +} + +.main-wrapper .hero { + min-height: 480px; +} + +.main-wrapper .hero a.button.button--outline.button--secondary { + color: #fff; +} + +.main-wrapper .hero a.button.button--outline.button--secondary:hover { + color: #2196f3; +} + +.container { + text-align: left; +} + +.container .get-started { + justify-content: start; + padding-top: 20px; +} + +.hero__subtitle { + font-size: 36px; +} + +/* Reduce the text sizes */ + +#__docusaurus article h1 { + font-size: 28px; +} + +#__docusaurus article h2 { + font-size: 24px; +} + +#__docusaurus article h3 { + font-size: 20px; +} + +#__docusaurus article h4 { + font-size: 18px; +} + +.menu__list, .menu__link { + font-size: 14px; +} + +#__docusaurus .menu .menu__link--sublist:after { + background-size: 1.25rem 1.25rem; +} + +#__docusaurus .docSidebarContainer_node_modules-\@docusaurus-theme-classic-src-theme-DocPage- { + width: 280px; +} + +.home-intro { + /*margin-right: 150px;*/ +} + +.home-logo { + height: 300px; + opacity: .9; + position: absolute; + right: -150px; +} + +@media screen and (max-width: 966px) { + .home-logo { + display: none; + } + + .hero__subtitle { + font-size: 35px; + } + + section .screenshot { + margin: 0 0; + } + + +} + +@media screen and (max-width: 813px) { + .hero__subtitle { + font-size: 31px; + } + + img.feature-img { + height: 64px; + width: auto; + } + + +} + +/* Screenshot */ + +.blue { + background-color: #2196f3; + color: #fff +} + +.white { + background-color: #fff; + color: #444; +} + + +.screenshot { + display: flex; + margin: 60px 0; +} + +.screenshot img { + width: 600px; + -webkit-box-shadow: 0px 0px 37px 15px rgba(20,20,20,0.75); + -moz-box-shadow: 0px 0px 37px 15px rgba(20,20,20,0.75); + box-shadow: 0px 0px 37px 15px rgba(20,20,20,0.75); +} + +.screenshot img.right { + margin-left: 60px; +} + +.screenshot img.left { + margin-right: 60px; +} + +.screenshot div { + font-size: 20px; +} + +@media screen and (max-width: 966px) { + .screenshot img { + display: none; + margin: 0; + } +} diff --git a/website/src/pages/index.js b/website/src/pages/index.js new file mode 100644 index 0000000000..fc5979e229 --- /dev/null +++ b/website/src/pages/index.js @@ -0,0 +1,176 @@ +import Link from '@docusaurus/Link'; +import useBaseUrl from '@docusaurus/useBaseUrl'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import Layout from '@theme/Layout'; +import clsx from 'clsx'; +import React from 'react'; + +import styles from './styles.module.css'; + +const features = [ + { + title: <>Cloud Foundry</>, + imageUrl: 'img/cloudfoundry.png', + description: ( + <> + Stratos is the de-facto UI for Cloud Foundry, providing a rich management experience + for all you Cloud Foundry needs... and we're an offical Cloud Foundry project too! + </> + ), + cls: 'cf-logo' + }, + { + title: <>Kubernetes</>, + imageUrl: 'img/kubernetes.svg', + description: ( + <> + Stratos sports a growing feature set for Kubernetes developers, extending its reach further + towards providing a single-pane-of-glass for your Cloud Native application development needs. + </> + ), + }, + { + title: <>Multi-Cluster</>, + imageUrl: 'img/multi-cluster.svg', + description: ( + <> + Stratos allows you to manage multiple Cloud Foundry and Kubernetes cluster from a single + management UI + </> + ), + }, + { + title: <>Extensible</>, + imageUrl: 'img/extend.svg', + description: ( + <> + Stratos is built with extensibility in mind and we continue to expand and improve the + extensibility experience for developers + </> + ), + }, + { + title: <>Open Source</>, + imageUrl: 'img/open-source.svg', + description: ( + <> + Stratos is Open Source with an Apache 2.0 License. Our codes lives on GitHub and we're a project within + the Cloud Foundry Foundation + </> + ), + }, + { + title: <>Easy to Deploy</>, + imageUrl: 'img/deploy.svg', + description: ( + <> + Stratos is easy to deploy and can be pushed as an application to Cloud Foundry, deployed to Kubernetes using Helm + or run locally in a Docker container + </> + ), + }, + + +]; + +function Feature({imageUrl, title, description, cls}) { + const imgUrl = useBaseUrl(imageUrl); + return ( + <div className={clsx('col col--4', styles.feature)}> + {imgUrl && ( + <div className=""> + <img className={clsx(styles.featureImage, cls, 'feature-img')} src={imgUrl} alt={title} /> + </div> + )} + <h3>{title}</h3> + <p>{description}</p> + </div> + ); +} + +function Home() { + const context = useDocusaurusContext(); + const {siteConfig = {}} = context; + return ( + <Layout + title={`Home`} + description="Stratos - Web-based Management Interface for Cloud Foundry and Kubernetes"> + <header className={clsx('hero hero--primary', styles.heroBanner)}> + <div className="container home-intro"> + <h1 className="hero__title">{siteConfig.title}</h1> + <h2 className="hero__subtitle">Open-Source Multi-Cluster UI for <br/> Cloud Foundry and Kubernetes</h2> + <div className={clsx(styles.buttons, 'get-started')}> + <Link + className={clsx( + 'button button--outline button--secondary button--lg', + styles.getStarted, + )} + to={useBaseUrl('docs/')}> + Get Started + </Link> + </div> + </div> + <img class="home-logo" src="img/logo.png" /> + </header> + <main> + {features && features.length > 0 && ( + <section className={styles.features}> + <div className="container"> + <div className="row"> + {features.map((props, idx) => ( + <Feature key={idx} {...props} /> + ))} + </div> + </div> + </section> + )} + <section className={clsx(styles.features, 'screenshot-section', 'blue')}> + <div className="container"> + <div class="screenshot"> + <img class="left" src="img/screens/cf-app.png" /> + <div> + <h2>Cloud Foundry</h2> + <p>Deploy and manage applications in Cloud Foundry. Stream application logs, scale applications and ssh to application instances</p> + <p>View and manage Cloud Foundry organizations and spaces and quotas.</p> + <p>Browse the Service Marketplace and create and manage service instances.</p> + <p>and a whole lot more ...</p> + </div> + </div> + </div> + </section> + + <section className={clsx(styles.features, 'screenshot-section', 'white')}> + <div className="container"> + <div class="screenshot"> + <div> + <h2>Kubernetes</h2> + <p>View cluster-level metadata</p> + <p>Browse, view and install Helm Charts</p> + <p>View Helm Releases and see relationships between Kubernetes Resources</p> + <p>and lots more ...</p> + </div> + <img class="right" src="img/screens/kube-graph.png" /> + </div> + </div> + </section> + + <section className={clsx(styles.features, 'screenshot-section', 'blue')}> + <div className="container"> + <div class="screenshot"> + <img class="left" src="img/screens/endpoints.png" /> + <div> + <h2>Multi-Cluster</h2> + <p>Add and Connect multiple Cloud Foundry and/or Kubernetes clusters.</p> + <p>Seemlessly switch between clusters and get aggregated views across clusters.</p> + <p>Favorite clusters and entities for easy access from the Home screen.</p> + </div> + </div> + </div> + </section> + + </main> + </Layout> + ); +} + +export default Home; diff --git a/website/src/pages/styles.module.css b/website/src/pages/styles.module.css new file mode 100644 index 0000000000..c1aa85121c --- /dev/null +++ b/website/src/pages/styles.module.css @@ -0,0 +1,37 @@ +/* stylelint-disable docusaurus/copyright-header */ + +/** + * CSS files with the .module.css suffix will be treated as CSS modules + * and scoped locally. + */ + +.heroBanner { + padding: 4rem 0; + text-align: center; + position: relative; + overflow: hidden; +} + +@media screen and (max-width: 966px) { + .heroBanner { + padding: 2rem; + } +} + +.buttons { + display: flex; + align-items: center; + justify-content: center; +} + +.features { + display: flex; + align-items: center; + padding: 2rem 0; + width: 100%; +} + +.featureImage { + height: 200px; + width: 200px; +} diff --git a/website/static/.nojekyll b/website/static/.nojekyll new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/images/Browserstack-logo.svg b/website/static/images/Browserstack-logo.svg similarity index 100% rename from docs/images/Browserstack-logo.svg rename to website/static/images/Browserstack-logo.svg diff --git a/docs/images/extensions/app-tab-example.png b/website/static/images/extensions/app-tab-example.png similarity index 100% rename from docs/images/extensions/app-tab-example.png rename to website/static/images/extensions/app-tab-example.png diff --git a/docs/images/extensions/appwall-action-example.png b/website/static/images/extensions/appwall-action-example.png similarity index 100% rename from docs/images/extensions/appwall-action-example.png rename to website/static/images/extensions/appwall-action-example.png diff --git a/docs/images/high-level-arch.png b/website/static/images/high-level-arch.png similarity index 100% rename from docs/images/high-level-arch.png rename to website/static/images/high-level-arch.png diff --git a/docs/images/screenshots/app-summary.png b/website/static/images/screenshots/app-summary.png similarity index 100% rename from docs/images/screenshots/app-summary.png rename to website/static/images/screenshots/app-summary.png diff --git a/website/static/img/cloudfoundry.png b/website/static/img/cloudfoundry.png new file mode 100644 index 0000000000000000000000000000000000000000..378a74380f786b3875408d2bb53d1bfde949f076 GIT binary patch literal 5301 zcmV;m6iVxfP)<h;3K|Lk000e1NJLTq002e+003kN1^@s6J4!~m00001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGf6951U69E94oEQKA6iP`%K~#8N<(vtW zmBp3E?QUskXxdgW#$lkv#--ILY9!KU4w@sGDEktS4j`Md2s1Joz?nH@oFpd3nG=Xg zI6$-Y0z#t#u~|+anPjk02i%B6>7XsbC@2WM3e7UV`})?i-uu?qtCRfCsq<CUtFP+b zf8DxOU%mRiqzl>rxw*L)a!K#nwd-}AJ9obK{Q2{ejv^^3=}c8s)t1AD55G&Ys;sQ+ zY{M_DhgKnG`t<34d*;lUVRdzN-z9ki;bN3e?a-k^`wK6;Fga2v4wOIl1%R~EBtNJ8 z-n6u|Z7C@!n-(ovbf}?J%b`Vyx$nOFZc9x~ol6)<NYm^l;q>X#we|J&8@T@GbI(2Z zhGsWAnw=Q24mx@&!jGeeyLnkeQy~_G1%3$OE8Dkkzr4Avs9A{_J$m%5kg<g!@-@wB z70?lfA!$%^T2QkPBUXa5Tn8!NYAFi|f?Ci{ocWmMu%0FpGj{CQ>oMg$m~;otYE$5Q zY9Q&BRjXE&YgVJsXkx@lYHDhJ1Su1+l4kc+O@UYs1TF5^vExr0YdwupNF8hmzT^(g z`bq*Bw<F`k#*mXRF-po-8kF_OS02dv8nTW^l$?Z!QBq!_p_?_U(NJgkKGB0L-i{<E zCs#@SxpU_(Vo`TF(QY{N9?jv*k$ERF=Osc;XkwI<0vh<XW+eoOI1dr~A>nQO(rfM7 zwR<i|P-zoiF@|tEWZjI<$Y$l+sNsL=)Tyns`(RjdLO1fj0}sqbnWr?F5Fm6v3f;4B z-@Xc-d(LZiLg4u-DJLhV2Nv}kI_E0QP6%huo}Ia5$&!Vd^hGFQWJ<=Y-^Q#{G%FM! z<Wq*oFPO@U8sh-nAXY^0Pa^s-7H~;JZaA<SKLfGXEnmL;fM$6jkQg08W%N=v%?btX z*$g2MEnd9%L(OVdOr1J4iy^iQx$f4iP(ZE&+;dk@M+G`IDw>f9Dbg$4^Cb67Z7EU& zQa5c(z;FE${}p;$f_t*DxCxr{gs;Vjwa{oq*dZh{O=V?e@3m{!?$NAP!N`##`($Qj zz8rR;N$1t$FR${m8sCb>jT^TyJw1J}CVe5KoIH85&$@N%Uel~lm^N+NMfk5wl!|Ot z(VM4_9XocsxVX3`QXnLT4<CM4mo8m?8+O*JuC88NSXek(lMZ-RGI{c3@%(?pPlVb> zsjaPjhUb%i)TA$F%$RXoMn=XIdhI(n=XA2#5zMfbVNs8h{G5(`;mDCAOK_GI4L|u~ z{P^+m7%)YXzMuoDY5S)9{QM6zD+Zp#Oq@9J3C82UXwnz>l#eKz>t~t|Km2eeWd_ot zKZcMiS<hU++C+bCfaDq2aR_>qsrGS(>3=O;xbO?jc0ebJ-LxYM#6UpKJV^hECS%}A zj82LBm<~H@mM6lD*IBb>ji4u=p@*(8gm{r)AbECKxnf1-4A)^#KmGJOTFe3UiytyF z11A=A{0tp$ag*KkW-9((7*YiAve=81yu7@2vuDqq->Fll^(c3R<QXfGs|hF^3GsPY zNz88$do@n>k8|hFeJE0i3q%$aUZqK2pko*6y;qYla3zK*P&Tat!8+nJLt}v^9q8J% z>vn#hWfV0bUSSj5^6Y%MPty3kg>&Z2$=7Dw@RV4x#(hy!Q*)0-W8h_O*Xh+4^h}xC zDw36Q(xZ<)x(0LZD`}%_yy7P9mv8b6Od4_~L(V+SjzK{|!MpgHP}`i;b)!aO;2?%) z5}AcUEw!1-S8Fr|nEPnVezdKaTM*dkcrr|${hM6|;vb(mb7oGYqByVwSfkM(bV29# z8jr$3OphKtvT>Aw{sn8P!f$NRWE2Ai3`k?_t~Scp!^S9ND{9gb4D37!UiWiNMplOm z0!UlWNI#`XPskbyoqK9B3I{PLH9X8Bi6Q)ZH>caPXV0I@)hJ-cCD=;F2O-fAY2V`) zu*@uGJ;xGXo$#xg^n}b_kmGQ`4q}**uGOe7n4%raepa!av7&xPQ4!pZhYeCb5lcgs ziGBO_b?Ac9|D#b~(8=938ik`W8vPrM`T_##G#UkhWO9E`TWAxELMFK7+vNpz+AbF# z_&vK<uU;2NZnA*oAJeEWs3S|GQ8<X9k&86y33)DJO5du<C>YaO5Z2C18IvzCaXZ-M z;_nFvmoua~MJl$yRJ;{gzjR(6;QmaFM&Tev6w|0Dm<mr-R8%;&6zCz@VT%#kz|J#D zo3efc*k$c<ae49p>X=y$9z6IVvdW==AUK`}9mF&RkeVDXwDA=Jqf}6^@0Z|KCO$Mb z+=vNWG4hbsJ}WEhN=-&V?|kMasHEeasU6HcUiaF;?OwSuWX|X<QFQO#JquY=HR%V3 zzjPz!q+UHCQyl9D$5NX7KiR^XxCDV6H!yjoyaapNw#(QdF8?@X9m?^Hb}upoE<LdH z6B>=eK@3jpe>Lh01aUY!@}z-}JcN%1QP>2x-wh=0W@(adUkMdh>*sn~6x`cIqrO1r zgBp#(L5%deMty<h-m1|k^7HeLlmCVh+AG*_`(2fd#O*Q$CT#+3ZsnQHvC1XCw?=)z zIw+VJW|nG=`hthJyJYV}lTol(`gbF+QP2+Y`KGKL4C3?M?jwHo*s)_z>Mc=-Y^>sM z*Q6&PPnI5=H5rA27}g?NWII8Vo`9H+2sKcXQLJ3K@^#2rU@K&TAU@Al)`a*nCfG1} zEc^3HN=hPs@7h3y?;~TN<As!yAAIn^pEVhUgP8L2^8GaSu_k@N0xD0VF+ix<DEN-8 zlvi*=JW&#)Y<!yD-h`~r>uoW>=Zw;55RR3Vl{to?gBbk_HPoRSO!bcO<HtLmt%{0@ z&d?Kgqd>#70^${m4~8hWVD7gAq#W40ckgi8ind`O|C};S-=s-jFeB~LXbc?02vGQU z8uf*m>LU%QSQHl*AE~UY{2EHXYm_rUm}n5l?_o)jz-+T)>C&Y=ckSBc`bQhH%wssz zz!kgz0)MN~7`PIHz6H3MT21<bvJbd9VF6?R806eS@9wn~jwitn@#U$jfZ4B~{_KD{ z-LabV1;g+Jv#{$>bR|aiG7w;yCVe4`J;rmH4>@1pkZ(e%P(DY3Awl8>VqN5IVp<+Q zVZsF0MVrWgEPKK%>IBsPtk*`qD=`8raXwZPxC17@9J|%l*3QwSD{#m|jIwsHN$~rP zjyh?aJw!XqOh~~379vZiqqH(2Seh)?qywJB$Uk&6^ao7_0`r|YYSgH2Y0?3F&H<F0 zEK0h;Cd32-iDL<U)~;RaIlTFOIx>X%J6QlRJG}md6nGLN=-j#UB$RP@eK*K<XhufH z%cDn+zD2VfU<O%^<J|6NC2oOxo?f$NjpvGd#E21n89t9`G7wnr+4S^u&;O<bzQoA* zgV|6gkOU0B6do?Od-)pyiprkwSy9*!WRqk=%bcQfex+Fs$T^wJ%*@}2uhXnRK=jYN ze2YO~WhnX-W_nbUfxw(Eq5KXna<Ce)7>7`X7@H&|Z41NjJuMakX@lN>LzfMqfNY2H zLyI-(i9lk6Sr%*e&};6_!38vY4Fb&2qyyCT&qjF@Y}k3U?RkO%XZ&w;gk!JQ3*r_C z-wCo7K`8T+mlx=nUuZHEm|h?BvKsuw8Z4pCCPu9zn%2BM>({UUNN<aQ)Xng5eI)S$ zr2Mq7urSo_LFjRrmX@{vD+qlkh@QNZSxpWRdf^4V#=s_q;Q4Q&SQ2eG_Jq8E?0*O| zgbYFxBj!iNBWPg1W(5MPdCbddu!ik%B*cd^gx9rr45UtG!I_#21Tw#c><PV<5}Fu+ zl5;l=xjMsm;1km*J6)48K<WL2Q%XWKSg2+8a6L)IOnJZ5tw~?d{$6Arm<TC?goqI+ zIsItR^YD%h#&s`^#sG`?*e1rnr1||+b#?V&&5i-YxYijDWZZ+S{SzfckT5X<CFdS| zNU3Id0wu53Xbd3iQ!ddBF++$IYD-H?T{q15&G8RKJdkZYG7fDFDS}23BajAhe$#M* z{~^yEn(c<m^csVmJ9pNiU}T|Z2Ls}+vysB{j2(njVJSaBwts3YDS}25Banu9pqs<A zdA--rVm8ZgbHGQB9zBQh;$Y(mi3A+C-@g?ZdW``xybdAPuUN5SUQ<XBG>I4kJ*6!} zYaYVItde!9erH3<fe0h^hLs_StLJHN%E$a2OPe2KwT~ggkfsu1&@99VL?AgD%52da z|1nF1m3(Fv(e(Ecj|a*}EJhY~5)3gK_d5n3IV?>Z2c<3A?6YjyvY$5>DS}WNI*ox0 zA4nN;=+L3m&6_vR*X$UK969oRdcTJzV*oKlB_$=}G#P{b{rk`A)~#Dza&mHMbCO}B z#fS+5A%=@5F+2{q64M%JwxQHW_;t*e9=Q$B*2Ki@<QpF&0mOVEq_xqe#MneA8Ic)E zLz~Lh*2JXPqzFu!naOpn(pqRUVwh3HzeIhEA;b!Qoy7F%(?|S^r}O=*ASO3Aw<AH0 zrhi?t<R6GsKR?26F`H1_dzseJDj(Nqv2~0r@T#k;e?`ydk`xjmZ?UDPr)SHeZ20iu zax7ogF_BYG;&9_|==tZLkG!NRbyZbWy-OMKN%F+K4`N5Q)Z$LiA{HZx@a*ygBn(1< zO9`EccOazJ*Vjk(X<5p55=Dg&LyGzq6Qa{Kj(8wdbdox_W)SY+abV5;_un7tEt$rk z*(^q%pzo}ysp-#y;{cv=T@O;D<TM^S7Y`XS<jRzkl()$8@+C(<`Q(#sWo2cjse26R zqK51*0m<?z^Ph3#yJ>GX?S4kK>pKpKA|Ybr1yU4y5P{^hfjQkNzMu<Fwpl?(FL?Ss zjsiD};6sNFy&6lA_Yvg%gN6dJ_>A&BN=r+tg>(pboi<$ec5GmYXY1<f<hUHwCUwB_ zK0&Mk$XmgKrVQOs2t|wthQC<AQ*8gRd#&+6$x2B5=E{{TBZrb8<{Jq63ek>|mf;fz zIih;a#*G^z?*mMlG%1_QYY>~zXA2A%+I57N7><vGv8Yf!2FLdb6^=-h6xnj%-uLP4 zr%>nyLy96DG8_v$u^tWbo4k4)fxJm4)`qn{QdwE~2A1Jq(hnj#I*de?f6y#ngdzr0 z4E6G*fsB1D?AL^wi;9Y}SFBhuH@=rpJTgl)q`rh9ija&CiuvnR>d4Q}pI=;Dd@I)0 z1AkVGC5Ad>DxhpB!|@qS`XUrDRQ4if9jb@ukpmFfqp+}W@aom8_iDBSY7Ks$Mi7hf z{6kH44GRhi#$rV|v>EDX9QC>W$K{5=Z#Ia3f$8}u>$=--0|9qHqVLZ9ph1JK%E`&; zhsBH_`27tcop$QlYp+es&dxqrUS96{NrT<HcUQ8C-hsnCOMQ1>NuHg}to3zdq<Eig za-U|q!q@rgY*2)<UVFAtP|ov1S%U`;M&_ic$ovG?OJt}d3hpT<{DAQ5O`A5kRxokm z#GfHU;5iv_#MJX7*5>)eMF%lL20r3G6#X_g-i^}=T!K^nG!5mrO^3`r10Z85(Z~zw z@!+?}mHQ*t5O|AWJ&D=M@%OD-_nXLlsb={Cf@&%M79I8{;?LS&UW^h0+dWZm5taU3 z7&2@y^SpzTb-cekY}hcVNB(9F^{oXjxRCb>@xB{2Y;e3n#A>*(Q>RX$PHozdS&rH6 zcJqms=y-&b1uV7uB}$6I^+Oc&tj3xKAtcVClDsSo5)=BoiUIy7sbj~E>%{6L9U(>% zjLpB7cw>N--C!!Tfw_MHIS)yXdxge;_0bgSc75TD-#iaU1_2J_(=ZW#YAohXW*y(< zydT8Mm=%7Y$rzx(3s~cg^sv0);ZP=sEGsRKAceGZ3C{QJhOAiR<>mECOG^uNRNjUR zBizAB!f||S<X!DZt5c>-xsnEdiSU=A<c(NB;G4AZkjf$88dkTCH#r0Y2M$bu`wZF` zgR)Zy-_V~tA;X36--Kr=zYQ5Hw{6?zI)=nzaiH%Zf8-G(4CJ{A=Uc#aenmxv$X(Av zYvdI|hb`-nxRX`uP#PITaJ-%B1tEuq@^OAY@>|Oou%_wF*zS(v-B4T>bLSa)mBbHm zeGkiUeF4IT$V)HFQebu>vU1PI44vMgr)wK>R1_<aFC8Yd%?LS_2>Lh{QWA!s#=vwS z3d-6gp-<7#f#>LuEI-+Ni4h=x>t0A0AoLM>egi};VU6H=Y%EcX88hYv2%n5q4T2EI z$11`AUvh}$g)f<5i1{X-{#736JeS%*kRHN6i1RF`o;PujyHQNe)Oc<x8H<oO_ufVw za^gIPd%Muv34Osa9x~)*2sg=MaLhAM=2=ZPD;TD-11E34%F7()^pq%#F?6^X%Lsk2 zIZ@z4H}Rl5M3WBq5+l9FFc2RT`jGMe7sy@_mh)qDTk2*xp2I^NFTMHFUm__2MnMv~ z{g7em`i7n-F_`%z%(=-QH2x|>rfjUn^&?=O#E6N>*AT^VYSv#2bjww~3*4L8$#tz1 z=zQgkRGgq34R!rUmJo+I%HI|;>zMB=$b)D)Gsxvlok9G7|EprP`zC9qkC^3T<H<a7 zq_SvCBV0zvMviPF2Uhawi4!Nj|Kf`;ZjY4oKp-&!al$n4T|D^@5uhva8m=$XU@2pD zqu-Rvkjz7vQIx$E;Tk>V+n~N9SoAz(*y1<k8i*Wp-YkhD$AU2Tgd)a9Zf@=sJWqsr zVcmxOYY2(58+e;$B@EeEV!^n8I?ydxxuzgtVwwV-{wG1u<5*8ZZDV=$44JE!ELqZG zA5XT?s>B#1`e9=6V_3`et5&TF^>RmZpjpOzq9`dT*~OT5e1XFYl-<>uqzKx?VgxeV zF*N=aM=o0hvPou6H^_d79DY8HbCqzC>2;CUwAwPb;DY}HUe|tZ@4cyp00000NkvXX Hu0mjftwamV literal 0 HcmV?d00001 diff --git a/website/static/img/deploy.svg b/website/static/img/deploy.svg new file mode 100644 index 0000000000..097f164ef5 --- /dev/null +++ b/website/static/img/deploy.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="1em" height="1em" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M7.288 23.292l7.997-7.997l1.415 1.414l-7.998 7.997z" fill="#626262"/><path d="M17 30a1 1 0 0 1-.37-.07a1 1 0 0 1-.62-.79l-1-7l2-.28l.75 5.27L21 24.52V17a1 1 0 0 1 .29-.71l4.07-4.07A8.94 8.94 0 0 0 28 5.86V4h-1.86a8.94 8.94 0 0 0-6.36 2.64l-4.07 4.07A1 1 0 0 1 15 11H7.48l-2.61 3.26l5.27.75l-.28 2l-7-1a1 1 0 0 1-.79-.62a1 1 0 0 1 .15-1l4-5A1 1 0 0 1 7 9h7.59l3.77-3.78A10.92 10.92 0 0 1 26.14 2H28a2 2 0 0 1 2 2v1.86a10.92 10.92 0 0 1-3.22 7.78L23 17.41V25a1 1 0 0 1-.38.78l-5 4A1 1 0 0 1 17 30z" fill="#626262"/></svg> \ No newline at end of file diff --git a/website/static/img/easy.svg b/website/static/img/easy.svg new file mode 100644 index 0000000000..1e32c1c49d --- /dev/null +++ b/website/static/img/easy.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="1em" height="1em" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M26 21v-1a1 1 0 0 1 2 0v10h2V20a3.003 3.003 0 0 0-3-3a2.964 2.964 0 0 0-1.47.401a2.954 2.954 0 0 0-4-1A2.993 2.993 0 0 0 19 15a2.96 2.96 0 0 0-1 .185V10a3 3 0 0 0-6 0v11.105l-2.235-1.53v.001a2.999 2.999 0 0 0-3.882 4.55L12.323 30l1.347-1.478l-6.378-5.818A.99.99 0 0 1 7 22a1 1 0 0 1 1.6-.8l5.4 3.695V10a1 1 0 0 1 2 0v11h2v-3a1 1 0 0 1 2 0v3h2v-2a1 1 0 0 1 2 0v2z" fill="#626262"/><path d="M28 12h-6v-2h6V4H4v6h4v2H4a2.002 2.002 0 0 1-2-2V4a2.002 2.002 0 0 1 2-2h24a2.002 2.002 0 0 1 2 2v6a2.002 2.002 0 0 1-2 2z" fill="#626262"/></svg> \ No newline at end of file diff --git a/website/static/img/extend.svg b/website/static/img/extend.svg new file mode 100644 index 0000000000..3c4cbcf5ee --- /dev/null +++ b/website/static/img/extend.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="1em" height="1em" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M26 22v3.586l-9-9V5.828l2.586 2.586L21 7l-5-5l-5 5l1.414 1.414L15 5.828v10.758l-9 9V22H4v7h7v-2H7.414L16 18.414L24.586 27H21v2h7v-7h-2z" fill="#626262"/></svg> \ No newline at end of file diff --git a/website/static/img/favicon.ico b/website/static/img/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..21f49bc43b08f557f085d0ba170f388e5956373f GIT binary patch literal 15086 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 literal 0 HcmV?d00001 diff --git a/website/static/img/kubernetes.svg b/website/static/img/kubernetes.svg new file mode 100644 index 0000000000..bd6b1464fe --- /dev/null +++ b/website/static/img/kubernetes.svg @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="722.8457" + height="701.96637" + id="svg2" + version="1.1" + inkscape:version="0.48.4 r9939" + sodipodi:docname="logo.svg" + inkscape:export-filename="/home/thockin/src/kubernetes/new.png" + inkscape:export-xdpi="460.95001" + inkscape:export-ydpi="460.95001"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="16.190509" + inkscape:cx="277.56851" + inkscape:cy="157.54494" + inkscape:document-units="px" + inkscape:current-layer="g3052" + showgrid="false" + inkscape:window-width="1519" + inkscape:window-height="822" + inkscape:window-x="51" + inkscape:window-y="25" + inkscape:window-maximized="0" + inkscape:snap-global="false" + fit-margin-top="10" + fit-margin-left="10" + fit-margin-right="10" + fit-margin-bottom="10" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-6.3260942,-174.7524)"> + <g + id="g3052"> + <path + style="fill:#326ce5;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" + d="m 365.3125,184.8125 a 46.724621,46.342246 0 0 0 -17.90625,4.53125 l -244.34375,116.75 a 46.724621,46.342246 0 0 0 -25.28125,31.4375 L 17.5,599.78125 A 46.724621,46.342246 0 0 0 23.84375,635.3125 46.724621,46.342246 0 0 0 26.5,639 l 169.125,210.28125 a 46.724621,46.342246 0 0 0 36.53125,17.4375 L 503.375,866.65625 A 46.724621,46.342246 0 0 0 539.90625,849.25 L 708.96875,638.9375 A 46.724621,46.342246 0 0 0 718,599.71875 l -60.375,-262.25 a 46.724621,46.342246 0 0 0 -25.28125,-31.4375 l -244.375,-116.6875 A 46.724621,46.342246 0 0 0 365.3125,184.8125 z" + id="path3055" + inkscape:connector-curvature="0" + inkscape:export-filename="new.png" + inkscape:export-xdpi="250.55" + inkscape:export-ydpi="250.55" /> + <path + inkscape:connector-curvature="0" + id="path3059" + d="m 367.73366,274.05962 c -8.07696,8.2e-4 -14.62596,7.27591 -14.625,16.25 1e-5,0.13773 0.0282,0.26934 0.0312,0.40625 -0.0119,1.21936 -0.0708,2.68836 -0.0312,3.75 0.19262,5.176 1.3209,9.13749 2,13.90625 1.23028,10.20666 2.26117,18.66736 1.625,26.53125 -0.61869,2.9654 -2.80288,5.67741 -4.75,7.5625 l -0.34375,6.1875 c -8.77682,0.72717 -17.61235,2.05874 -26.4375,4.0625 -37.97461,8.62218 -70.67008,28.18307 -95.5625,54.59375 -1.61522,-1.10193 -4.44103,-3.12914 -5.2813,-3.75 -2.61117,0.35262 -5.25021,1.15829 -8.6875,-0.84375 -6.54491,-4.40563 -12.50587,-10.48693 -19.71875,-17.8125 -3.30498,-3.50419 -5.69832,-6.84101 -9.625,-10.21875 -0.89172,-0.76707 -2.25258,-1.80455 -3.25,-2.59375 -3.06988,-2.44757 -6.6907,-3.72402 -10.1875,-3.84375 -4.49589,-0.15394 -8.82394,1.60385 -11.65625,5.15625 -5.03521,6.31538 -3.42312,15.96805 3.59375,21.5625 0.0712,0.0567 0.14702,0.10078 0.21875,0.15625 0.96422,0.78162 2.14496,1.78313 3.03125,2.4375 4.16687,3.07655 7.9732,4.65145 12.125,7.09375 8.747,5.40181 15.99837,9.88086 21.75,15.28125 2.24602,2.39417 2.63858,6.61292 2.9375,8.4375 l 4.6875,4.1875 c -25.09342,37.76368 -36.70686,84.40946 -29.8437,131.9375 l -6.125,1.78125 c -1.6143,2.08461 -3.89541,5.36474 -6.2813,6.34375 -7.52513,2.37021 -15.99424,3.24059 -26.21875,4.3125 -4.80031,0.39915 -8.94218,0.16095 -14.03125,1.125 -1.12008,0.21218 -2.68072,0.61877 -3.90625,0.90625 -0.0426,0.009 -0.0824,0.0216 -0.125,0.0312 -0.0668,0.0155 -0.15456,0.0479 -0.21875,0.0625 -8.62014,2.08279 -14.15774,10.006 -12.375,17.8125 1.78316,7.80833 10.20314,12.55677 18.875,10.6875 0.0626,-0.0143 0.1535,-0.0167 0.21875,-0.0312 0.0979,-0.0224 0.18409,-0.0699 0.28125,-0.0937 1.20885,-0.26536 2.72377,-0.5606 3.78125,-0.84375 5.00334,-1.33963 8.62694,-3.30796 13.125,-5.03125 9.67694,-3.47077 17.69173,-6.37022 25.5,-7.5 3.26118,-0.25542 6.69711,2.01216 8.40625,2.96875 l 6.375,-1.09375 c 14.67018,45.48282 45.41416,82.24502 84.34375,105.3125 l -2.65625,6.375 c 0.95742,2.47542 2.01341,5.8247 1.30022,8.26932 -2.83868,7.3612 -7.70097,15.13097 -13.23772,23.79318 -2.68085,4.00192 -5.42453,7.10761 -7.84375,11.6875 -0.5789,1.09589 -1.31618,2.77932 -1.875,3.9375 -3.75884,8.04236 -1.00164,17.3052 6.21875,20.78125 7.26575,3.49788 16.28447,-0.19134 20.1875,-8.25 0.006,-0.0114 0.0257,-0.0198 0.0312,-0.0312 0.004,-0.009 -0.004,-0.0225 0,-0.0312 0.55593,-1.14255 1.34353,-2.64437 1.8125,-3.71875 2.07213,-4.74702 2.76161,-8.81506 4.21875,-13.40625 3.86962,-9.72014 5.99567,-19.91903 11.32258,-26.27411 1.45868,-1.74023 3.83681,-2.4095 6.30242,-3.06964 l 3.3125,-6 c 33.93824,13.0268 71.92666,16.52246 109.875,7.90625 8.65697,-1.96557 17.01444,-4.50945 25.09375,-7.5625 0.93098,1.65133 2.66113,4.8257 3.125,5.625 2.50559,0.81518 5.24044,1.23614 7.46875,4.53125 3.98539,6.80898 6.7109,14.86416 10.03125,24.59375 1.45738,4.59111 2.17762,8.65933 4.25,13.40625 0.47234,1.08195 1.256,2.60486 1.8125,3.75 3.89482,8.08484 12.94212,11.78667 20.21875,8.28125 7.2195,-3.4779 9.97974,-12.7399 6.21875,-20.78125 -0.55889,-1.15814 -1.3273,-2.84164 -1.90625,-3.9375 -2.41946,-4.57976 -5.1627,-7.65448 -7.84375,-11.65625 -5.53721,-8.66192 -10.12968,-15.8577 -12.96875,-23.21875 -1.18711,-3.79657 0.20028,-6.15774 1.125,-8.625 -0.55378,-0.63477 -1.73881,-4.22009 -2.4375,-5.90625 40.4574,-23.88816 70.29856,-62.02129 84.3125,-106.0625 1.8924,0.29742 5.18154,0.87936 6.25,1.09375 2.19954,-1.4507 4.22194,-3.34352 8.1875,-3.03125 7.80832,1.12937 15.82288,4.02973 25.5,7.5 4.49815,1.72306 8.1216,3.72313 13.125,5.0625 1.05749,0.28309 2.57238,0.5472 3.78125,0.8125 0.0972,0.0238 0.1833,0.0714 0.28125,0.0937 0.0653,0.0146 0.15615,0.0169 0.21875,0.0312 8.67236,1.86695 17.09384,-2.87871 18.875,-10.6875 1.78074,-7.80696 -3.7543,-15.73201 -12.375,-17.8125 -1.25393,-0.28513 -3.03225,-0.76938 -4.25,-1 -5.08912,-0.96378 -9.23092,-0.7261 -14.03125,-1.125 -10.22456,-1.07138 -18.6935,-1.94269 -26.21875,-4.3125 -3.06826,-1.19028 -5.25103,-4.84124 -6.31255,-6.34375 l -5.90625,-1.71875 c 3.06226,-22.15442 2.23655,-45.21134 -3.0625,-68.28125 -5.34839,-23.28471 -14.80037,-44.58084 -27.40625,-63.34375 1.51505,-1.37729 4.37619,-3.91091 5.1875,-4.65625 0.23716,-2.62417 0.0334,-5.37553 2.75,-8.28125 5.75134,-5.40069 13.00329,-9.87898 21.75,-15.28125 4.15167,-2.44252 7.98954,-4.01698 12.15625,-7.09375 0.94225,-0.69576 2.2289,-1.79759 3.21875,-2.59375 7.01538,-5.59633 8.63058,-15.24842 3.59375,-21.5625 -5.03683,-6.31408 -14.79712,-6.90883 -21.8125,-1.3125 -0.99856,0.79085 -2.35353,1.82252 -3.25,2.59375 -3.9265,3.37796 -6.35145,6.71439 -9.65625,10.21875 -7.21249,7.32595 -13.17407,13.43777 -19.71875,17.84375 -2.83601,1.65106 -6.98996,1.07978 -8.87505,0.96875 l -5.5625,3.96875 c -31.7188,-33.26057 -74.90466,-54.52546 -121.40605,-58.6563 -0.13006,-1.94872 -0.30045,-5.47117 -0.34375,-6.53125 -1.90371,-1.82165 -4.20342,-3.37686 -4.78125,-7.3125 -0.63617,-7.86389 0.42597,-16.32459 1.65625,-26.53125 0.6791,-4.76876 1.80738,-8.73025 2,-13.90625 0.0438,-1.17663 -0.0265,-2.88401 -0.0312,-4.15625 -9.6e-4,-8.97409 -6.54804,-16.25082 -14.625,-16.25 z m -18.3125,113.4375 -4.34375,76.71875 -0.3125,0.15625 c -0.29134,6.86335 -5.93996,12.34375 -12.875,12.34375 -2.84081,0 -5.46294,-0.91229 -7.59375,-2.46875 l -0.125,0.0625 -62.90625,-44.59375 c 19.33365,-19.01115 44.06291,-33.06039 72.5625,-39.53125 5.20599,-1.18203 10.40966,-2.0591 15.59375,-2.6875 z m 36.65625,0 c 33.27347,4.09232 64.04501,19.15882 87.625,42.25 l -62.5,44.3125 -0.21875,-0.0937 c -5.54745,4.05169 -13.36343,3.04639 -17.6875,-2.375 -1.77132,-2.22096 -2.70072,-4.83239 -2.8125,-7.46875 l -0.0625,-0.0312 z m -147.625,70.875 57.4375,51.375 -0.0625,0.3125 c 5.18437,4.50697 5.94888,12.32794 1.625,17.75 -1.7712,2.22105 -4.14208,3.71074 -6.6875,4.40625 l -0.0625,0.25 -73.625,21.25 c -3.74728,-34.26517 4.32855,-67.57364 21.375,-95.34375 z m 258.15625,0.0312 c 8.5341,13.83256 14.99655,29.28214 18.84375,46.03125 3.80106,16.54828 4.75499,33.06697 3.1875,49.03125 l -74,-21.3125 -0.0625,-0.3125 c -6.6265,-1.81104 -10.69893,-8.55162 -9.15625,-15.3125 0.63203,-2.76962 2.10222,-5.11264 4.09375,-6.84375 l -0.0312,-0.15625 57.125,-51.125 z m -140.65625,55.3125 23.53125,0 14.625,18.28125 -5.25,22.8125 -21.125,10.15625 -21.1875,-10.1875 -5.25,-22.8125 z m 75.4375,62.5625 c 0.99997,-0.0505 1.99558,0.0396 2.96875,0.21875 l 0.125,-0.15625 76.15625,12.875 c -11.1455,31.3131 -32.47281,58.44018 -60.96875,76.59375 l -29.5625,-71.40625 0.0937,-0.125 c -2.71561,-6.30999 0.002,-13.70956 6.25,-16.71875 1.59965,-0.77041 3.27089,-1.19701 4.9375,-1.28125 z m -127.90625,0.3125 c 5.81174,0.0815 11.02462,4.11525 12.375,10.03125 0.63219,2.76958 0.3245,5.51375 -0.71875,7.9375 l 0.21875,0.28125 -29.25,70.6875 c -27.34716,-17.5486 -49.12927,-43.82403 -60.78125,-76.06245 l 75.5,-12.8125 0.125,0.15625 c 0.84451,-0.15541 1.701,-0.2304 2.53125,-0.21875 z m 63.78125,30.9688 c 2.02445,-0.0744 4.07865,0.34098 6.03125,1.28125 2.55951,1.23253 4.53673,3.17319 5.78125,5.5 l 0.28125,0 37.21875,67.25 c -4.83029,1.61923 -9.79609,3.00308 -14.875,4.15625 -28.46453,6.4629 -56.83862,4.50467 -82.53125,-4.25 l 37.125,-67.125 0.0625,0 c 2.22767,-4.16441 6.45247,-6.64887 10.90625,-6.8125 z" + style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.25;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans" + sodipodi:nodetypes="ccccccccsccccscssccsccccccccscccsccccccccccccccscccscsccsccccscscsccccccccscccscsccccsccccscscscccccccccccccccscccsccccccccccccscccccscccccccccccccccccccccccscccscccccccccscccscccc" + inkscape:export-filename="./path3059.png" + inkscape:export-xdpi="250.55" + inkscape:export-ydpi="250.55" /> + </g> + </g> +</svg> \ No newline at end of file diff --git a/website/static/img/logo.png b/website/static/img/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..03d4990b89312c6d14d0efbcd5810d81678e45d7 GIT binary patch literal 93628 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 TTf_I|@o~l4;H{rpy8rjTjBk7) literal 0 HcmV?d00001 diff --git a/website/static/img/logo.svg b/website/static/img/logo.svg new file mode 100644 index 0000000000..9db6d0d066 --- /dev/null +++ b/website/static/img/logo.svg @@ -0,0 +1 @@ +<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="#FFF" d="M99 52h84v34H99z"/><path d="M23 163c-7.398 0-13.843-4.027-17.303-10A19.886 19.886 0 0 0 3 163c0 11.046 8.954 20 20 20h20v-20H23z" fill="#3ECC5F"/><path d="M112.98 57.376L183 53V43c0-11.046-8.954-20-20-20H73l-2.5-4.33c-1.112-1.925-3.889-1.925-5 0L63 23l-2.5-4.33c-1.111-1.925-3.889-1.925-5 0L53 23l-2.5-4.33c-1.111-1.925-3.889-1.925-5 0L43 23c-.022 0-.042.003-.065.003l-4.142-4.141c-1.57-1.571-4.252-.853-4.828 1.294l-1.369 5.104-5.192-1.392c-2.148-.575-4.111 1.389-3.535 3.536l1.39 5.193-5.102 1.367c-2.148.576-2.867 3.259-1.296 4.83l4.142 4.142c0 .021-.003.042-.003.064l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 53l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 63l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 73l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 83l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 93l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 103l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 113l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 123l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 133l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 143l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 153l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 163c0 11.046 8.954 20 20 20h120c11.046 0 20-8.954 20-20V83l-70.02-4.376A10.645 10.645 0 0 1 103 68c0-5.621 4.37-10.273 9.98-10.624" fill="#3ECC5F"/><path fill="#3ECC5F" d="M143 183h30v-40h-30z"/><path d="M193 158c-.219 0-.428.037-.639.064-.038-.15-.074-.301-.116-.451A5 5 0 0 0 190.32 148a4.96 4.96 0 0 0-3.016 1.036 26.531 26.531 0 0 0-.335-.336 4.955 4.955 0 0 0 1.011-2.987 5 5 0 0 0-9.599-1.959c-.148-.042-.297-.077-.445-.115.027-.211.064-.42.064-.639a5 5 0 0 0-5-5 5 5 0 0 0-5 5c0 .219.037.428.064.639-.148.038-.297.073-.445.115a4.998 4.998 0 0 0-9.599 1.959c0 1.125.384 2.151 1.011 2.987-3.717 3.632-6.031 8.693-6.031 14.3 0 11.046 8.954 20 20 20 9.339 0 17.16-6.41 19.361-15.064.211.027.42.064.639.064a5 5 0 0 0 5-5 5 5 0 0 0-5-5" fill="#44D860"/><path fill="#3ECC5F" d="M153 123h30v-20h-30z"/><path d="M193 115.5a2.5 2.5 0 1 0 0-5c-.109 0-.214.019-.319.032-.02-.075-.037-.15-.058-.225a2.501 2.501 0 0 0-.963-4.807c-.569 0-1.088.197-1.508.518a6.653 6.653 0 0 0-.168-.168c.314-.417.506-.931.506-1.494a2.5 2.5 0 0 0-4.8-.979A9.987 9.987 0 0 0 183 103c-5.522 0-10 4.478-10 10s4.478 10 10 10c.934 0 1.833-.138 2.69-.377a2.5 2.5 0 0 0 4.8-.979c0-.563-.192-1.077-.506-1.494.057-.055.113-.111.168-.168.42.321.939.518 1.508.518a2.5 2.5 0 0 0 .963-4.807c.021-.074.038-.15.058-.225.105.013.21.032.319.032" fill="#44D860"/><path d="M63 55.5a2.5 2.5 0 0 1-2.5-2.5c0-4.136-3.364-7.5-7.5-7.5s-7.5 3.364-7.5 7.5a2.5 2.5 0 1 1-5 0c0-6.893 5.607-12.5 12.5-12.5S65.5 46.107 65.5 53a2.5 2.5 0 0 1-2.5 2.5" fill="#000"/><path d="M103 183h60c11.046 0 20-8.954 20-20V93h-60c-11.046 0-20 8.954-20 20v70z" fill="#FFFF50"/><path d="M168.02 124h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2m0 20h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2m0 20h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2m0-49.814h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2m0 19.814h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2m0 20h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2M183 61.611c-.012 0-.022-.006-.034-.005-3.09.105-4.552 3.196-5.842 5.923-1.346 2.85-2.387 4.703-4.093 4.647-1.889-.068-2.969-2.202-4.113-4.46-1.314-2.594-2.814-5.536-5.963-5.426-3.046.104-4.513 2.794-5.807 5.167-1.377 2.528-2.314 4.065-4.121 3.994-1.927-.07-2.951-1.805-4.136-3.813-1.321-2.236-2.848-4.75-5.936-4.664-2.994.103-4.465 2.385-5.763 4.4-1.373 2.13-2.335 3.428-4.165 3.351-1.973-.07-2.992-1.51-4.171-3.177-1.324-1.873-2.816-3.993-5.895-3.89-2.928.1-4.399 1.97-5.696 3.618-1.232 1.564-2.194 2.802-4.229 2.724a1 1 0 0 0-.072 2c3.017.101 4.545-1.8 5.872-3.487 1.177-1.496 2.193-2.787 4.193-2.855 1.926-.082 2.829 1.115 4.195 3.045 1.297 1.834 2.769 3.914 5.731 4.021 3.103.104 4.596-2.215 5.918-4.267 1.182-1.834 2.202-3.417 4.15-3.484 1.793-.067 2.769 1.35 4.145 3.681 1.297 2.197 2.766 4.686 5.787 4.796 3.125.108 4.634-2.62 5.949-5.035 1.139-2.088 2.214-4.06 4.119-4.126 1.793-.042 2.728 1.595 4.111 4.33 1.292 2.553 2.757 5.445 5.825 5.556l.169.003c3.064 0 4.518-3.075 5.805-5.794 1.139-2.41 2.217-4.68 4.067-4.773v-2z" fill="#000"/><path fill="#3ECC5F" d="M83 183h40v-40H83z"/><path d="M143 158c-.219 0-.428.037-.639.064-.038-.15-.074-.301-.116-.451A5 5 0 0 0 140.32 148a4.96 4.96 0 0 0-3.016 1.036 26.531 26.531 0 0 0-.335-.336 4.955 4.955 0 0 0 1.011-2.987 5 5 0 0 0-9.599-1.959c-.148-.042-.297-.077-.445-.115.027-.211.064-.42.064-.639a5 5 0 0 0-5-5 5 5 0 0 0-5 5c0 .219.037.428.064.639-.148.038-.297.073-.445.115a4.998 4.998 0 0 0-9.599 1.959c0 1.125.384 2.151 1.011 2.987-3.717 3.632-6.031 8.693-6.031 14.3 0 11.046 8.954 20 20 20 9.339 0 17.16-6.41 19.361-15.064.211.027.42.064.639.064a5 5 0 0 0 5-5 5 5 0 0 0-5-5" fill="#44D860"/><path fill="#3ECC5F" d="M83 123h40v-20H83z"/><path d="M133 115.5a2.5 2.5 0 1 0 0-5c-.109 0-.214.019-.319.032-.02-.075-.037-.15-.058-.225a2.501 2.501 0 0 0-.963-4.807c-.569 0-1.088.197-1.508.518a6.653 6.653 0 0 0-.168-.168c.314-.417.506-.931.506-1.494a2.5 2.5 0 0 0-4.8-.979A9.987 9.987 0 0 0 123 103c-5.522 0-10 4.478-10 10s4.478 10 10 10c.934 0 1.833-.138 2.69-.377a2.5 2.5 0 0 0 4.8-.979c0-.563-.192-1.077-.506-1.494.057-.055.113-.111.168-.168.42.321.939.518 1.508.518a2.5 2.5 0 0 0 .963-4.807c.021-.074.038-.15.058-.225.105.013.21.032.319.032" fill="#44D860"/><path d="M143 41.75c-.16 0-.33-.02-.49-.05a2.52 2.52 0 0 1-.47-.14c-.15-.06-.29-.14-.431-.23-.13-.09-.259-.2-.38-.31-.109-.12-.219-.24-.309-.38s-.17-.28-.231-.43a2.619 2.619 0 0 1-.189-.96c0-.16.02-.33.05-.49.03-.16.08-.31.139-.47.061-.15.141-.29.231-.43.09-.13.2-.26.309-.38.121-.11.25-.22.38-.31.141-.09.281-.17.431-.23.149-.06.31-.11.47-.14.32-.07.65-.07.98 0 .159.03.32.08.47.14.149.06.29.14.43.23.13.09.259.2.38.31.11.12.22.25.31.38.09.14.17.28.23.43.06.16.11.31.14.47.029.16.05.33.05.49 0 .66-.271 1.31-.73 1.77-.121.11-.25.22-.38.31-.14.09-.281.17-.43.23a2.565 2.565 0 0 1-.96.19m20-1.25c-.66 0-1.3-.27-1.771-.73a3.802 3.802 0 0 1-.309-.38c-.09-.14-.17-.28-.231-.43a2.619 2.619 0 0 1-.189-.96c0-.66.27-1.3.729-1.77.121-.11.25-.22.38-.31.141-.09.281-.17.431-.23.149-.06.31-.11.47-.14.32-.07.66-.07.98 0 .159.03.32.08.47.14.149.06.29.14.43.23.13.09.259.2.38.31.459.47.73 1.11.73 1.77 0 .16-.021.33-.05.49-.03.16-.08.32-.14.47-.07.15-.14.29-.23.43-.09.13-.2.26-.31.38-.121.11-.25.22-.38.31-.14.09-.281.17-.43.23a2.565 2.565 0 0 1-.96.19" fill="#000"/></g></svg> \ No newline at end of file diff --git a/website/static/img/multi-cluster.svg b/website/static/img/multi-cluster.svg new file mode 100644 index 0000000000..ad663a1212 --- /dev/null +++ b/website/static/img/multi-cluster.svg @@ -0,0 +1,3 @@ +<svg id="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><defs><style></style></defs> +<path fill="#626262" d="M26,22a3.86,3.86,0,0,0-2,.57l-3.09-3.1a6,6,0,0,0,0-6.94L24,9.43A3.86,3.86,0,0,0,26,10a4,4,0,1,0-4-4,3.86,3.86,0,0,0,.57,2l-3.1,3.09a6,6,0,0,0-6.94,0L9.43,8A3.86,3.86,0,0,0,10,6a4,4,0,1,0-4,4,3.86,3.86,0,0,0,2-.57l3.09,3.1a6,6,0,0,0,0,6.94L8,22.57A3.86,3.86,0,0,0,6,22a4,4,0,1,0,4,4,3.86,3.86,0,0,0-.57-2l3.1-3.09a6,6,0,0,0,6.94,0L22.57,24A3.86,3.86,0,0,0,22,26a4,4,0,1,0,4-4ZM16,20a4,4,0,1,1,4-4A4,4,0,0,1,16,20Z"/> +</svg> \ No newline at end of file diff --git a/website/static/img/open-source.svg b/website/static/img/open-source.svg new file mode 100644 index 0000000000..c40cc14486 --- /dev/null +++ b/website/static/img/open-source.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="1em" height="1em" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32"><path d="M23.5 4h-15l-6.784 9.045L16 29.527l14.284-16.482zm3.5 8h-5.446l-3.75-6H22.5zm-16.698 2l3.754 10.23L5.19 14zm2.13 0h7.136l-3.569 9.721zm.373-2L16 6.887L19.196 12zm8.893 2h5.112l-8.867 10.231zM9.5 6h4.696l-3.75 6H5z" fill="#626262"/></svg> \ No newline at end of file diff --git a/website/static/img/screens/cf-app.png b/website/static/img/screens/cf-app.png new file mode 100644 index 0000000000000000000000000000000000000000..447fdada88c8af307db397593a2ddc8efccba89e GIT binary patch literal 67519 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 b9~%n~kqX>^HtwbUbp@&^X`v+w7J>g4)z<77 literal 0 HcmV?d00001 diff --git a/website/static/img/screens/endpoints.png b/website/static/img/screens/endpoints.png new file mode 100644 index 0000000000000000000000000000000000000000..5abf20fc94f847a8235076af7807fd31d98ff082 GIT binary patch literal 33521 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 RY9QcGSwUUCRL<nl{{n}{@l5~# literal 0 HcmV?d00001 diff --git a/website/static/img/screens/kube-graph.png b/website/static/img/screens/kube-graph.png new file mode 100644 index 0000000000000000000000000000000000000000..5d0ad09bde9eb739d53bb4563b9d8abb7b5f2176 GIT binary patch literal 63442 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 Ls$X!#KIDG^g_rTu literal 0 HcmV?d00001 From 3502fb5c305d68b13e2891e83beb4812e11119fe Mon Sep 17 00:00:00 2001 From: Richard Cox <richard-cox@users.noreply.github.com> Date: Tue, 25 Aug 2020 11:10:32 +0100 Subject: [PATCH 597/648] Update kube pod container component following ux review (#441) * Update kube pod container component following ux review * Changes following review - smaller icon - re-add container probes --- .../kubernetes-pod-containers.component.html | 47 ++++- .../kubernetes-pod-containers.component.scss | 61 +++++- .../kubernetes-pod-containers.component.ts | 178 ++++++------------ 3 files changed, 154 insertions(+), 132 deletions(-) diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.html index 3eab146d85..6d265bc99c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.html @@ -1,4 +1,45 @@ -<div class="pod-containers"> - <app-table [dataSource]="containerDataSource" [columns]="columns" [inExpandedRow]="true"> - </app-table> +<div *ngIf="containers$ | async as containers" class="pod-containers"> + <div *ngFor="let container of containers" class="pod-containers__container"> + <div class="pod-containers__container__icon"> + <mat-icon *ngIf="icon[container.isInit] as iconDetail" class="" [fontSet]="iconDetail.font" + [ngClass]="{'container-icon': iconDetail.icon === 'container'}" [matTooltip]="iconDetail.tooltip"> + {{ iconDetail.icon }}</mat-icon> + </div> + <div class="pod-containers__container__details"> + <div class="pod-containers__container__details--header"> + <app-boolean-indicator [isTrue]="container.containerStatus.ready" type="yes-no" [subtle]="false" + [showText]="false"> + </app-boolean-indicator> + <span> + {{container.container.name}} + </span> + </div> + <div class="pod-containers__container__details--content"> + <div> + <span> + Image: + </span> + <span> + {{container.container.image}} + </span> + </div> + <div> + <span> + State: + </span> + <span> + {{container.status}} + </span> + </div> + <div *ngIf="!container.isInit"> + <span> + Probes (L:R): + </span> + <span> + {{container.container.livenessProbe ? 'on' : 'off'}}:{{container.container.readinessProbe ? 'on' : 'off'}} + </span> + </div> + </div> + </div> + </div> </div> \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.scss index e6fc2cb39b..d6f9565d71 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.scss @@ -1,8 +1,61 @@ +$detail-padding: 28px; + .pod-containers { display: flex; - app-table { - flex: 1; - padding-bottom: 10px; - padding-left: 57px; + flex-direction: column; + padding-left: 54px; + &__container { + display: flex; + flex-direction: row; + height: 100px; + &__icon { + align-items: center; + display: flex; + height: 60px; + width: 50px; + mat-icon { + font-size: 35px; + height: 35px; + width: 35px; + } + .container-icon { + padding-left: 4px; + } + } + + &__details { + display: flex; + flex-direction: column; + min-width: 75%; + + &--header { + display: flex; + flex-direction: row; + font-size: 15px; + font-weight: 500; + margin-top: 10px; + app-boolean-indicator { + width: $detail-padding; + } + } + &--content { + font-size: 13px; + margin-left: $detail-padding; + padding-bottom: 10px; + & > div { + display: flex; + flex-direction: row; + } + span:first-of-type { + min-width: 90px; + } + } + } + + &:not(:last-of-type) { + .pod-containers__container__details { + border-bottom: 2px solid rgba(0, 0, 0, .1); + } + } } } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.ts index d5b4702542..f01ff1652c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.ts @@ -1,24 +1,15 @@ import { TitleCasePipe } from '@angular/common'; import { Component, Input } from '@angular/core'; import * as moment from 'moment'; -import { of } from 'rxjs'; +import { Observable } from 'rxjs'; import { filter, map } from 'rxjs/operators'; import { BooleanIndicatorType, } from '../../../../../../../core/src/shared/components/boolean-indicator/boolean-indicator.component'; import { - ITableListDataSource, -} from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; -import { - TableCellBooleanIndicatorComponent, TableCellBooleanIndicatorComponentConfig, } from '../../../../../../../core/src/shared/components/list/list-table/table-cell-boolean-indicator/table-cell-boolean-indicator.component'; -import { - TableCellIconComponent, - TableCellIconComponentConfig, -} from '../../../../../../../core/src/shared/components/list/list-table/table-cell-icon/table-cell-icon.component'; -import { ITableColumn } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; import { CardCell } from '../../../../../../../core/src/shared/components/list/list.types'; import { kubeEntityCatalog } from '../../../kubernetes-entity-catalog'; import { Container, ContainerState, ContainerStatus, InitContainer, KubernetesPod } from '../../../store/kube.types'; @@ -27,6 +18,7 @@ export interface ContainerForTable { isInit: boolean; container: Container | InitContainer; containerStatus: ContainerStatus; + status: string; } @Component({ @@ -39,21 +31,30 @@ export interface ContainerForTable { }) export class KubernetesPodContainersComponent extends CardCell<KubernetesPod> { + public containers$: Observable<ContainerForTable[]>; + public icon = { + false: { + icon: 'container', + font: 'stratos-icons', + tooltip: 'Container' + }, + true: { + icon: 'border_clear', + font: '', + tooltip: 'Init Container' + } + }; + @Input() set row(row: KubernetesPod) { - if (!row || !!this.containerDataSource) { + if (!row || !!this.containers$) { return; } const id = kubeEntityCatalog.pod.getSchema().getId(row); - this.containerDataSource = { - isTableLoading$: of(false), - connect: () => kubeEntityCatalog.pod.store.getEntityMonitor(id).entity$.pipe( - filter(pod => !!pod), - map(pod => this.map(pod)), - ), - disconnect: () => { }, - trackBy: (index, container: ContainerForTable) => container.container.name, - }; + this.containers$ = kubeEntityCatalog.pod.store.getEntityMonitor(id).entity$.pipe( + filter(pod => !!pod), + map(pod => this.map(pod)), + ); } constructor( @@ -62,106 +63,24 @@ export class KubernetesPodContainersComponent extends CardCell<KubernetesPod> { super(); } - private readyBoolConfig: TableCellBooleanIndicatorComponentConfig<ContainerForTable> = { - isEnabled: (row: ContainerForTable) => row.containerStatus.ready, - type: BooleanIndicatorType.yesNo, - subtle: false, - showText: false - }; - - private iconConfig: TableCellIconComponentConfig<ContainerForTable> = { - getIcon: (row: ContainerForTable) => row.isInit ? - { - icon: 'border_clear', - font: '', - tooltip: 'Init Container' - } : { - icon: 'border_outer', - font: '', - tooltip: 'Container' - }, - }; - - public containerDataSource: ITableListDataSource<ContainerForTable>; - public columns: ITableColumn<ContainerForTable>[] = [ - { - columnId: 'icon', - headerCell: () => '', - cellComponent: TableCellIconComponent, - cellConfig: this.iconConfig, - cellFlex: '0 0 53px', - }, - { - columnId: 'name', - headerCell: () => 'Container Name', - cellDefinition: { - valuePath: 'container.name' - }, - cellFlex: '2', - }, - { - columnId: 'image', - headerCell: () => 'Image', - cellDefinition: { - valuePath: 'container.image' - }, - cellFlex: '3', - }, - { - columnId: 'ready', - headerCell: () => 'Ready', - cellComponent: TableCellBooleanIndicatorComponent, - cellConfig: this.readyBoolConfig, - cellFlex: '1', - }, - { - columnId: 'status', - headerCell: () => 'State', - cellDefinition: { - getValue: cft => { - if (!cft.containerStatus.state) { - return 'Unknown'; - } - const entries = Object.entries(cft.containerStatus.state); - if (!entries.length) { - return 'Unknown'; - } - const sorted = entries.sort((a, b) => { - const aStarted = moment(a[1].startedAt); - const bStarted = moment(b[1].startedAt); + private getState(containerStatus: ContainerStatus) { + if (!containerStatus.state) { + return 'Unknown'; + } + const entries = Object.entries(containerStatus.state); + if (!entries.length) { + return 'Unknown'; + } + const sorted = entries.sort((a, b) => { + const aStarted = moment(a[1].startedAt); + const bStarted = moment(b[1].startedAt); - return aStarted.isBefore(bStarted) ? -1 : - aStarted.isAfter(bStarted) ? 1 : 0; + return aStarted.isBefore(bStarted) ? -1 : + aStarted.isAfter(bStarted) ? 1 : 0; - }); - return this.containerStatusToString(sorted[0][0], sorted[0][1]); - } - }, - cellFlex: '2' - }, - { - columnId: 'restarts', - headerCell: () => 'Restarts', - cellDefinition: { - getValue: cft => cft.containerStatus.restartCount.toString() - }, - cellFlex: '1', - }, - { - columnId: 'probes', - headerCell: () => 'Probes (L:R)', - cellDefinition: { - getValue: cft => { - if (cft.isInit) { - return ''; - } - const container: Container = cft.container as Container; - return cft.isInit ? '' : `${container.livenessProbe ? 'on' : 'off'}:${container.readinessProbe ? 'on' : 'off'}`; - } - }, - cellFlex: '1', - }, - ]; + }); + return this.containerStatusToString(sorted[0][0], sorted[0][1]); + } private map(row: KubernetesPod): ContainerForTable[] { const containerStatus = row.status.containerStatuses || []; @@ -173,20 +92,29 @@ export class KubernetesPodContainersComponent extends CardCell<KubernetesPod> { return containerStatusWithContainers.sort((a, b) => a.container.name.localeCompare(b.container.name)); } + public readyBoolConfig: TableCellBooleanIndicatorComponentConfig<ContainerForTable> = { + isEnabled: (row: ContainerForTable) => row.containerStatus.ready, + type: BooleanIndicatorType.yesNo, + subtle: false, + showText: false + }; + + private containerStatusToString(state: string, status: ContainerState): string { + const exitCode = status.exitCode ? `:${status.exitCode}` : ''; + const signal = status.signal ? `:${status.signal}` : ''; + const reason = status.reason ? ` (${status.reason}${exitCode || signal})` : ''; + return `${this.titleCase.transform(state)}${reason}`; + } + private createContainerForTable(containerStatus: ContainerStatus, containers: (Container | InitContainer)[], isInit = false): ContainerForTable { const containerForTable: ContainerForTable = { isInit, containerStatus, - container: containers.find(c => c.name === containerStatus.name) + container: containers.find(c => c.name === containerStatus.name), + status: this.getState(containerStatus) }; return containerForTable; } - private containerStatusToString(state: string, status: ContainerState): string { - const exitCode = status.exitCode ? `:${status.exitCode}` : ''; - const signal = status.signal ? `:${status.signal}` : ''; - const reason = status.reason ? ` (${status.reason}${exitCode || signal})` : ''; - return `${this.titleCase.transform(state)}${reason}`; - } } From d2d2223f6d385b7160361ace370c11e7382b673a Mon Sep 17 00:00:00 2001 From: Neil MacDougall <nwmac@users.noreply.github.com> Date: Wed, 26 Aug 2020 15:15:00 +0100 Subject: [PATCH 598/648] Helm: Fix upgrade bug (#457) * Fix upgrade bug * Fix unit tests --- .../console/templates/deployment.yaml | 17 +++++++++++------ .../console/tests/custom_annotations_test.yaml | 14 ++++++++------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/deploy/kubernetes/console/templates/deployment.yaml b/deploy/kubernetes/console/templates/deployment.yaml index 6fd4fd2985..2cd00464d0 100644 --- a/deploy/kubernetes/console/templates/deployment.yaml +++ b/deploy/kubernetes/console/templates/deployment.yaml @@ -4,12 +4,13 @@ apiVersion: apps/v1 {{- else }} apiVersion: apps/v1beta1 {{- end }} -kind: StatefulSet +kind: Deployment metadata: name: stratos -{{- if .Values.console.statefulSetAnnotations }} +{{- if .Values.console.deploymentAnnotations }} annotations: -{{ toYaml .Values.console.statefulSetAnnotations | indent 4 }} + checksum/config: {{ default (randAlphaNum 5 | quote) .Values.console.checksum }} +{{ toYaml .Values.console.deploymentAnnotations | indent 4 }} {{- end }} labels: app.kubernetes.io/name: "stratos" @@ -19,12 +20,16 @@ metadata: helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" app: "{{ .Release.Name }}" component: stratos -{{- if .Values.console.statefulSetExtraLabels }} -{{ toYaml .Values.console.statefulSetExtraLabels | indent 4 }} +{{- if .Values.console.deploymentExtraLabels }} +{{ toYaml .Values.console.deploymentExtraLabels | indent 4 }} {{- end }} spec: - serviceName: stratos replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 selector: matchLabels: app.kubernetes.io/name: "stratos" diff --git a/deploy/kubernetes/console/tests/custom_annotations_test.yaml b/deploy/kubernetes/console/tests/custom_annotations_test.yaml index 39a06a3787..451ffcd8c0 100644 --- a/deploy/kubernetes/console/tests/custom_annotations_test.yaml +++ b/deploy/kubernetes/console/tests/custom_annotations_test.yaml @@ -67,28 +67,30 @@ tests: value: r23453463456 template: database.yaml -# Stateful set +# Stratos Deployment - - it: statefulset should not have annotaions unless configured + - it: stratos deploymentment should not have annotaions unless configured asserts: - isNull: path: metadata.annotations template: deployment.yaml - it: statefulset should support custom annotations set: - console.statefulSetAnnotations: + console.checksum: checksum-test + console.deploymentAnnotations: test-annotation: 172623 test-annotation2: r23453463456 asserts: - equal: path: metadata.annotations value: + checksum/config: checksum-test test-annotation: 172623 test-annotation2: r23453463456 template: deployment.yaml - - it: statefulset should support custom labels + - it: stratos deploymentment should support custom labels set: - console.statefulSetExtraLabels: + console.deploymentExtraLabels: test-label: 172623 test-label2: r23453463456 asserts: @@ -99,7 +101,7 @@ tests: path: metadata.labels.test-label2 value: r23453463456 template: deployment.yaml - - it: statefulset should support custom labels and annotations on its pods + - it: stratos deploymentment should support custom labels and annotations on its pods set: console.podExtraLabels: test-label: 172623 From 0254f0af16b2f4f39d42686b575e29c535676f87 Mon Sep 17 00:00:00 2001 From: Neil MacDougall <nwmac@users.noreply.github.com> Date: Thu, 27 Aug 2020 15:32:55 +0100 Subject: [PATCH 599/648] Update login screen to latest design (#449) * Updated SUSE login screen * Remove unused logos * Addressed PR feedback * Improvements to the SSO login page --- .../assets/custom/login/sign-in-bg.svg | 1 + .../assets/custom/login/sign-in-line.svg | 1 + .../assets/custom/login/sign-in-lock-1.svg | 1 + .../assets/custom/login/suse-logo-dark.svg | 1 + .../assets/custom/login/suse-logo-light.svg | 1 + .../assets/custom/suse_login_logo.svg | 10 - .../assets/custom/suse_logo.svg | 10 - .../assets/suse_login_logo.svg | 10 - .../suse-login/suse-login.component.html | 85 ++++---- .../suse-login/suse-login.component.scss | 189 +++++++++++++----- 10 files changed, 196 insertions(+), 113 deletions(-) create mode 100644 src/frontend/packages/suse-extensions/assets/custom/login/sign-in-bg.svg create mode 100644 src/frontend/packages/suse-extensions/assets/custom/login/sign-in-line.svg create mode 100644 src/frontend/packages/suse-extensions/assets/custom/login/sign-in-lock-1.svg create mode 100644 src/frontend/packages/suse-extensions/assets/custom/login/suse-logo-dark.svg create mode 100644 src/frontend/packages/suse-extensions/assets/custom/login/suse-logo-light.svg delete mode 100644 src/frontend/packages/suse-extensions/assets/custom/suse_login_logo.svg delete mode 100644 src/frontend/packages/suse-extensions/assets/custom/suse_logo.svg delete mode 100644 src/frontend/packages/suse-extensions/assets/suse_login_logo.svg diff --git a/src/frontend/packages/suse-extensions/assets/custom/login/sign-in-bg.svg b/src/frontend/packages/suse-extensions/assets/custom/login/sign-in-bg.svg new file mode 100644 index 0000000000..f868cfa9b9 --- /dev/null +++ b/src/frontend/packages/suse-extensions/assets/custom/login/sign-in-bg.svg @@ -0,0 +1 @@ +<svg data-name="BG 1900" xmlns="http://www.w3.org/2000/svg" width="1900" height="768"><defs><clipPath id="a"><path data-name="Rectangle 2066" fill="#fff" stroke="#707070" d="M0 0h1900v768H0z"/></clipPath><clipPath id="b"><path data-name="Rectangle 2019" transform="translate(556.592 1945.341)" fill="none" d="M0 0h302.951v793H0z"/></clipPath></defs><g data-name="Mask Group 8"><g data-name="BG 1900" clip-path="url(#a)"><path data-name="Rectangle 1700" fill="#7ad4aa" d="M0 0h1900v768H0z"/><g data-name="Group 323" fill="none" stroke-miterlimit="10"><path data-name="Path 364" d="M728.929 357h208.236A28.835 28.835 0 00966 328.165v-431.33A28.835 28.835 0 01994.835-132H1211" stroke="#000" stroke-linecap="round" stroke-width="3" stroke-dasharray="0 8"/><path data-name="Path 365" d="M-146 174h450.165A28.835 28.835 0 01333 202.835v125.33A28.835 28.835 0 00361.835 357H579" stroke="#0c322c" stroke-width="2"/></g><g data-name="Group 325" transform="translate(1099 -316)"><path data-name="Path 364" d="M692.929 700h208.236A28.835 28.835 0 00930 671.165v-431.33A28.835 28.835 0 01958.835 211H1175" fill="none" stroke="#0c322c" stroke-linecap="round" stroke-miterlimit="10" stroke-width="4" stroke-dasharray="0 8"/><path data-name="Path 365" d="M-36 517h450.165A28.835 28.835 0 01443 545.835v125.33A28.835 28.835 0 00471.835 700H689" fill="none" stroke="#0c322c" stroke-miterlimit="10" stroke-width="2"/><circle data-name="Ellipse 860" cx="9" cy="9" r="9" transform="translate(680 691)" fill="#ff6a52"/></g><g data-name="Group 326"><path data-name="Path 364" d="M1446.929 1048h208.236a28.835 28.835 0 0028.835-28.835v-431.33A28.835 28.835 0 011712.835 559H1929" fill="none" stroke="#fff" stroke-linecap="round" stroke-miterlimit="10" stroke-width="3" stroke-dasharray="0 8"/></g><g data-name="Group 805"><g data-name="Group 804"><g data-name="Group 803" clip-path="url(#b)" transform="translate(-679.592 -1963.341)"><g data-name="Group 767"><path data-name="Rectangle 1983" fill="none" d="M785 1864.8h103.6v103.6H785z"/><circle data-name="Ellipse 4279" cx="2.8" cy="2.8" r="2.8" transform="translate(795 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4280" cx="2.8" cy="2.8" r="2.8" transform="translate(821 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4281" cx="2.8" cy="2.8" r="2.8" transform="translate(847 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4282" cx="2.8" cy="2.8" r="2.8" transform="translate(873 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4283" cx="2.8" cy="2.8" r="2.8" transform="translate(795 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4284" cx="2.8" cy="2.8" r="2.8" transform="translate(821 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4285" cx="2.8" cy="2.8" r="2.8" transform="translate(847 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4286" cx="2.8" cy="2.8" r="2.8" transform="translate(873 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4287" cx="2.8" cy="2.8" r="2.8" transform="translate(795 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4288" cx="2.8" cy="2.8" r="2.8" transform="translate(821 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4289" cx="2.8" cy="2.8" r="2.8" transform="translate(847 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4290" cx="2.8" cy="2.8" r="2.8" transform="translate(873 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4291" cx="2.8" cy="2.8" r="2.8" transform="translate(795 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4292" cx="2.8" cy="2.8" r="2.8" transform="translate(821 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4293" cx="2.8" cy="2.8" r="2.8" transform="translate(847 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4294" cx="2.8" cy="2.8" r="2.8" transform="translate(873 1952.8)" fill="#30ba78"/></g><g data-name="Group 768"><path data-name="Rectangle 1984" fill="none" d="M785 1968.4h103.6V2072H785z"/><circle data-name="Ellipse 4295" cx="2.8" cy="2.8" r="2.8" transform="translate(795 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4296" cx="2.8" cy="2.8" r="2.8" transform="translate(821 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4297" cx="2.8" cy="2.8" r="2.8" transform="translate(847 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4298" cx="2.8" cy="2.8" r="2.8" transform="translate(873 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4299" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4300" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4301" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4302" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4303" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4304" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4305" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4306" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4307" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4308" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4309" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4310" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2056.4)" fill="#30ba78"/></g><g data-name="Group 769"><path data-name="Rectangle 1985" fill="none" d="M785 2072h103.6v103.6H785z"/><circle data-name="Ellipse 4311" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2082)" fill="#30ba78"/><circle data-name="Ellipse 4312" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2082)" fill="#30ba78"/><circle data-name="Ellipse 4313" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2082)" fill="#30ba78"/><circle data-name="Ellipse 4314" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2082)" fill="#30ba78"/><circle data-name="Ellipse 4315" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2108)" fill="#30ba78"/><circle data-name="Ellipse 4316" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2108)" fill="#30ba78"/><circle data-name="Ellipse 4317" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2108)" fill="#30ba78"/><circle data-name="Ellipse 4318" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2108)" fill="#30ba78"/><circle data-name="Ellipse 4322" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2134)" fill="#30ba78"/><circle data-name="Ellipse 4323" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2160)" fill="#30ba78"/><circle data-name="Ellipse 4324" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2160)" fill="#30ba78"/><circle data-name="Ellipse 4325" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2160)" fill="#30ba78"/><circle data-name="Ellipse 4326" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2160)" fill="#30ba78"/></g><g data-name="Group 770"><path data-name="Rectangle 1986" fill="none" d="M785 2175.6h103.6v103.6H785z"/><circle data-name="Ellipse 4327" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4328" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4329" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4330" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4331" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4332" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4333" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4334" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4335" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4336" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4337" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4338" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4339" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4340" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4341" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4342" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2263.6)" fill="#30ba78"/></g><g data-name="Group 771"><path data-name="Rectangle 1987" fill="none" d="M785 2279.2h103.6v103.6H785z"/><circle data-name="Ellipse 4343" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4344" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4345" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4346" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4347" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4348" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4349" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4350" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4351" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4352" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4353" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4354" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4355" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4356" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4357" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4358" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2367.2)" fill="#30ba78"/></g><g data-name="Group 772"><path data-name="Rectangle 1988" fill="none" d="M785 2382.8h103.6v103.6H785z"/><circle data-name="Ellipse 4359" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4360" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4361" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4362" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4363" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4364" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4365" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4366" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4367" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4368" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4369" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4370" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4371" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4372" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4373" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4374" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2470.8)" fill="#30ba78"/></g><g data-name="Group 773"><path data-name="Rectangle 1989" fill="none" d="M785 2486.4h103.6V2590H785z"/><circle data-name="Ellipse 4375" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4376" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4377" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4378" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4379" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4380" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4381" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4382" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4383" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4384" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4385" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4386" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4387" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4388" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4389" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4390" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2574.4)" fill="#30ba78"/></g><g data-name="Group 774"><path data-name="Rectangle 1990" fill="none" d="M785 2590h103.6v103.6H785z"/><circle data-name="Ellipse 4391" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2600)" fill="#30ba78"/><circle data-name="Ellipse 4392" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2600)" fill="#30ba78"/><circle data-name="Ellipse 4393" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2600)" fill="#30ba78"/><circle data-name="Ellipse 4394" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2600)" fill="#30ba78"/><circle data-name="Ellipse 4395" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2626)" fill="#30ba78"/><circle data-name="Ellipse 4396" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2626)" fill="#30ba78"/><circle data-name="Ellipse 4397" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2626)" fill="#30ba78"/><circle data-name="Ellipse 4398" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2626)" fill="#30ba78"/><circle data-name="Ellipse 4399" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2652)" fill="#30ba78"/><circle data-name="Ellipse 4400" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2652)" fill="#30ba78"/><circle data-name="Ellipse 4401" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2652)" fill="#30ba78"/><circle data-name="Ellipse 4402" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2652)" fill="#30ba78"/><circle data-name="Ellipse 4403" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2678)" fill="#30ba78"/><circle data-name="Ellipse 4404" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2678)" fill="#30ba78"/><circle data-name="Ellipse 4405" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2678)" fill="#30ba78"/><circle data-name="Ellipse 4406" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2678)" fill="#30ba78"/></g><g data-name="Group 775"><path data-name="Rectangle 1991" fill="none" d="M785 2693.6h103.6v103.6H785z"/><circle data-name="Ellipse 4407" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4408" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4409" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4410" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4411" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4412" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4413" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4414" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4415" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4416" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4417" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4418" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4419" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4420" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4421" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4422" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2781.6)" fill="#30ba78"/></g><g data-name="Group 776"><path data-name="Rectangle 1992" fill="none" d="M681.4 1864.8H785v103.6H681.4z"/><circle data-name="Ellipse 4423" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4424" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4425" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4426" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4427" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4428" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4429" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4430" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4431" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4432" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4433" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4434" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4435" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4436" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4437" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4438" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 1952.8)" fill="#30ba78"/></g><g data-name="Group 777"><path data-name="Rectangle 1993" fill="none" d="M681.4 1968.4H785V2072H681.4z"/><circle data-name="Ellipse 4439" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4440" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4441" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4442" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4443" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4444" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4445" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4446" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4447" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4448" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4449" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4450" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4451" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4452" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4453" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4454" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2056.4)" fill="#30ba78"/></g><g data-name="Group 778"><path data-name="Rectangle 1994" fill="none" d="M681.4 2072H785v103.6H681.4z"/><circle data-name="Ellipse 4455" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2082)" fill="#30ba78"/><circle data-name="Ellipse 4456" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2082)" fill="#30ba78"/><circle data-name="Ellipse 4457" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2082)" fill="#30ba78"/><circle data-name="Ellipse 4458" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2082)" fill="#30ba78"/><circle data-name="Ellipse 4459" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2108)" fill="#30ba78"/><circle data-name="Ellipse 4460" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2108)" fill="#30ba78"/><circle data-name="Ellipse 4461" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2108)" fill="#30ba78"/><circle data-name="Ellipse 4462" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2108)" fill="#30ba78"/><circle data-name="Ellipse 4467" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2160)" fill="#30ba78"/><circle data-name="Ellipse 4468" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2160)" fill="#30ba78"/><circle data-name="Ellipse 4469" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2160)" fill="#30ba78"/><circle data-name="Ellipse 4470" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2160)" fill="#30ba78"/></g><g data-name="Group 779"><path data-name="Rectangle 1995" fill="none" d="M681.4 2175.6H785v103.6H681.4z"/><circle data-name="Ellipse 4471" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4472" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4473" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4474" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4475" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4476" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4477" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4478" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4479" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4480" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4481" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4482" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4483" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4484" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4485" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4486" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2263.6)" fill="#30ba78"/></g><g data-name="Group 780"><path data-name="Rectangle 1996" fill="none" d="M681.4 2279.2H785v103.6H681.4z"/><circle data-name="Ellipse 4487" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4488" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4489" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4490" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4491" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4492" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4493" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4494" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4495" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4496" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4497" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4498" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4499" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4500" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4501" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4502" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2367.2)" fill="#30ba78"/></g><g data-name="Group 781"><path data-name="Rectangle 1997" fill="none" d="M681.4 2382.8H785v103.6H681.4z"/><circle data-name="Ellipse 4503" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4504" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4505" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4506" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4507" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4508" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4509" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4510" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4511" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4512" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4513" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4514" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4515" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4516" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4517" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4518" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2470.8)" fill="#30ba78"/></g><g data-name="Group 782"><path data-name="Rectangle 1998" fill="none" d="M681.4 2486.4H785V2590H681.4z"/><circle data-name="Ellipse 4519" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4520" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4521" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4522" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4523" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4524" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4525" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4526" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4527" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4528" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4529" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4530" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4531" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4532" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4533" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4534" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2574.4)" fill="#30ba78"/></g><g data-name="Group 783"><path data-name="Rectangle 1999" fill="none" d="M681.4 2590H785v103.6H681.4z"/><circle data-name="Ellipse 4535" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2600)" fill="#30ba78"/><circle data-name="Ellipse 4536" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2600)" fill="#30ba78"/><circle data-name="Ellipse 4537" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2600)" fill="#30ba78"/><circle data-name="Ellipse 4538" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2600)" fill="#30ba78"/><circle data-name="Ellipse 4539" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2626)" fill="#30ba78"/><circle data-name="Ellipse 4540" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2626)" fill="#30ba78"/><circle data-name="Ellipse 4541" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2626)" fill="#30ba78"/><circle data-name="Ellipse 4542" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2626)" fill="#30ba78"/><circle data-name="Ellipse 4543" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2652)" fill="#30ba78"/><circle data-name="Ellipse 4544" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2652)" fill="#30ba78"/><circle data-name="Ellipse 4545" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2652)" fill="#30ba78"/><circle data-name="Ellipse 4546" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2652)" fill="#30ba78"/><circle data-name="Ellipse 4547" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2678)" fill="#30ba78"/><circle data-name="Ellipse 4548" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2678)" fill="#30ba78"/><circle data-name="Ellipse 4549" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2678)" fill="#30ba78"/><circle data-name="Ellipse 4550" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2678)" fill="#30ba78"/></g><g data-name="Group 784"><path data-name="Rectangle 2000" fill="none" d="M681.4 2693.6H785v103.6H681.4z"/><circle data-name="Ellipse 4551" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4552" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4553" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4554" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4555" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4556" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4557" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4558" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4559" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4560" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4561" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4562" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4563" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4564" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4565" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4566" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2781.6)" fill="#30ba78"/></g><g data-name="Group 785"><path data-name="Rectangle 2001" fill="none" d="M577.8 1864.8h103.6v103.6H577.8z"/><circle data-name="Ellipse 4567" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4568" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4569" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4570" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4571" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4572" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4573" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4574" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4575" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4576" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4577" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4578" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4579" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4580" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4581" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4582" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 1952.8)" fill="#30ba78"/></g><g data-name="Group 786"><path data-name="Rectangle 2002" fill="none" d="M577.8 1968.4h103.6V2072H577.8z"/><circle data-name="Ellipse 4583" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4584" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4585" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4586" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4587" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4588" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4589" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4590" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4591" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4592" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4593" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4594" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4595" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4596" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4597" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4598" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2056.4)" fill="#30ba78"/></g><g data-name="Group 787"><path data-name="Rectangle 2003" fill="none" d="M577.8 2072h103.6v103.6H577.8z"/><circle data-name="Ellipse 4599" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2082)" fill="#30ba78"/><circle data-name="Ellipse 4600" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2082)" fill="#30ba78"/><circle data-name="Ellipse 4601" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2082)" fill="#30ba78"/><circle data-name="Ellipse 4602" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2082)" fill="#30ba78"/><circle data-name="Ellipse 4603" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2108)" fill="#30ba78"/><circle data-name="Ellipse 4604" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2108)" fill="#30ba78"/><circle data-name="Ellipse 4605" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2108)" fill="#30ba78"/><circle data-name="Ellipse 4606" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2108)" fill="#30ba78"/><circle data-name="Ellipse 4607" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2134)" fill="#30ba78"/><circle data-name="Ellipse 4608" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2134)" fill="#30ba78"/><circle data-name="Ellipse 4609" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2134)" fill="#30ba78"/><circle data-name="Ellipse 4610" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2134)" fill="#30ba78"/><circle data-name="Ellipse 4611" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2160)" fill="#30ba78"/><circle data-name="Ellipse 4612" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2160)" fill="#30ba78"/><circle data-name="Ellipse 4613" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2160)" fill="#30ba78"/><circle data-name="Ellipse 4614" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2160)" fill="#30ba78"/></g><g data-name="Group 788"><path data-name="Rectangle 2004" fill="none" d="M577.8 2175.6h103.6v103.6H577.8z"/><circle data-name="Ellipse 4615" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4616" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4617" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4618" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4619" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4620" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4621" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4622" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4623" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4624" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4625" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4626" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4627" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4628" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4629" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4630" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2263.6)" fill="#30ba78"/></g><g data-name="Group 789"><path data-name="Rectangle 2005" fill="none" d="M577.8 2279.2h103.6v103.6H577.8z"/><circle data-name="Ellipse 4631" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4632" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4633" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4634" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4635" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4636" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4637" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4638" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4639" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4640" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4641" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4642" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4643" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4644" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4645" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4646" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2367.2)" fill="#30ba78"/></g><g data-name="Group 790"><path data-name="Rectangle 2006" fill="none" d="M577.8 2382.8h103.6v103.6H577.8z"/><circle data-name="Ellipse 4647" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4648" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4649" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4650" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4651" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4652" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4653" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4654" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4655" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4656" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4657" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4658" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4659" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4660" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4661" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4662" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2470.8)" fill="#30ba78"/></g><g data-name="Group 791"><path data-name="Rectangle 2007" fill="none" d="M577.8 2486.4h103.6V2590H577.8z"/><circle data-name="Ellipse 4663" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4664" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4665" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4666" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4667" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4668" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4669" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4670" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4671" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4672" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4673" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4674" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4675" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4676" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4677" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4678" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2574.4)" fill="#30ba78"/></g><g data-name="Group 792"><path data-name="Rectangle 2008" fill="none" d="M577.8 2590h103.6v103.6H577.8z"/><circle data-name="Ellipse 4679" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2600)" fill="#30ba78"/><circle data-name="Ellipse 4680" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2600)" fill="#30ba78"/><circle data-name="Ellipse 4681" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2600)" fill="#30ba78"/><circle data-name="Ellipse 4682" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2600)" fill="#30ba78"/><circle data-name="Ellipse 4683" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2626)" fill="#30ba78"/><circle data-name="Ellipse 4684" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2626)" fill="#30ba78"/><circle data-name="Ellipse 4685" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2626)" fill="#30ba78"/><circle data-name="Ellipse 4686" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2626)" fill="#30ba78"/><circle data-name="Ellipse 4687" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2652)" fill="#30ba78"/><circle data-name="Ellipse 4688" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2652)" fill="#30ba78"/><circle data-name="Ellipse 4689" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2652)" fill="#30ba78"/><circle data-name="Ellipse 4690" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2652)" fill="#30ba78"/><circle data-name="Ellipse 4691" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2678)" fill="#30ba78"/><circle data-name="Ellipse 4692" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2678)" fill="#30ba78"/><circle data-name="Ellipse 4693" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2678)" fill="#30ba78"/><circle data-name="Ellipse 4694" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2678)" fill="#30ba78"/></g><g data-name="Group 793"><path data-name="Rectangle 2009" fill="none" d="M577.8 2693.6h103.6v103.6H577.8z"/><circle data-name="Ellipse 4695" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4696" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4697" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4698" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4699" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4700" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4701" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4702" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4703" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4704" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4705" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4706" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4707" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4708" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4709" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4710" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2781.6)" fill="#30ba78"/></g><g data-name="Group 794"><path data-name="Rectangle 2010" fill="none" d="M474.2 1864.8h103.6v103.6H474.2z"/><circle data-name="Ellipse 4711" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4712" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4713" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4714" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4715" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4716" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4717" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4718" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4719" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4720" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4721" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4722" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4723" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4724" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4725" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4726" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 1952.8)" fill="#30ba78"/></g><g data-name="Group 795"><path data-name="Rectangle 2011" fill="none" d="M474.2 1968.4h103.6V2072H474.2z"/><circle data-name="Ellipse 4727" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4728" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4729" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4730" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4731" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4732" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4733" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4734" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4735" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4736" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4737" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4738" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4739" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4740" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4741" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4742" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2056.4)" fill="#30ba78"/></g><g data-name="Group 796"><path data-name="Rectangle 2012" fill="none" d="M474.2 2072h103.6v103.6H474.2z"/><circle data-name="Ellipse 4743" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2082)" fill="#30ba78"/><circle data-name="Ellipse 4744" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2082)" fill="#30ba78"/><circle data-name="Ellipse 4745" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2082)" fill="#30ba78"/><circle data-name="Ellipse 4746" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2082)" fill="#30ba78"/><circle data-name="Ellipse 4747" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2108)" fill="#30ba78"/><circle data-name="Ellipse 4748" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2108)" fill="#30ba78"/><circle data-name="Ellipse 4749" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2108)" fill="#30ba78"/><circle data-name="Ellipse 4750" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2108)" fill="#30ba78"/><circle data-name="Ellipse 4751" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2134)" fill="#30ba78"/><circle data-name="Ellipse 4752" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2134)" fill="#30ba78"/><circle data-name="Ellipse 4753" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2134)" fill="#30ba78"/><circle data-name="Ellipse 4754" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2134)" fill="#30ba78"/><circle data-name="Ellipse 4755" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2160)" fill="#30ba78"/><circle data-name="Ellipse 4756" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2160)" fill="#30ba78"/><circle data-name="Ellipse 4757" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2160)" fill="#30ba78"/><circle data-name="Ellipse 4758" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2160)" fill="#30ba78"/></g><g data-name="Group 797"><path data-name="Rectangle 2013" fill="none" d="M474.2 2175.6h103.6v103.6H474.2z"/><circle data-name="Ellipse 4759" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4760" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4761" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4762" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4763" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4764" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4765" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4766" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4767" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4768" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4769" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4770" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4771" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4772" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4773" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4774" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2263.6)" fill="#30ba78"/></g><g data-name="Group 798"><path data-name="Rectangle 2014" fill="none" d="M474.2 2279.2h103.6v103.6H474.2z"/><circle data-name="Ellipse 4775" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4776" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4777" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4778" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4779" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4780" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4781" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4782" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4783" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4784" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4785" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4786" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4787" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4788" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4789" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4790" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2367.2)" fill="#30ba78"/></g><g data-name="Group 799"><path data-name="Rectangle 2015" fill="none" d="M474.2 2382.8h103.6v103.6H474.2z"/><circle data-name="Ellipse 4791" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4792" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4793" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4794" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4795" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4796" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4797" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4798" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4799" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4800" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4801" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4802" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4803" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4804" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4805" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4806" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2470.8)" fill="#30ba78"/></g><g data-name="Group 800"><path data-name="Rectangle 2016" fill="none" d="M474.2 2486.4h103.6V2590H474.2z"/><circle data-name="Ellipse 4807" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4808" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4809" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4810" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4811" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4812" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4813" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4814" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4815" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4816" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4817" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4818" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4819" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4820" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4821" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4822" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2574.4)" fill="#30ba78"/></g><g data-name="Group 801"><path data-name="Rectangle 2017" fill="none" d="M474.2 2590h103.6v103.6H474.2z"/><circle data-name="Ellipse 4823" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2600)" fill="#30ba78"/><circle data-name="Ellipse 4824" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2600)" fill="#30ba78"/><circle data-name="Ellipse 4825" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2600)" fill="#30ba78"/><circle data-name="Ellipse 4826" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2600)" fill="#30ba78"/><circle data-name="Ellipse 4827" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2626)" fill="#30ba78"/><circle data-name="Ellipse 4828" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2626)" fill="#30ba78"/><circle data-name="Ellipse 4829" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2626)" fill="#30ba78"/><circle data-name="Ellipse 4830" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2626)" fill="#30ba78"/><circle data-name="Ellipse 4831" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2652)" fill="#30ba78"/><circle data-name="Ellipse 4832" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2652)" fill="#30ba78"/><circle data-name="Ellipse 4833" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2652)" fill="#30ba78"/><circle data-name="Ellipse 4834" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2652)" fill="#30ba78"/><circle data-name="Ellipse 4835" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2678)" fill="#30ba78"/><circle data-name="Ellipse 4836" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2678)" fill="#30ba78"/><circle data-name="Ellipse 4837" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2678)" fill="#30ba78"/><circle data-name="Ellipse 4838" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2678)" fill="#30ba78"/></g><g data-name="Group 802"><path data-name="Rectangle 2018" fill="none" d="M474.2 2693.6h103.6v103.6H474.2z"/><circle data-name="Ellipse 4839" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4840" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4841" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4842" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4843" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4844" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4845" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4846" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4847" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4848" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4849" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4850" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4851" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4852" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4853" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4854" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2781.6)" fill="#30ba78"/></g></g></g></g><g data-name="Group 806"><g data-name="Group 804"><g data-name="Group 803" clip-path="url(#b)" transform="translate(1192.408 -2594.341)"><g data-name="Group 767"><path data-name="Rectangle 1983" fill="none" d="M785 1864.8h103.6v103.6H785z"/><circle data-name="Ellipse 4279" cx="2.8" cy="2.8" r="2.8" transform="translate(795 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4280" cx="2.8" cy="2.8" r="2.8" transform="translate(821 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4281" cx="2.8" cy="2.8" r="2.8" transform="translate(847 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4282" cx="2.8" cy="2.8" r="2.8" transform="translate(873 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4283" cx="2.8" cy="2.8" r="2.8" transform="translate(795 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4284" cx="2.8" cy="2.8" r="2.8" transform="translate(821 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4285" cx="2.8" cy="2.8" r="2.8" transform="translate(847 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4286" cx="2.8" cy="2.8" r="2.8" transform="translate(873 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4287" cx="2.8" cy="2.8" r="2.8" transform="translate(795 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4288" cx="2.8" cy="2.8" r="2.8" transform="translate(821 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4289" cx="2.8" cy="2.8" r="2.8" transform="translate(847 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4290" cx="2.8" cy="2.8" r="2.8" transform="translate(873 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4291" cx="2.8" cy="2.8" r="2.8" transform="translate(795 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4292" cx="2.8" cy="2.8" r="2.8" transform="translate(821 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4293" cx="2.8" cy="2.8" r="2.8" transform="translate(847 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4294" cx="2.8" cy="2.8" r="2.8" transform="translate(873 1952.8)" fill="#30ba78"/></g><g data-name="Group 768"><path data-name="Rectangle 1984" fill="none" d="M785 1968.4h103.6V2072H785z"/><circle data-name="Ellipse 4295" cx="2.8" cy="2.8" r="2.8" transform="translate(795 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4296" cx="2.8" cy="2.8" r="2.8" transform="translate(821 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4297" cx="2.8" cy="2.8" r="2.8" transform="translate(847 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4298" cx="2.8" cy="2.8" r="2.8" transform="translate(873 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4299" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4300" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4301" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4302" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4303" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4304" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4305" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4306" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4307" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4308" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4309" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4310" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2056.4)" fill="#30ba78"/></g><g data-name="Group 769"><path data-name="Rectangle 1985" fill="none" d="M785 2072h103.6v103.6H785z"/><circle data-name="Ellipse 4311" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2082)" fill="#30ba78"/><circle data-name="Ellipse 4312" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2082)" fill="#30ba78"/><circle data-name="Ellipse 4313" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2082)" fill="#30ba78"/><circle data-name="Ellipse 4314" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2082)" fill="#30ba78"/><circle data-name="Ellipse 4315" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2108)" fill="#30ba78"/><circle data-name="Ellipse 4316" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2108)" fill="#30ba78"/><circle data-name="Ellipse 4317" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2108)" fill="#30ba78"/><circle data-name="Ellipse 4318" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2108)" fill="#30ba78"/><circle data-name="Ellipse 4322" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2134)" fill="#30ba78"/><circle data-name="Ellipse 4323" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2160)" fill="#30ba78"/><circle data-name="Ellipse 4324" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2160)" fill="#30ba78"/><circle data-name="Ellipse 4325" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2160)" fill="#30ba78"/><circle data-name="Ellipse 4326" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2160)" fill="#30ba78"/></g><g data-name="Group 770"><path data-name="Rectangle 1986" fill="none" d="M785 2175.6h103.6v103.6H785z"/><circle data-name="Ellipse 4327" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4328" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4329" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4330" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4331" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4332" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4333" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4334" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4335" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4336" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4337" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4338" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4339" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4340" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4341" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4342" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2263.6)" fill="#30ba78"/></g><g data-name="Group 771"><path data-name="Rectangle 1987" fill="none" d="M785 2279.2h103.6v103.6H785z"/><circle data-name="Ellipse 4343" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4344" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4345" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4346" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4347" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4348" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4349" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4350" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4351" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4352" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4353" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4354" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4355" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4356" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4357" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4358" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2367.2)" fill="#30ba78"/></g><g data-name="Group 772"><path data-name="Rectangle 1988" fill="none" d="M785 2382.8h103.6v103.6H785z"/><circle data-name="Ellipse 4359" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4360" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4361" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4362" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4363" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4364" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4365" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4366" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4367" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4368" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4369" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4370" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4371" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4372" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4373" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4374" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2470.8)" fill="#30ba78"/></g><g data-name="Group 773"><path data-name="Rectangle 1989" fill="none" d="M785 2486.4h103.6V2590H785z"/><circle data-name="Ellipse 4375" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4376" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4377" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4378" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4379" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4380" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4381" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4382" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4383" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4384" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4385" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4386" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4387" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4388" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4389" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4390" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2574.4)" fill="#30ba78"/></g><g data-name="Group 774"><path data-name="Rectangle 1990" fill="none" d="M785 2590h103.6v103.6H785z"/><circle data-name="Ellipse 4391" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2600)" fill="#30ba78"/><circle data-name="Ellipse 4392" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2600)" fill="#30ba78"/><circle data-name="Ellipse 4393" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2600)" fill="#30ba78"/><circle data-name="Ellipse 4394" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2600)" fill="#30ba78"/><circle data-name="Ellipse 4395" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2626)" fill="#30ba78"/><circle data-name="Ellipse 4396" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2626)" fill="#30ba78"/><circle data-name="Ellipse 4397" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2626)" fill="#30ba78"/><circle data-name="Ellipse 4398" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2626)" fill="#30ba78"/><circle data-name="Ellipse 4399" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2652)" fill="#30ba78"/><circle data-name="Ellipse 4400" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2652)" fill="#30ba78"/><circle data-name="Ellipse 4401" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2652)" fill="#30ba78"/><circle data-name="Ellipse 4402" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2652)" fill="#30ba78"/><circle data-name="Ellipse 4403" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2678)" fill="#30ba78"/><circle data-name="Ellipse 4404" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2678)" fill="#30ba78"/><circle data-name="Ellipse 4405" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2678)" fill="#30ba78"/><circle data-name="Ellipse 4406" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2678)" fill="#30ba78"/></g><g data-name="Group 775"><path data-name="Rectangle 1991" fill="none" d="M785 2693.6h103.6v103.6H785z"/><circle data-name="Ellipse 4407" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4408" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4409" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4410" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4411" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4412" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4413" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4414" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4415" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4416" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4417" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4418" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4419" cx="2.8" cy="2.8" r="2.8" transform="translate(795 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4420" cx="2.8" cy="2.8" r="2.8" transform="translate(821 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4421" cx="2.8" cy="2.8" r="2.8" transform="translate(847 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4422" cx="2.8" cy="2.8" r="2.8" transform="translate(873 2781.6)" fill="#30ba78"/></g><g data-name="Group 776"><path data-name="Rectangle 1992" fill="none" d="M681.4 1864.8H785v103.6H681.4z"/><circle data-name="Ellipse 4423" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4424" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4425" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4426" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4427" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4428" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4429" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4430" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4431" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4432" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4433" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4434" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4435" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4436" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4437" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4438" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 1952.8)" fill="#30ba78"/></g><g data-name="Group 777"><path data-name="Rectangle 1993" fill="none" d="M681.4 1968.4H785V2072H681.4z"/><circle data-name="Ellipse 4439" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4440" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4441" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4442" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4443" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4444" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4445" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4446" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4447" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4448" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4449" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4450" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4451" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4452" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4453" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4454" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2056.4)" fill="#30ba78"/></g><g data-name="Group 779"><path data-name="Rectangle 1995" fill="none" d="M681.4 2175.6H785v103.6H681.4z"/><circle data-name="Ellipse 4471" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4472" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4473" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4474" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4475" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4476" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4477" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4478" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4479" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4480" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4481" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4482" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4483" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4484" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4485" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4486" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2263.6)" fill="#30ba78"/></g><g data-name="Group 780"><path data-name="Rectangle 1996" fill="none" d="M681.4 2279.2H785v103.6H681.4z"/><circle data-name="Ellipse 4487" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4488" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4489" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4490" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4491" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4492" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4493" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4494" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4495" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4496" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4497" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4498" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4499" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4500" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4501" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4502" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2367.2)" fill="#30ba78"/></g><g data-name="Group 781"><path data-name="Rectangle 1997" fill="none" d="M681.4 2382.8H785v103.6H681.4z"/><circle data-name="Ellipse 4503" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4504" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4505" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4506" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4507" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4508" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4509" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4510" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4511" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4512" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4513" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4514" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4515" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4516" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4517" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4518" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2470.8)" fill="#30ba78"/></g><g data-name="Group 782"><path data-name="Rectangle 1998" fill="none" d="M681.4 2486.4H785V2590H681.4z"/><circle data-name="Ellipse 4519" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4520" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4521" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4522" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4523" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4524" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4525" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4526" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4527" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4528" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4529" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4530" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4531" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4532" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4533" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4534" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2574.4)" fill="#30ba78"/></g><g data-name="Group 783"><path data-name="Rectangle 1999" fill="none" d="M681.4 2590H785v103.6H681.4z"/><circle data-name="Ellipse 4535" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2600)" fill="#30ba78"/><circle data-name="Ellipse 4536" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2600)" fill="#30ba78"/><circle data-name="Ellipse 4537" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2600)" fill="#30ba78"/><circle data-name="Ellipse 4538" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2600)" fill="#30ba78"/><circle data-name="Ellipse 4539" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2626)" fill="#30ba78"/><circle data-name="Ellipse 4540" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2626)" fill="#30ba78"/><circle data-name="Ellipse 4541" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2626)" fill="#30ba78"/><circle data-name="Ellipse 4542" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2626)" fill="#30ba78"/><circle data-name="Ellipse 4543" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2652)" fill="#30ba78"/><circle data-name="Ellipse 4544" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2652)" fill="#30ba78"/><circle data-name="Ellipse 4545" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2652)" fill="#30ba78"/><circle data-name="Ellipse 4546" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2652)" fill="#30ba78"/><circle data-name="Ellipse 4547" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2678)" fill="#30ba78"/><circle data-name="Ellipse 4548" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2678)" fill="#30ba78"/><circle data-name="Ellipse 4549" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2678)" fill="#30ba78"/><circle data-name="Ellipse 4550" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2678)" fill="#30ba78"/></g><g data-name="Group 784"><path data-name="Rectangle 2000" fill="none" d="M681.4 2693.6H785v103.6H681.4z"/><circle data-name="Ellipse 4551" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4552" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4553" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4554" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4555" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4556" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4557" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4558" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4559" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4560" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4561" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4562" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4563" cx="2.8" cy="2.8" r="2.8" transform="translate(691.4 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4564" cx="2.8" cy="2.8" r="2.8" transform="translate(717.4 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4565" cx="2.8" cy="2.8" r="2.8" transform="translate(743.4 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4566" cx="2.8" cy="2.8" r="2.8" transform="translate(769.4 2781.6)" fill="#30ba78"/></g><g data-name="Group 785"><path data-name="Rectangle 2001" fill="none" d="M577.8 1864.8h103.6v103.6H577.8z"/><circle data-name="Ellipse 4567" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4568" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4569" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4570" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4571" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4572" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4573" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4574" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4575" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4576" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4577" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4578" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4579" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4580" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4581" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4582" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 1952.8)" fill="#30ba78"/></g><g data-name="Group 786"><path data-name="Rectangle 2002" fill="none" d="M577.8 1968.4h103.6V2072H577.8z"/><circle data-name="Ellipse 4584" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4585" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4586" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4587" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4588" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4589" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4590" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4591" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4592" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4593" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4594" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4595" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4596" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4597" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4598" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2056.4)" fill="#30ba78"/></g><g data-name="Group 787"><path data-name="Rectangle 2003" fill="none" d="M577.8 2072h103.6v103.6H577.8z"/><circle data-name="Ellipse 4599" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2082)" fill="#30ba78"/><circle data-name="Ellipse 4600" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2082)" fill="#30ba78"/><circle data-name="Ellipse 4601" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2082)" fill="#30ba78"/><circle data-name="Ellipse 4602" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2082)" fill="#30ba78"/><circle data-name="Ellipse 4603" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2108)" fill="#30ba78"/><circle data-name="Ellipse 4604" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2108)" fill="#30ba78"/><circle data-name="Ellipse 4605" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2108)" fill="#30ba78"/><circle data-name="Ellipse 4606" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2108)" fill="#30ba78"/><circle data-name="Ellipse 4607" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2134)" fill="#30ba78"/><circle data-name="Ellipse 4608" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2134)" fill="#30ba78"/><circle data-name="Ellipse 4609" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2134)" fill="#30ba78"/><circle data-name="Ellipse 4610" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2134)" fill="#30ba78"/><circle data-name="Ellipse 4611" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2160)" fill="#30ba78"/><circle data-name="Ellipse 4612" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2160)" fill="#30ba78"/><circle data-name="Ellipse 4613" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2160)" fill="#30ba78"/><circle data-name="Ellipse 4614" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2160)" fill="#30ba78"/></g><g data-name="Group 788"><path data-name="Rectangle 2004" fill="none" d="M577.8 2175.6h103.6v103.6H577.8z"/><circle data-name="Ellipse 4615" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4616" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4617" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4618" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4619" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4620" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4621" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4622" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4623" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4624" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4625" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4626" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4627" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4628" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4629" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4630" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2263.6)" fill="#30ba78"/></g><g data-name="Group 789"><path data-name="Rectangle 2005" fill="none" d="M577.8 2279.2h103.6v103.6H577.8z"/><circle data-name="Ellipse 4631" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4632" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4633" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4634" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4635" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4636" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4637" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4638" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4639" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4640" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4641" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4642" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4643" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4644" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4645" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4646" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2367.2)" fill="#30ba78"/></g><g data-name="Group 790"><path data-name="Rectangle 2006" fill="none" d="M577.8 2382.8h103.6v103.6H577.8z"/><circle data-name="Ellipse 4647" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4648" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4649" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4650" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4651" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4652" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4653" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4654" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4655" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4656" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4657" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4658" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4659" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4660" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4661" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4662" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2470.8)" fill="#30ba78"/></g><g data-name="Group 791"><path data-name="Rectangle 2007" fill="none" d="M577.8 2486.4h103.6V2590H577.8z"/><circle data-name="Ellipse 4663" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4664" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4665" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4666" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4667" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4668" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4669" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4670" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4671" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4672" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4673" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4674" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4675" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4676" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4677" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4678" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2574.4)" fill="#30ba78"/></g><g data-name="Group 792"><path data-name="Rectangle 2008" fill="none" d="M577.8 2590h103.6v103.6H577.8z"/><circle data-name="Ellipse 4679" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2600)" fill="#30ba78"/><circle data-name="Ellipse 4680" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2600)" fill="#30ba78"/><circle data-name="Ellipse 4681" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2600)" fill="#30ba78"/><circle data-name="Ellipse 4682" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2600)" fill="#30ba78"/><circle data-name="Ellipse 4683" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2626)" fill="#30ba78"/><circle data-name="Ellipse 4684" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2626)" fill="#30ba78"/><circle data-name="Ellipse 4685" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2626)" fill="#30ba78"/><circle data-name="Ellipse 4686" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2626)" fill="#30ba78"/><circle data-name="Ellipse 4687" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2652)" fill="#30ba78"/><circle data-name="Ellipse 4688" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2652)" fill="#30ba78"/><circle data-name="Ellipse 4689" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2652)" fill="#30ba78"/><circle data-name="Ellipse 4690" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2652)" fill="#30ba78"/><circle data-name="Ellipse 4691" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2678)" fill="#30ba78"/><circle data-name="Ellipse 4692" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2678)" fill="#30ba78"/><circle data-name="Ellipse 4693" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2678)" fill="#30ba78"/><circle data-name="Ellipse 4694" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2678)" fill="#30ba78"/></g><g data-name="Group 793"><path data-name="Rectangle 2009" fill="none" d="M577.8 2693.6h103.6v103.6H577.8z"/><circle data-name="Ellipse 4695" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4696" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4697" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4698" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4699" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4700" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4701" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4702" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4703" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4704" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4705" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4706" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4707" cx="2.8" cy="2.8" r="2.8" transform="translate(587.8 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4708" cx="2.8" cy="2.8" r="2.8" transform="translate(613.8 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4709" cx="2.8" cy="2.8" r="2.8" transform="translate(639.8 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4710" cx="2.8" cy="2.8" r="2.8" transform="translate(665.8 2781.6)" fill="#30ba78"/></g><g data-name="Group 794"><path data-name="Rectangle 2010" fill="none" d="M474.2 1864.8h103.6v103.6H474.2z"/><circle data-name="Ellipse 4711" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4712" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4713" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4714" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 1874.8)" fill="#30ba78"/><circle data-name="Ellipse 4715" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4716" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4717" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4718" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 1900.8)" fill="#30ba78"/><circle data-name="Ellipse 4719" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4720" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4721" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4722" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 1926.8)" fill="#30ba78"/><circle data-name="Ellipse 4723" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4724" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4725" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 1952.8)" fill="#30ba78"/><circle data-name="Ellipse 4726" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 1952.8)" fill="#30ba78"/></g><g data-name="Group 795"><path data-name="Rectangle 2011" fill="none" d="M474.2 1968.4h103.6V2072H474.2z"/><circle data-name="Ellipse 4727" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4728" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4729" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4730" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 1978.4)" fill="#30ba78"/><circle data-name="Ellipse 4731" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4732" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4733" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4734" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2004.4)" fill="#30ba78"/><circle data-name="Ellipse 4735" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4736" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4737" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4738" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2030.4)" fill="#30ba78"/><circle data-name="Ellipse 4739" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4740" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4741" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2056.4)" fill="#30ba78"/><circle data-name="Ellipse 4742" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2056.4)" fill="#30ba78"/></g><g data-name="Group 796"><path data-name="Rectangle 2012" fill="none" d="M474.2 2072h103.6v103.6H474.2z"/><circle data-name="Ellipse 4743" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2082)" fill="#30ba78"/><circle data-name="Ellipse 4744" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2082)" fill="#30ba78"/><circle data-name="Ellipse 4745" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2082)" fill="#30ba78"/><circle data-name="Ellipse 4746" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2082)" fill="#30ba78"/><circle data-name="Ellipse 4747" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2108)" fill="#30ba78"/><circle data-name="Ellipse 4748" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2108)" fill="#30ba78"/><circle data-name="Ellipse 4749" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2108)" fill="#30ba78"/><circle data-name="Ellipse 4750" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2108)" fill="#30ba78"/><circle data-name="Ellipse 4751" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2134)" fill="#30ba78"/><circle data-name="Ellipse 4752" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2134)" fill="#30ba78"/><circle data-name="Ellipse 4753" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2134)" fill="#30ba78"/><circle data-name="Ellipse 4754" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2134)" fill="#30ba78"/><circle data-name="Ellipse 4755" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2160)" fill="#30ba78"/><circle data-name="Ellipse 4756" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2160)" fill="#30ba78"/><circle data-name="Ellipse 4757" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2160)" fill="#30ba78"/><circle data-name="Ellipse 4758" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2160)" fill="#30ba78"/></g><g data-name="Group 797"><path data-name="Rectangle 2013" fill="none" d="M474.2 2175.6h103.6v103.6H474.2z"/><circle data-name="Ellipse 4759" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4760" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4761" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4762" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2185.6)" fill="#30ba78"/><circle data-name="Ellipse 4763" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4764" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4765" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4766" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2211.6)" fill="#30ba78"/><circle data-name="Ellipse 4767" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4768" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4769" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4770" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2237.6)" fill="#30ba78"/><circle data-name="Ellipse 4771" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4772" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4773" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2263.6)" fill="#30ba78"/><circle data-name="Ellipse 4774" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2263.6)" fill="#30ba78"/></g><g data-name="Group 798"><path data-name="Rectangle 2014" fill="none" d="M474.2 2279.2h103.6v103.6H474.2z"/><circle data-name="Ellipse 4775" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4776" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4777" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4778" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2289.2)" fill="#30ba78"/><circle data-name="Ellipse 4779" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4780" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4781" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4782" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2315.2)" fill="#30ba78"/><circle data-name="Ellipse 4783" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4784" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4785" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4786" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2341.2)" fill="#30ba78"/><circle data-name="Ellipse 4787" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4788" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4789" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2367.2)" fill="#30ba78"/><circle data-name="Ellipse 4790" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2367.2)" fill="#30ba78"/></g><g data-name="Group 799"><path data-name="Rectangle 2015" fill="none" d="M474.2 2382.8h103.6v103.6H474.2z"/><circle data-name="Ellipse 4791" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4792" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4793" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4794" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2392.8)" fill="#30ba78"/><circle data-name="Ellipse 4795" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4796" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4797" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4798" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2418.8)" fill="#30ba78"/><circle data-name="Ellipse 4799" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4800" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4801" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4802" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2444.8)" fill="#30ba78"/><circle data-name="Ellipse 4803" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4804" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4805" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2470.8)" fill="#30ba78"/><circle data-name="Ellipse 4806" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2470.8)" fill="#30ba78"/></g><g data-name="Group 800"><path data-name="Rectangle 2016" fill="none" d="M474.2 2486.4h103.6V2590H474.2z"/><circle data-name="Ellipse 4807" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4808" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4809" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4810" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2496.4)" fill="#30ba78"/><circle data-name="Ellipse 4811" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4812" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4813" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4814" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2522.4)" fill="#30ba78"/><circle data-name="Ellipse 4815" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4816" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4817" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4818" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2548.4)" fill="#30ba78"/><circle data-name="Ellipse 4819" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4820" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4821" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2574.4)" fill="#30ba78"/><circle data-name="Ellipse 4822" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2574.4)" fill="#30ba78"/></g><g data-name="Group 801"><path data-name="Rectangle 2017" fill="none" d="M474.2 2590h103.6v103.6H474.2z"/><circle data-name="Ellipse 4823" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2600)" fill="#30ba78"/><circle data-name="Ellipse 4824" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2600)" fill="#30ba78"/><circle data-name="Ellipse 4825" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2600)" fill="#30ba78"/><circle data-name="Ellipse 4826" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2600)" fill="#30ba78"/><circle data-name="Ellipse 4827" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2626)" fill="#30ba78"/><circle data-name="Ellipse 4828" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2626)" fill="#30ba78"/><circle data-name="Ellipse 4829" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2626)" fill="#30ba78"/><circle data-name="Ellipse 4830" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2626)" fill="#30ba78"/><circle data-name="Ellipse 4831" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2652)" fill="#30ba78"/><circle data-name="Ellipse 4832" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2652)" fill="#30ba78"/><circle data-name="Ellipse 4833" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2652)" fill="#30ba78"/><circle data-name="Ellipse 4834" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2652)" fill="#30ba78"/><circle data-name="Ellipse 4835" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2678)" fill="#30ba78"/><circle data-name="Ellipse 4836" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2678)" fill="#30ba78"/><circle data-name="Ellipse 4837" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2678)" fill="#30ba78"/><circle data-name="Ellipse 4838" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2678)" fill="#30ba78"/></g><g data-name="Group 802"><path data-name="Rectangle 2018" fill="none" d="M474.2 2693.6h103.6v103.6H474.2z"/><circle data-name="Ellipse 4839" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4840" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4841" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4842" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2703.6)" fill="#30ba78"/><circle data-name="Ellipse 4843" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4844" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4845" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4846" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2729.6)" fill="#30ba78"/><circle data-name="Ellipse 4847" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4848" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4849" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4850" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2755.6)" fill="#30ba78"/><circle data-name="Ellipse 4851" cx="2.8" cy="2.8" r="2.8" transform="translate(484.2 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4852" cx="2.8" cy="2.8" r="2.8" transform="translate(510.2 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4853" cx="2.8" cy="2.8" r="2.8" transform="translate(536.2 2781.6)" fill="#30ba78"/><circle data-name="Ellipse 4854" cx="2.8" cy="2.8" r="2.8" transform="translate(562.2 2781.6)" fill="#30ba78"/></g></g></g></g></g></g></svg> \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/assets/custom/login/sign-in-line.svg b/src/frontend/packages/suse-extensions/assets/custom/login/sign-in-line.svg new file mode 100644 index 0000000000..5a6c479b25 --- /dev/null +++ b/src/frontend/packages/suse-extensions/assets/custom/login/sign-in-line.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="457" height="95.016" viewBox="0 0 457 95.016"><g id="Group_811" data-name="Group 811" transform="translate(-3307.583 -534.597)"><line id="Line_10" data-name="Line 10" x2="182.791" transform="translate(3580.792 621.613)" fill="none" stroke="#30ba78" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><path id="Path_643" data-name="Path 643" d="M3569.289,621.613h-90.955a21.719,21.719,0,0,1-21.719-21.719V557.316A21.719,21.719,0,0,0,3434.9,535.6H3308.583" fill="none" stroke="#30ba78" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke-dasharray="0 7"/><circle id="Ellipse_4857" data-name="Ellipse 4857" cx="8" cy="8" r="8" transform="translate(3567.927 613.613)" fill="#ff6a52"/></g></svg> \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/assets/custom/login/sign-in-lock-1.svg b/src/frontend/packages/suse-extensions/assets/custom/login/sign-in-lock-1.svg new file mode 100644 index 0000000000..2237550796 --- /dev/null +++ b/src/frontend/packages/suse-extensions/assets/custom/login/sign-in-lock-1.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="223.234" height="119.787" viewBox="0 0 223.234 119.787"><defs><clipPath id="clip-path"><rect id="Rectangle_2065" data-name="Rectangle 2065" width="223.234" height="88.341" transform="translate(3451.343 89.42)" fill="none"/></clipPath></defs><g id="Group_880" data-name="Group 880" transform="translate(-3451.343 -57.974)"><g id="Group_867" data-name="Group 867"><g id="Group_866" data-name="Group 866"><rect id="Rectangle_2060" data-name="Rectangle 2060" width="58" height="58" rx="12.981" transform="translate(3530.603 93.584)" fill="#0c322c" stroke="#ff6a52" stroke-width="2.993"/></g><path id="Path_635" data-name="Path 635" d="M3539.905,93.236,3540,76.343c.051-9.333,8.382-16.925,18.515-16.869l2.235.012c10.132.055,18.381,7.737,18.33,17.071" fill="none" stroke="#ff6a52" stroke-linecap="round" stroke-width="3"/><circle id="Ellipse_4277" data-name="Ellipse 4277" cx="8" cy="8" r="8" transform="translate(3551.603 114.584)" fill="#ff6a52"/></g><g id="Group_878" data-name="Group 878"><g id="Group_877" data-name="Group 877"><g id="Group_876" data-name="Group 876" clip-path="url(#clip-path)"><g id="Group_868" data-name="Group 868"><rect id="Rectangle_2061" data-name="Rectangle 2061" width="78" height="78" transform="translate(3617.497 64.62)" fill="none"/><circle id="Ellipse_5121" data-name="Ellipse 5121" cx="1.5" cy="1.5" r="1.5" transform="translate(3624.998 72.12)" fill="#30ba78"/><circle id="Ellipse_5122" data-name="Ellipse 5122" cx="1.5" cy="1.5" r="1.5" transform="translate(3644.998 72.12)" fill="#30ba78"/><circle id="Ellipse_5123" data-name="Ellipse 5123" cx="1.5" cy="1.5" r="1.5" transform="translate(3664.998 72.12)" fill="#30ba78"/><circle id="Ellipse_5124" data-name="Ellipse 5124" cx="1.5" cy="1.5" r="1.5" transform="translate(3684.998 72.12)" fill="#30ba78"/><circle id="Ellipse_5125" data-name="Ellipse 5125" cx="1.5" cy="1.5" r="1.5" transform="translate(3624.998 92.12)" fill="#30ba78"/><circle id="Ellipse_5126" data-name="Ellipse 5126" cx="1.5" cy="1.5" r="1.5" transform="translate(3644.998 92.12)" fill="#30ba78"/><circle id="Ellipse_5127" data-name="Ellipse 5127" cx="1.5" cy="1.5" r="1.5" transform="translate(3664.998 92.12)" fill="#30ba78"/><circle id="Ellipse_5128" data-name="Ellipse 5128" cx="1.5" cy="1.5" r="1.5" transform="translate(3684.998 92.12)" fill="#30ba78"/><circle id="Ellipse_5129" data-name="Ellipse 5129" cx="1.5" cy="1.5" r="1.5" transform="translate(3624.998 112.12)" fill="#30ba78"/><circle id="Ellipse_5130" data-name="Ellipse 5130" cx="1.5" cy="1.5" r="1.5" transform="translate(3644.998 112.12)" fill="#30ba78"/><circle id="Ellipse_5131" data-name="Ellipse 5131" cx="1.5" cy="1.5" r="1.5" transform="translate(3664.998 112.12)" fill="#30ba78"/><circle id="Ellipse_5132" data-name="Ellipse 5132" cx="1.5" cy="1.5" r="1.5" transform="translate(3684.998 112.12)" fill="#30ba78"/><circle id="Ellipse_5133" data-name="Ellipse 5133" cx="1.5" cy="1.5" r="1.5" transform="translate(3624.998 132.12)" fill="#30ba78"/><circle id="Ellipse_5134" data-name="Ellipse 5134" cx="1.5" cy="1.5" r="1.5" transform="translate(3644.998 132.12)" fill="#30ba78"/><circle id="Ellipse_5135" data-name="Ellipse 5135" cx="1.5" cy="1.5" r="1.5" transform="translate(3664.998 132.12)" fill="#30ba78"/><circle id="Ellipse_5136" data-name="Ellipse 5136" cx="1.5" cy="1.5" r="1.5" transform="translate(3684.998 132.12)" fill="#30ba78"/></g><g id="Group_869" data-name="Group 869"><circle id="Ellipse_5137" data-name="Ellipse 5137" cx="1.5" cy="1.5" r="1.5" transform="translate(3624.998 150.12)" fill="#30ba78"/><circle id="Ellipse_5138" data-name="Ellipse 5138" cx="1.5" cy="1.5" r="1.5" transform="translate(3644.998 150.12)" fill="#30ba78"/><circle id="Ellipse_5139" data-name="Ellipse 5139" cx="1.5" cy="1.5" r="1.5" transform="translate(3664.998 150.12)" fill="#30ba78"/><circle id="Ellipse_5140" data-name="Ellipse 5140" cx="1.5" cy="1.5" r="1.5" transform="translate(3684.998 150.12)" fill="#30ba78"/></g><g id="Group_870" data-name="Group 870"><rect id="Rectangle_2062" data-name="Rectangle 2062" width="78" height="78" transform="translate(3539.497 64.62)" fill="none"/><circle id="Ellipse_5141" data-name="Ellipse 5141" cx="1.5" cy="1.5" r="1.5" transform="translate(3546.998 72.12)" fill="#30ba78"/><circle id="Ellipse_5142" data-name="Ellipse 5142" cx="1.5" cy="1.5" r="1.5" transform="translate(3566.998 72.12)" fill="#30ba78"/><circle id="Ellipse_5143" data-name="Ellipse 5143" cx="1.5" cy="1.5" r="1.5" transform="translate(3586.998 72.12)" fill="#30ba78"/><circle id="Ellipse_5144" data-name="Ellipse 5144" cx="1.5" cy="1.5" r="1.5" transform="translate(3606.998 72.12)" fill="#30ba78"/><circle id="Ellipse_5145" data-name="Ellipse 5145" cx="1.5" cy="1.5" r="1.5" transform="translate(3606.998 92.12)" fill="#30ba78"/><circle id="Ellipse_5146" data-name="Ellipse 5146" cx="1.5" cy="1.5" r="1.5" transform="translate(3606.998 112.12)" fill="#30ba78"/><circle id="Ellipse_5147" data-name="Ellipse 5147" cx="1.5" cy="1.5" r="1.5" transform="translate(3606.998 132.12)" fill="#30ba78"/></g><g id="Group_871" data-name="Group 871"><circle id="Ellipse_5148" data-name="Ellipse 5148" cx="1.5" cy="1.5" r="1.5" transform="translate(3606.998 150.12)" fill="#30ba78"/></g><g id="Group_872" data-name="Group 872"><rect id="Rectangle_2063" data-name="Rectangle 2063" width="78" height="78" transform="translate(3461.497 64.62)" fill="none"/><circle id="Ellipse_5149" data-name="Ellipse 5149" cx="1.5" cy="1.5" r="1.5" transform="translate(3468.998 72.12)" fill="#30ba78"/><circle id="Ellipse_5150" data-name="Ellipse 5150" cx="1.5" cy="1.5" r="1.5" transform="translate(3488.998 72.12)" fill="#30ba78"/><circle id="Ellipse_5151" data-name="Ellipse 5151" cx="1.5" cy="1.5" r="1.5" transform="translate(3508.998 72.12)" fill="#30ba78"/><circle id="Ellipse_5152" data-name="Ellipse 5152" cx="1.5" cy="1.5" r="1.5" transform="translate(3528.998 72.12)" fill="#30ba78"/><circle id="Ellipse_5153" data-name="Ellipse 5153" cx="1.5" cy="1.5" r="1.5" transform="translate(3468.998 92.12)" fill="#30ba78"/><circle id="Ellipse_5154" data-name="Ellipse 5154" cx="1.5" cy="1.5" r="1.5" transform="translate(3488.998 92.12)" fill="#30ba78"/><circle id="Ellipse_5155" data-name="Ellipse 5155" cx="1.5" cy="1.5" r="1.5" transform="translate(3508.998 92.12)" fill="#30ba78"/><circle id="Ellipse_5156" data-name="Ellipse 5156" cx="1.5" cy="1.5" r="1.5" transform="translate(3468.998 112.12)" fill="#30ba78"/><circle id="Ellipse_5157" data-name="Ellipse 5157" cx="1.5" cy="1.5" r="1.5" transform="translate(3488.998 112.12)" fill="#30ba78"/><circle id="Ellipse_5158" data-name="Ellipse 5158" cx="1.5" cy="1.5" r="1.5" transform="translate(3508.998 112.12)" fill="#30ba78"/><circle id="Ellipse_5159" data-name="Ellipse 5159" cx="1.5" cy="1.5" r="1.5" transform="translate(3468.998 132.12)" fill="#30ba78"/><circle id="Ellipse_5160" data-name="Ellipse 5160" cx="1.5" cy="1.5" r="1.5" transform="translate(3488.998 132.12)" fill="#30ba78"/><circle id="Ellipse_5161" data-name="Ellipse 5161" cx="1.5" cy="1.5" r="1.5" transform="translate(3508.998 132.12)" fill="#30ba78"/></g><g id="Group_873" data-name="Group 873"><circle id="Ellipse_5162" data-name="Ellipse 5162" cx="1.5" cy="1.5" r="1.5" transform="translate(3468.998 150.12)" fill="#30ba78"/><circle id="Ellipse_5163" data-name="Ellipse 5163" cx="1.5" cy="1.5" r="1.5" transform="translate(3488.998 150.12)" fill="#30ba78"/><circle id="Ellipse_5164" data-name="Ellipse 5164" cx="1.5" cy="1.5" r="1.5" transform="translate(3508.998 150.12)" fill="#30ba78"/></g><g id="Group_874" data-name="Group 874"><rect id="Rectangle_2064" data-name="Rectangle 2064" width="78" height="78" transform="translate(3383.497 64.62)" fill="none"/><circle id="Ellipse_5165" data-name="Ellipse 5165" cx="1.5" cy="1.5" r="1.5" transform="translate(3390.998 72.12)" fill="#30ba78"/><circle id="Ellipse_5166" data-name="Ellipse 5166" cx="1.5" cy="1.5" r="1.5" transform="translate(3410.998 72.12)" fill="#30ba78"/><circle id="Ellipse_5167" data-name="Ellipse 5167" cx="1.5" cy="1.5" r="1.5" transform="translate(3430.998 72.12)" fill="#30ba78"/><circle id="Ellipse_5168" data-name="Ellipse 5168" cx="1.5" cy="1.5" r="1.5" transform="translate(3450.998 72.12)" fill="#30ba78"/><circle id="Ellipse_5169" data-name="Ellipse 5169" cx="1.5" cy="1.5" r="1.5" transform="translate(3390.998 92.12)" fill="#30ba78"/><circle id="Ellipse_5170" data-name="Ellipse 5170" cx="1.5" cy="1.5" r="1.5" transform="translate(3410.998 92.12)" fill="#30ba78"/><circle id="Ellipse_5171" data-name="Ellipse 5171" cx="1.5" cy="1.5" r="1.5" transform="translate(3430.998 92.12)" fill="#30ba78"/><circle id="Ellipse_5172" data-name="Ellipse 5172" cx="1.5" cy="1.5" r="1.5" transform="translate(3450.998 92.12)" fill="#30ba78"/><circle id="Ellipse_5173" data-name="Ellipse 5173" cx="1.5" cy="1.5" r="1.5" transform="translate(3390.998 112.12)" fill="#30ba78"/><circle id="Ellipse_5174" data-name="Ellipse 5174" cx="1.5" cy="1.5" r="1.5" transform="translate(3410.998 112.12)" fill="#30ba78"/><circle id="Ellipse_5175" data-name="Ellipse 5175" cx="1.5" cy="1.5" r="1.5" transform="translate(3430.998 112.12)" fill="#30ba78"/><circle id="Ellipse_5176" data-name="Ellipse 5176" cx="1.5" cy="1.5" r="1.5" transform="translate(3450.998 112.12)" fill="#30ba78"/><circle id="Ellipse_5177" data-name="Ellipse 5177" cx="1.5" cy="1.5" r="1.5" transform="translate(3390.998 132.12)" fill="#30ba78"/><circle id="Ellipse_5178" data-name="Ellipse 5178" cx="1.5" cy="1.5" r="1.5" transform="translate(3410.998 132.12)" fill="#30ba78"/><circle id="Ellipse_5179" data-name="Ellipse 5179" cx="1.5" cy="1.5" r="1.5" transform="translate(3430.998 132.12)" fill="#30ba78"/><circle id="Ellipse_5180" data-name="Ellipse 5180" cx="1.5" cy="1.5" r="1.5" transform="translate(3450.998 132.12)" fill="#30ba78"/></g><g id="Group_875" data-name="Group 875"><circle id="Ellipse_5181" data-name="Ellipse 5181" cx="1.5" cy="1.5" r="1.5" transform="translate(3450.998 150.12)" fill="#30ba78"/></g></g></g></g></g></svg> \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/assets/custom/login/suse-logo-dark.svg b/src/frontend/packages/suse-extensions/assets/custom/login/suse-logo-dark.svg new file mode 100644 index 0000000000..0b04bd9b06 --- /dev/null +++ b/src/frontend/packages/suse-extensions/assets/custom/login/suse-logo-dark.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="64.804" height="56.333" viewBox="0 0 64.804 56.333"><g id="Group_886" data-name="Group 886" transform="translate(-27.115 -27.141)"><path id="Path_646" data-name="Path 646" d="M57.557,100.141v8.676a6.231,6.231,0,0,1-1.648,4.7,6.655,6.655,0,0,1-4.8,1.59,6.656,6.656,0,0,1-4.8-1.59,6.229,6.229,0,0,1-1.648-4.7v-8.676a1.29,1.29,0,0,1,2.58,0v8.364a4.885,4.885,0,0,0,.944,3.31,3.72,3.72,0,0,0,2.928,1.063,3.721,3.721,0,0,0,2.927-1.063,4.888,4.888,0,0,0,.943-3.31v-8.364a1.29,1.29,0,0,1,2.581,0Zm-23.538,5.764a9.553,9.553,0,0,1-3.154-.968,1.723,1.723,0,0,1-.884-1.519,2,2,0,0,1,.908-1.684,4.31,4.31,0,0,1,2.558-.657,4.9,4.9,0,0,1,2.677.645,3.908,3.908,0,0,1,.985.926,1.225,1.225,0,1,0,1.883-1.564,6.371,6.371,0,0,0-1.853-1.441,8.075,8.075,0,0,0-3.716-.789,7.71,7.71,0,0,0-3.262.646,5.052,5.052,0,0,0-2.139,1.744,4.269,4.269,0,0,0-.741,2.437,3.8,3.8,0,0,0,.586,2.151,4.368,4.368,0,0,0,1.876,1.482,14.988,14.988,0,0,0,3.465,1,9.959,9.959,0,0,1,3.047.932,1.574,1.574,0,0,1,.872,1.386,1.839,1.839,0,0,1-.968,1.661,5.14,5.14,0,0,1-2.664.586,6.114,6.114,0,0,1-3-.669,4.533,4.533,0,0,1-1.235-1.014,1.218,1.218,0,0,0-1.778-.048l0,0a1.216,1.216,0,0,0-.06,1.667q2.045,2.283,6.1,2.283a8.906,8.906,0,0,0,3.286-.562,4.958,4.958,0,0,0,2.21-1.613,3.952,3.952,0,0,0,.789-2.438,3.858,3.858,0,0,0-.573-2.163,4.275,4.275,0,0,0-1.829-1.458A14.067,14.067,0,0,0,34.019,105.905Zm35.293-.005a9.531,9.531,0,0,1-3.154-.968,1.721,1.721,0,0,1-.884-1.517,2,2,0,0,1,.908-1.685,4.307,4.307,0,0,1,2.558-.657,4.894,4.894,0,0,1,2.677.645,3.945,3.945,0,0,1,.985.925,1.224,1.224,0,1,0,1.883-1.563,6.346,6.346,0,0,0-1.853-1.441,8.071,8.071,0,0,0-3.716-.788,7.7,7.7,0,0,0-3.262.645,5.045,5.045,0,0,0-2.139,1.744,4.262,4.262,0,0,0-.741,2.437,3.8,3.8,0,0,0,.586,2.151,4.368,4.368,0,0,0,1.876,1.482,14.976,14.976,0,0,0,3.466,1,9.972,9.972,0,0,1,3.047.932,1.575,1.575,0,0,1,.874,1.386,1.839,1.839,0,0,1-.97,1.661,5.127,5.127,0,0,1-2.663.586,6.112,6.112,0,0,1-3-.669,4.544,4.544,0,0,1-1.236-1.014,1.217,1.217,0,0,0-1.777-.048l0,0a1.215,1.215,0,0,0-.06,1.667q2.045,2.283,6.1,2.283a8.91,8.91,0,0,0,3.287-.562,4.962,4.962,0,0,0,2.21-1.613,3.953,3.953,0,0,0,.788-2.438,3.855,3.855,0,0,0-.573-2.162,4.274,4.274,0,0,0-1.828-1.458A14.1,14.1,0,0,0,69.312,105.9Zm21.05,6.739H83.2a1,1,0,0,1-1-1v-3.616h6.909a1.076,1.076,0,0,0,0-2.151H82.2v-3.558a1,1,0,0,1,1-1h7.158a1.128,1.128,0,1,0,0-2.256H83.2a3.261,3.261,0,0,0-3.257,3.258v9.326A3.262,3.262,0,0,0,83.2,114.9h7.158a1.129,1.129,0,1,0,0-2.257Z" transform="translate(0 -31.631)" fill="#0c322c"/><path id="Path_647" data-name="Path 647" d="M71.052,51.509a5.542,5.542,0,0,1,.74,1.355,1.519,1.519,0,0,0,.555.9.344.344,0,0,0,.051.023,8.456,8.456,0,0,0,2.051.173h2.716c.231,0,2.271,0,2.22-.23-.244-1.091-1.507-1.286-2.467-1.856a4.391,4.391,0,0,1-2.106-2.156,3.109,3.109,0,0,1,.262-2.2,1.754,1.754,0,0,1,1.012-.624,4.3,4.3,0,0,1,1.33.032c.539.055,1.073.153,1.61.22a20.21,20.21,0,0,0,3.135.162,17.345,17.345,0,0,0,5.1-.882A11.657,11.657,0,0,0,90.5,44.8c1.105-.822.817-.744-.3-.63a25.109,25.109,0,0,1-4.043.078,8.6,8.6,0,0,1-6-2.445.49.49,0,0,1,.022-.625c.188-.2.587-.082.711.022a8.756,8.756,0,0,0,4.994,1.979c1.033.051,2.039.071,3.073.025.516-.023,1.3-.02,1.814-.025.268,0,1,.074,1.134-.21a.566.566,0,0,0,.034-.271c-.151-4.135-.457-8.8-4.784-10.777-3.229-1.476-8.07-3.763-10.115-4.712A.727.727,0,0,0,76,27.869c0,1.382.071,3.366.071,5.172a13.31,13.31,0,0,0-3.886-2.2,36.555,36.555,0,0,0-4.4-1.677A47.18,47.18,0,0,0,58.5,27.336a35.577,35.577,0,0,0-10.7.54,33.069,33.069,0,0,0-15.772,8.042c-2.647,2.495-4.724,6.039-4.865,9.638-.2,5.093,1.226,7.828,3.849,10.646C35.2,60.694,44.2,61.323,47.845,56a8.7,8.7,0,0,0,.806-8.3,7.988,7.988,0,0,0-6.827-4.661,5.726,5.726,0,0,0-5.512,3.152,4.543,4.543,0,0,0,.914,4.788,1.97,1.97,0,0,0,1.791.721,1.014,1.014,0,0,0,.809-.809c.09-.609-.443-1-.771-1.472a2.26,2.26,0,0,1,.269-2.829,3.424,3.424,0,0,1,2.422-.783,4.639,4.639,0,0,1,2.339.556,4.136,4.136,0,0,1,1.861,2.756c.675,3.344-2.043,6.061-5.731,6.276a7.684,7.684,0,0,1-5.278-1.57c-3.728-3-4.642-9.13-.379-12.4,4.045-3.106,9.152-2.305,12.163-.691a14.667,14.667,0,0,1,5.567,5.741,34.622,34.622,0,0,1,1.805,3.649A8.276,8.276,0,0,0,56.138,53.4a3.5,3.5,0,0,0,2.439.562h5.147c.7,0,.529-.466.226-.774a5.3,5.3,0,0,0-2.57-1.105c-2.073-.57-1.862-3.316-1.288-3.316,1.854,0,1.914.055,3.538.034,2.346-.032,3.054-.168,4.887.511A6.094,6.094,0,0,1,71.052,51.509ZM90,36.375a3.9,3.9,0,1,1-2.907-2.9A3.9,3.9,0,0,1,90,36.375Zm-1.658,2.3a1.054,1.054,0,0,1-1.325-1.626,1.034,1.034,0,0,1,1.489,0A1.059,1.059,0,0,1,88.343,38.677Z" transform="translate(-0.016)" fill="#30ba78"/></g></svg> \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/assets/custom/login/suse-logo-light.svg b/src/frontend/packages/suse-extensions/assets/custom/login/suse-logo-light.svg new file mode 100644 index 0000000000..fa3148dd68 --- /dev/null +++ b/src/frontend/packages/suse-extensions/assets/custom/login/suse-logo-light.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="70.917" height="62.04" viewBox="0 0 70.917 62.04"><g id="Group_271" data-name="Group 271" transform="translate(-2151.086 -921.086)"><g id="Path_362" data-name="Path 362"><path id="Path_633" data-name="Path 633" d="M2220.32,980.421h-7.837a1.1,1.1,0,0,1-1.1-1.1v-3.977h7.563a1.183,1.183,0,0,0,0-2.365h-7.563v-3.915a1.1,1.1,0,0,1,1.1-1.1h7.836a1.241,1.241,0,0,0,0-2.482h-7.836a3.578,3.578,0,0,0-3.566,3.583v10.256a3.578,3.578,0,0,0,3.566,3.582h7.836a1.241,1.241,0,0,0,0-2.482m-23.044-7.411a10.42,10.42,0,0,1-3.454-1.064,1.9,1.9,0,0,1-.968-1.669,2.2,2.2,0,0,1,.994-1.852,4.7,4.7,0,0,1,2.8-.723,5.342,5.342,0,0,1,2.931.71,4.31,4.31,0,0,1,1.078,1.017,1.335,1.335,0,0,0,1.872.251c.03-.023.059-.047.087-.072a1.355,1.355,0,0,0,.1-1.9,6.99,6.99,0,0,0-2.028-1.584,8.818,8.818,0,0,0-4.068-.867,8.409,8.409,0,0,0-3.57.71,5.525,5.525,0,0,0-2.343,1.919,4.688,4.688,0,0,0-.811,2.68,4.185,4.185,0,0,0,.641,2.365,4.784,4.784,0,0,0,2.053,1.63,16.313,16.313,0,0,0,3.8,1.1,10.885,10.885,0,0,1,3.335,1.025,1.734,1.734,0,0,1,.956,1.524,2.026,2.026,0,0,1-1.061,1.827,5.594,5.594,0,0,1-2.917.644,6.661,6.661,0,0,1-3.284-.736,4.967,4.967,0,0,1-1.353-1.115,1.327,1.327,0,0,0-1.873-.121c-.025.022-.049.044-.072.068l-.005.005a1.339,1.339,0,0,0-.065,1.832q2.238,2.512,6.677,2.51a9.7,9.7,0,0,0,3.6-.618,5.434,5.434,0,0,0,2.42-1.774,4.367,4.367,0,0,0,.863-2.681,4.26,4.26,0,0,0-.628-2.378,4.671,4.671,0,0,0-2-1.6,15.354,15.354,0,0,0-3.7-1.065m-38.636.005a10.42,10.42,0,0,1-3.453-1.064,1.9,1.9,0,0,1-.969-1.67,2.2,2.2,0,0,1,.995-1.853,4.7,4.7,0,0,1,2.8-.722,5.337,5.337,0,0,1,2.93.71,4.276,4.276,0,0,1,1.078,1.018,1.338,1.338,0,0,0,1.875.248c.029-.023.057-.046.084-.071a1.351,1.351,0,0,0,.1-1.9,7,7,0,0,0-2.028-1.585,8.815,8.815,0,0,0-4.069-.866,8.423,8.423,0,0,0-3.571.709,5.555,5.555,0,0,0-2.341,1.919,4.713,4.713,0,0,0-.81,2.68,4.19,4.19,0,0,0,.64,2.365,4.792,4.792,0,0,0,2.055,1.631,16.374,16.374,0,0,0,3.793,1.1,10.89,10.89,0,0,1,3.337,1.025,1.732,1.732,0,0,1,.953,1.524,2.024,2.024,0,0,1-1.059,1.827,5.615,5.615,0,0,1-2.918.643,6.667,6.667,0,0,1-3.284-.736,4.96,4.96,0,0,1-1.352-1.115,1.328,1.328,0,0,0-1.875-.119c-.025.021-.049.044-.072.067h0a1.343,1.343,0,0,0-.066,1.833q2.239,2.512,6.679,2.51a9.716,9.716,0,0,0,3.6-.617,5.421,5.421,0,0,0,2.421-1.775,4.351,4.351,0,0,0,.863-2.681,4.25,4.25,0,0,0-.628-2.378,4.671,4.671,0,0,0-2-1.6,15.355,15.355,0,0,0-3.7-1.064m25.768-6.339v9.54a6.868,6.868,0,0,1-1.8,5.164,8.785,8.785,0,0,1-10.517,0,6.869,6.869,0,0,1-1.8-5.164v-9.54a1.412,1.412,0,1,1,2.824-.012v9.212a5.391,5.391,0,0,0,1.035,3.641,4.976,4.976,0,0,0,6.409,0,5.39,5.39,0,0,0,1.033-3.641v-9.2a1.413,1.413,0,1,1,2.826-.008v.008" fill="#fff" fill-rule="evenodd"/></g><g id="Path_363" data-name="Path 363"><path id="Path_634" data-name="Path 634" d="M2218.189,932.006a1.126,1.126,0,0,0-1.592-.039l-.039.039a1.168,1.168,0,0,0,.012,1.653,1.114,1.114,0,0,0,.169.138,1.15,1.15,0,0,0,1.268,0,1.171,1.171,0,0,0,.318-1.625,1.154,1.154,0,0,0-.136-.167m-1.491-3.946a4.291,4.291,0,1,0,3.182,3.2,4.293,4.293,0,0,0-3.182-3.2m-20.337,17.46c-2.007-.747-2.783-.6-5.35-.562-1.778.023-1.843-.038-3.873-.038-.627,0-.859,3.026,1.41,3.655a5.794,5.794,0,0,1,2.815,1.217c.33.341.516.854-.248.854h-5.632a3.816,3.816,0,0,1-2.67-.618,9.127,9.127,0,0,1-2.238-3.622,38.121,38.121,0,0,0-1.976-4.022,16.1,16.1,0,0,0-6.094-6.326c-3.3-1.779-8.887-2.662-13.314.761-4.666,3.608-3.666,10.364.415,13.669a8.376,8.376,0,0,0,5.778,1.73c4.036-.236,7.013-3.232,6.274-6.917a4.561,4.561,0,0,0-2.038-3.037,5.074,5.074,0,0,0-2.561-.613,3.745,3.745,0,0,0-2.651.863,2.5,2.5,0,0,0-.294,3.118c.359.516.942.95.844,1.622a1.118,1.118,0,0,1-.886.892,2.15,2.15,0,0,1-1.96-.8,5.03,5.03,0,0,1-1-5.277,6.262,6.262,0,0,1,6.034-3.473,8.748,8.748,0,0,1,7.474,5.137,9.635,9.635,0,0,1-.882,9.148c-3.99,5.87-13.842,5.177-18.419.226-2.872-3.106-4.432-6.12-4.213-11.733.153-3.966,2.427-7.872,5.325-10.621a36.092,36.092,0,0,1,17.264-8.863,38.681,38.681,0,0,1,11.71-.6,51.322,51.322,0,0,1,10.165,2.01,40.154,40.154,0,0,1,4.817,1.848,14.578,14.578,0,0,1,4.254,2.429c0-1.99-.077-4.178-.077-5.7a.8.8,0,0,1,.8-.795.813.813,0,0,1,.322.07c2.237,1.044,7.536,3.566,11.07,5.192,4.737,2.179,5.073,7.32,5.237,11.878a.612.612,0,0,1-.037.3c-.149.313-.947.228-1.24.232-.566.006-1.421,0-1.986.028-1.132.05-2.233.03-3.364-.028a9.537,9.537,0,0,1-5.466-2.182c-.135-.114-.573-.239-.779-.024a.542.542,0,0,0-.024.689,9.368,9.368,0,0,0,6.572,2.694,27.141,27.141,0,0,0,4.425-.086c1.227-.126,1.545-.211.335.7a12.777,12.777,0,0,1-3.55,1.791,18.879,18.879,0,0,1-5.583.972,21.993,21.993,0,0,1-3.432-.177c-.588-.074-1.172-.182-1.763-.243a4.7,4.7,0,0,0-1.454-.035,1.907,1.907,0,0,0-1.108.687,3.438,3.438,0,0,0-.287,2.428,4.823,4.823,0,0,0,2.306,2.376c1.051.628,2.432.843,2.7,2.046.055.252-2.177.259-2.431.254h-2.973a9.239,9.239,0,0,1-2.245-.191.252.252,0,0,1-.056-.025,1.674,1.674,0,0,1-.608-1,6.178,6.178,0,0,0-.81-1.493,6.692,6.692,0,0,0-2.776-2.421m22.51-13.281a3.152,3.152,0,1,1-3.153-3.172,3.152,3.152,0,0,1,3.153,3.172" fill="#30ba78" fill-rule="evenodd"/></g></g></svg> \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/assets/custom/suse_login_logo.svg b/src/frontend/packages/suse-extensions/assets/custom/suse_login_logo.svg deleted file mode 100644 index 7b8bbf809e..0000000000 --- a/src/frontend/packages/suse-extensions/assets/custom/suse_login_logo.svg +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" width="270" height="90" viewBox="0, 0, 270, 90"> - <g id="Layer_1"> - <g> - <path d="M238.462,55.726 L224.907,55.726 C223.862,55.726 223.011,54.876 223.011,53.83 L223.011,46.982 L236.095,46.982 C237.219,46.982 238.131,46.071 238.131,44.946 C238.131,43.821 237.219,42.909 236.095,42.909 L223.011,42.909 L223.011,36.17 C223.011,35.124 223.862,34.273 224.907,34.273 L238.462,34.273 C239.642,34.273 240.6,33.318 240.6,32.137 C240.6,30.957 239.642,30 238.462,30 L224.907,30 C221.507,30 218.738,32.767 218.738,36.17 L218.738,53.83 C218.738,57.232 221.507,59.999 224.907,59.999 L238.462,59.999 C239.642,59.999 240.6,59.044 240.6,57.864 C240.6,56.683 239.642,55.726 238.462,55.726 M198.602,42.965 C195.736,42.45 193.743,41.842 192.628,41.132 C191.511,40.423 190.953,39.466 190.953,38.259 C190.953,36.963 191.526,35.898 192.672,35.068 C193.819,34.24 195.433,33.824 197.516,33.824 C199.628,33.824 201.317,34.23 202.585,35.046 C203.267,35.485 203.888,36.068 204.448,36.798 C205.274,37.866 206.833,38.008 207.837,37.104 C208.784,36.25 208.867,34.786 208.015,33.836 C206.987,32.688 205.818,31.779 204.508,31.109 C202.561,30.113 200.215,29.616 197.471,29.616 C195.117,29.616 193.058,30.022 191.294,30.837 C189.529,31.651 188.177,32.753 187.243,34.14 C186.307,35.529 185.84,37.067 185.84,38.757 C185.84,40.356 186.208,41.713 186.949,42.829 C187.687,43.947 188.873,44.881 190.502,45.635 C192.13,46.39 194.317,47.023 197.063,47.536 C199.809,48.05 201.732,48.637 202.833,49.301 C203.935,49.965 204.485,50.839 204.485,51.924 C204.485,53.283 203.875,54.332 202.651,55.071 C201.431,55.81 199.748,56.18 197.606,56.18 C195.343,56.18 193.45,55.758 191.926,54.912 C191.056,54.429 190.277,53.789 189.587,52.992 C188.719,51.985 187.161,51.961 186.222,52.901 L186.214,52.911 C185.354,53.769 185.288,55.162 186.099,56.066 C188.681,58.948 192.533,60.388 197.651,60.388 C200.004,60.388 202.079,60.033 203.875,59.324 C205.67,58.616 207.063,57.598 208.059,56.271 C209.056,54.943 209.554,53.404 209.554,51.653 C209.554,50.025 209.192,48.66 208.468,47.559 C207.743,46.458 206.59,45.537 205.005,44.799 C203.422,44.059 201.287,43.448 198.602,42.965 M131.77,42.974 C128.904,42.459 126.913,41.85 125.797,41.141 C124.68,40.432 124.123,39.474 124.123,38.266 C124.123,36.97 124.696,35.906 125.842,35.077 C126.989,34.248 128.603,33.832 130.685,33.832 C132.795,33.832 134.486,34.24 135.753,35.054 C136.437,35.493 137.058,36.077 137.618,36.806 C138.442,37.876 140.002,38.017 141.006,37.113 C141.954,36.258 142.036,34.794 141.184,33.845 C140.156,32.696 138.986,31.787 137.677,31.117 C135.731,30.122 133.384,29.624 130.639,29.624 C128.286,29.624 126.227,30.031 124.463,30.845 C122.697,31.659 121.347,32.762 120.412,34.15 C119.477,35.537 119.009,37.075 119.009,38.765 C119.009,40.364 119.377,41.723 120.118,42.837 C120.857,43.954 122.042,44.891 123.67,45.644 C125.299,46.399 127.487,47.032 130.232,47.545 C132.977,48.058 134.9,48.647 136.002,49.31 C137.103,49.973 137.654,50.848 137.654,51.934 C137.654,53.292 137.043,54.34 135.822,55.079 C134.599,55.819 132.917,56.188 130.776,56.188 C128.512,56.188 126.618,55.766 125.095,54.92 C124.226,54.438 123.446,53.798 122.758,53.001 C121.89,51.993 120.331,51.97 119.39,52.911 L119.383,52.919 C118.523,53.777 118.457,55.17 119.268,56.074 C121.85,58.957 125.701,60.396 130.821,60.396 C133.174,60.396 135.248,60.042 137.043,59.333 C138.838,58.625 140.233,57.605 141.229,56.278 C142.225,54.952 142.722,53.411 142.722,51.662 C142.722,50.033 142.36,48.669 141.636,47.567 C140.911,46.465 139.759,45.546 138.173,44.807 C136.59,44.067 134.456,43.457 131.77,42.974 M176.343,32.059 L176.343,48.487 C176.343,52.408 175.302,55.372 173.221,57.379 C171.139,59.386 168.107,60.388 164.126,60.388 C160.142,60.388 157.11,59.386 155.028,57.379 C152.947,55.372 151.907,52.408 151.907,48.487 L151.907,32.059 C151.907,30.709 152.999,29.616 154.349,29.616 C155.698,29.616 156.794,30.709 156.794,32.059 L156.794,47.898 C156.794,50.735 157.389,52.824 158.581,54.166 C159.773,55.508 161.62,56.18 164.126,56.18 C166.63,56.18 168.476,55.508 169.668,54.166 C170.86,52.824 171.456,50.735 171.456,47.898 L171.456,32.059 C171.456,30.709 172.551,29.616 173.899,29.616 C175.249,29.616 176.343,30.709 176.343,32.059" fill="#0C322C"/> - <path d="M101.408,42.16 C101.003,42.429 100.461,42.429 100.055,42.16 C99.391,41.719 99.327,40.797 99.863,40.264 C100.339,39.771 101.124,39.771 101.6,40.263 C102.135,40.797 102.07,41.719 101.408,42.16 M103.344,39.473 C104.116,42.757 101.164,45.71 97.88,44.938 C96.208,44.546 94.881,43.219 94.488,41.548 C93.718,38.266 96.669,35.315 99.952,36.084 C101.623,36.475 102.951,37.801 103.344,39.473 M81.232,57.135 C81.607,57.674 81.919,58.195 82.094,58.716 C82.218,59.086 82.377,59.574 82.742,59.77 C82.762,59.781 82.78,59.791 82.802,59.797 C83.472,60.041 85.195,60 85.195,60 L88.364,60 C88.635,60.004 91.016,59.997 90.956,59.731 C90.669,58.458 89.197,58.23 88.076,57.564 C87.043,56.948 86.063,56.25 85.619,55.049 C85.387,54.429 85.524,52.999 85.923,52.478 C86.214,52.101 86.643,51.851 87.104,51.75 C87.615,51.641 88.145,51.735 88.656,51.787 C89.286,51.851 89.908,51.965 90.536,52.043 C91.748,52.201 92.972,52.264 94.194,52.231 C96.211,52.175 98.233,51.854 100.145,51.202 C101.48,50.754 102.795,50.148 103.93,49.305 C105.22,48.346 104.881,48.436 103.573,48.57 C102.007,48.73 100.427,48.754 98.856,48.661 C97.39,48.576 95.944,48.403 94.618,47.722 C93.574,47.183 92.677,46.643 91.849,45.808 C91.725,45.682 91.648,45.314 91.875,45.079 C92.095,44.851 92.561,44.983 92.704,45.105 C94.148,46.312 96.302,47.306 98.532,47.414 C99.738,47.474 100.911,47.496 102.118,47.444 C102.721,47.416 103.631,47.42 104.236,47.414 C104.548,47.41 105.399,47.5 105.558,47.169 C105.606,47.073 105.602,46.962 105.598,46.854 C105.421,42.028 105.064,36.585 100.014,34.278 C96.246,32.555 90.597,29.886 88.211,28.779 C87.658,28.516 87.011,28.932 87.011,29.548 C87.011,31.16 87.093,33.476 87.094,35.584 C85.951,34.42 84.026,33.685 82.559,33.011 C80.893,32.247 79.173,31.599 77.423,31.054 C73.9,29.963 70.254,29.291 66.587,28.927 C62.428,28.512 58.199,28.711 54.104,29.557 C47.36,30.955 40.731,34.198 35.699,38.941 C32.61,41.851 30.186,45.988 30.022,50.187 C29.788,56.13 31.453,59.321 34.513,62.61 C39.393,67.852 49.896,68.586 54.15,62.37 C56.064,59.572 56.479,55.777 55.09,52.684 C53.701,49.593 50.508,47.359 47.123,47.245 C44.496,47.158 41.697,48.494 40.69,50.923 C39.922,52.778 40.359,55.07 41.758,56.51 C42.303,57.072 43.041,57.532 43.847,57.352 C44.322,57.246 44.719,56.889 44.791,56.407 C44.897,55.696 44.275,55.235 43.892,54.689 C43.201,53.704 43.341,52.225 44.206,51.388 C44.936,50.681 46.017,50.472 47.033,50.475 C47.979,50.477 48.946,50.646 49.762,51.123 C50.909,51.798 51.671,53.034 51.935,54.34 C52.722,58.243 49.549,61.414 45.247,61.663 C43.046,61.793 40.806,61.215 39.088,59.832 C34.737,56.332 33.671,49.178 38.644,45.359 C43.365,41.734 49.325,42.668 52.839,44.552 C55.651,46.059 57.747,48.525 59.335,51.251 C60.132,52.622 60.811,54.055 61.441,55.51 C62.047,56.909 62.614,58.319 63.827,59.344 C64.63,60.024 65.621,60 66.673,60 L72.678,60 C73.494,60 73.295,59.456 72.943,59.096 C72.147,58.282 71.003,58.098 69.944,57.807 C67.523,57.141 67.77,53.936 68.44,53.936 C70.604,53.936 70.672,54.001 72.568,53.977 C75.304,53.939 76.132,53.78 78.272,54.571 C79.416,54.995 80.515,56.113 81.232,57.135" fill="#30BA78"/> - </g> - </g> -</svg> diff --git a/src/frontend/packages/suse-extensions/assets/custom/suse_logo.svg b/src/frontend/packages/suse-extensions/assets/custom/suse_logo.svg deleted file mode 100644 index 140888bd1a..0000000000 --- a/src/frontend/packages/suse-extensions/assets/custom/suse_logo.svg +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" width="90" height="30" viewBox="0, 0, 90, 30"> - <g id="Layer_1"> - <g> - <path d="M33.238,14.348 C33.06,14.466 32.821,14.466 32.643,14.348 C32.351,14.154 32.323,13.748 32.559,13.513 C32.768,13.296 33.114,13.296 33.323,13.513 C33.558,13.748 33.53,14.154 33.238,14.348 M33.936,13.378 C34.208,14.532 33.17,15.571 32.015,15.299 C31.428,15.161 30.961,14.695 30.823,14.107 C30.552,12.953 31.59,11.916 32.744,12.187 C33.331,12.324 33.798,12.79 33.936,13.378 M34.214,16.233 C33.789,16.195 33.805,15.69 34.33,15.743 C34.488,15.759 35.006,15.778 35.14,15.61 C35.201,15.534 35.192,15.387 35.19,15.351 C35.112,13.99 35.021,12.195 33.338,11.426 C32.082,10.852 30.199,9.962 29.404,9.593 C29.219,9.506 29.004,9.644 29.004,9.849 C29.004,10.344 29.027,10.881 29.027,11.57 C29.027,11.702 29.048,11.968 28.996,11.997 C28.947,12.024 28.874,11.822 28.694,11.671 C28.388,11.413 27.97,11.211 27.52,11.004 C26.964,10.749 26.391,10.533 25.808,10.352 C24.633,9.988 23.418,9.764 22.195,9.642 C20.81,9.504 19.4,9.57 18.034,9.852 C15.787,10.318 13.577,11.4 11.9,12.98 C10.87,13.951 10.062,15.329 10.007,16.729 C9.929,18.71 10.484,19.774 11.505,20.87 C13.131,22.617 16.632,22.862 18.05,20.79 C18.688,19.857 18.826,18.592 18.363,17.561 C17.9,16.531 16.836,15.786 15.708,15.748 C14.832,15.719 13.899,16.165 13.564,16.974 C13.307,17.592 13.453,18.356 13.919,18.837 C14.101,19.024 14.347,19.177 14.616,19.117 C14.774,19.082 14.906,18.963 14.93,18.802 C14.966,18.565 14.758,18.412 14.63,18.23 C14.4,17.901 14.447,17.408 14.735,17.129 C14.979,16.894 15.339,16.824 15.678,16.825 C15.993,16.825 16.315,16.882 16.587,17.041 C16.97,17.266 17.224,17.678 17.312,18.113 C17.574,19.414 16.517,20.471 15.082,20.555 C14.349,20.598 13.602,20.405 13.029,19.944 C11.579,18.778 11.224,16.393 12.881,15.12 C14.455,13.911 16.442,14.223 17.613,14.851 C18.55,15.353 19.249,16.175 19.778,17.084 C20.044,17.541 20.27,18.018 20.48,18.503 C20.682,18.97 20.871,19.44 21.276,19.781 C21.543,20.008 21.874,20 22.224,20 L24.226,20 C24.498,20 24.432,19.819 24.314,19.699 C24.049,19.427 23.668,19.366 23.314,19.269 C22.508,19.047 22.59,17.979 22.813,17.979 C23.535,17.979 23.558,18 24.189,17.992 C25.102,17.98 25.377,17.927 26.09,18.191 C26.472,18.332 26.838,18.705 27.077,19.045 C27.202,19.225 27.306,19.398 27.365,19.572 C27.406,19.695 27.459,19.858 27.581,19.923 C27.587,19.927 27.594,19.93 27.601,19.932 C27.824,20.014 28.398,20 28.398,20 L29.455,20 C29.545,20.001 30.339,19.999 30.319,19.91 C30.223,19.486 29.732,19.41 29.359,19.188 C29.014,18.983 28.688,18.75 28.539,18.35 C28.462,18.143 28.508,17.666 28.641,17.492 C28.738,17.367 28.881,17.284 29.035,17.25 C29.205,17.213 29.382,17.245 29.552,17.262 C29.762,17.284 29.969,17.322 30.178,17.348 C30.583,17.4 30.991,17.422 31.398,17.411 C32.07,17.392 32.744,17.284 33.382,17.067 C33.827,16.918 33.848,16.89 34.269,16.68 C34.909,16.36 34.434,16.253 34.214,16.233" fill="#30BA78"/> - <path d="M74.969,19.927 C73.874,19.927 72.983,19.036 72.983,17.941 L72.983,12.055 C72.983,10.96 73.874,10.069 74.969,10.069 L79.488,10.069 C79.842,10.069 80.13,10.357 80.13,10.71 C80.13,11.064 79.842,11.352 79.488,11.352 L74.969,11.352 C74.582,11.352 74.267,11.667 74.267,12.055 L74.267,14.372 L78.698,14.372 C79.034,14.372 79.306,14.645 79.306,14.98 C79.306,15.315 79.034,15.588 78.698,15.588 L74.267,15.588 L74.267,17.941 C74.267,18.329 74.582,18.644 74.969,18.644 L79.488,18.644 C79.842,18.644 80.13,18.932 80.13,19.286 C80.13,19.64 79.842,19.927 79.488,19.927 z M54.709,20.057 C53.406,20.057 52.402,19.726 51.725,19.073 C51.049,18.422 50.707,17.442 50.707,16.16 L50.707,10.684 C50.707,10.274 51.04,9.94 51.45,9.94 C51.86,9.94 52.194,10.274 52.194,10.684 L52.194,15.964 C52.194,16.923 52.401,17.642 52.808,18.1 C53.218,18.561 53.857,18.795 54.709,18.795 C55.56,18.795 56.199,18.561 56.609,18.1 C57.016,17.642 57.223,16.923 57.223,15.964 L57.223,10.684 C57.223,10.274 57.557,9.94 57.967,9.94 C58.377,9.94 58.711,10.274 58.711,10.684 L58.711,16.16 C58.711,17.441 58.368,18.421 57.691,19.073 C57.015,19.726 56.012,20.057 54.709,20.057 M65.884,20.057 C64.206,20.057 62.928,19.58 62.086,18.64 C61.842,18.368 61.858,17.948 62.121,17.685 L62.124,17.682 L62.127,17.679 C62.26,17.547 62.438,17.474 62.626,17.474 C62.826,17.474 63.014,17.559 63.143,17.708 C63.378,17.979 63.646,18.2 63.942,18.364 C64.457,18.65 65.106,18.795 65.869,18.795 C66.594,18.795 67.172,18.667 67.587,18.415 C68.015,18.156 68.233,17.783 68.233,17.306 C68.233,16.92 68.036,16.605 67.648,16.371 C67.271,16.144 66.634,15.949 65.701,15.774 C64.795,15.605 64.065,15.394 63.53,15.146 C63.002,14.901 62.613,14.595 62.376,14.235 C62.138,13.878 62.018,13.434 62.018,12.917 C62.018,12.37 62.171,11.866 62.473,11.417 C62.776,10.968 63.221,10.606 63.794,10.341 C64.371,10.075 65.054,9.94 65.824,9.94 C66.724,9.94 67.502,10.105 68.137,10.43 C68.564,10.648 68.95,10.949 69.287,11.324 C69.543,11.611 69.519,12.055 69.232,12.314 C69.103,12.43 68.936,12.494 68.763,12.494 C68.543,12.494 68.34,12.394 68.206,12.221 C68.015,11.973 67.8,11.771 67.567,11.621 C67.135,11.343 66.554,11.202 65.839,11.202 C65.134,11.202 64.577,11.346 64.184,11.63 C63.784,11.919 63.581,12.297 63.581,12.751 C63.581,13.177 63.78,13.52 64.172,13.768 C64.553,14.011 65.213,14.214 66.188,14.389 C67.074,14.548 67.786,14.752 68.305,14.995 C68.818,15.234 69.197,15.535 69.431,15.89 C69.663,16.244 69.781,16.69 69.781,17.216 C69.781,17.781 69.618,18.285 69.297,18.713 C68.974,19.143 68.515,19.478 67.933,19.707 C67.346,19.939 66.657,20.057 65.884,20.057 M43.607,20.059 C41.929,20.059 40.651,19.582 39.809,18.642 C39.565,18.371 39.581,17.951 39.844,17.687 L39.848,17.683 C39.982,17.55 40.16,17.477 40.35,17.477 C40.55,17.477 40.738,17.562 40.866,17.711 C41.1,17.982 41.368,18.202 41.664,18.366 C42.18,18.653 42.829,18.798 43.592,18.798 C44.316,18.798 44.894,18.67 45.311,18.418 C45.739,18.159 45.956,17.786 45.956,17.309 C45.956,16.923 45.759,16.608 45.371,16.374 C44.993,16.147 44.356,15.951 43.424,15.776 C42.518,15.608 41.788,15.396 41.253,15.149 C40.725,14.904 40.336,14.597 40.098,14.238 C39.861,13.881 39.741,13.437 39.741,12.92 C39.741,12.373 39.894,11.869 40.196,11.421 C40.499,10.971 40.944,10.609 41.517,10.344 C42.093,10.078 42.776,9.943 43.547,9.943 C44.447,9.943 45.225,10.108 45.86,10.433 C46.287,10.652 46.674,10.952 47.009,11.327 C47.266,11.614 47.242,12.057 46.955,12.317 C46.826,12.433 46.659,12.497 46.485,12.497 C46.266,12.497 46.063,12.397 45.929,12.224 C45.739,11.976 45.523,11.774 45.29,11.623 C44.857,11.345 44.276,11.205 43.562,11.205 C42.856,11.205 42.3,11.349 41.906,11.633 C41.507,11.923 41.304,12.3 41.304,12.754 C41.304,13.18 41.503,13.522 41.895,13.771 C42.276,14.014 42.935,14.217 43.912,14.392 C44.796,14.551 45.508,14.754 46.029,14.997 C46.542,15.237 46.92,15.539 47.153,15.892 C47.386,16.246 47.504,16.692 47.504,17.219 C47.504,17.784 47.341,18.288 47.02,18.715 C46.697,19.146 46.238,19.481 45.655,19.71 C45.068,19.942 44.379,20.059 43.607,20.059" fill="#FFFFFF"/> - </g> - </g> -</svg> diff --git a/src/frontend/packages/suse-extensions/assets/suse_login_logo.svg b/src/frontend/packages/suse-extensions/assets/suse_login_logo.svg deleted file mode 100644 index 7b8bbf809e..0000000000 --- a/src/frontend/packages/suse-extensions/assets/suse_login_logo.svg +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" width="270" height="90" viewBox="0, 0, 270, 90"> - <g id="Layer_1"> - <g> - <path d="M238.462,55.726 L224.907,55.726 C223.862,55.726 223.011,54.876 223.011,53.83 L223.011,46.982 L236.095,46.982 C237.219,46.982 238.131,46.071 238.131,44.946 C238.131,43.821 237.219,42.909 236.095,42.909 L223.011,42.909 L223.011,36.17 C223.011,35.124 223.862,34.273 224.907,34.273 L238.462,34.273 C239.642,34.273 240.6,33.318 240.6,32.137 C240.6,30.957 239.642,30 238.462,30 L224.907,30 C221.507,30 218.738,32.767 218.738,36.17 L218.738,53.83 C218.738,57.232 221.507,59.999 224.907,59.999 L238.462,59.999 C239.642,59.999 240.6,59.044 240.6,57.864 C240.6,56.683 239.642,55.726 238.462,55.726 M198.602,42.965 C195.736,42.45 193.743,41.842 192.628,41.132 C191.511,40.423 190.953,39.466 190.953,38.259 C190.953,36.963 191.526,35.898 192.672,35.068 C193.819,34.24 195.433,33.824 197.516,33.824 C199.628,33.824 201.317,34.23 202.585,35.046 C203.267,35.485 203.888,36.068 204.448,36.798 C205.274,37.866 206.833,38.008 207.837,37.104 C208.784,36.25 208.867,34.786 208.015,33.836 C206.987,32.688 205.818,31.779 204.508,31.109 C202.561,30.113 200.215,29.616 197.471,29.616 C195.117,29.616 193.058,30.022 191.294,30.837 C189.529,31.651 188.177,32.753 187.243,34.14 C186.307,35.529 185.84,37.067 185.84,38.757 C185.84,40.356 186.208,41.713 186.949,42.829 C187.687,43.947 188.873,44.881 190.502,45.635 C192.13,46.39 194.317,47.023 197.063,47.536 C199.809,48.05 201.732,48.637 202.833,49.301 C203.935,49.965 204.485,50.839 204.485,51.924 C204.485,53.283 203.875,54.332 202.651,55.071 C201.431,55.81 199.748,56.18 197.606,56.18 C195.343,56.18 193.45,55.758 191.926,54.912 C191.056,54.429 190.277,53.789 189.587,52.992 C188.719,51.985 187.161,51.961 186.222,52.901 L186.214,52.911 C185.354,53.769 185.288,55.162 186.099,56.066 C188.681,58.948 192.533,60.388 197.651,60.388 C200.004,60.388 202.079,60.033 203.875,59.324 C205.67,58.616 207.063,57.598 208.059,56.271 C209.056,54.943 209.554,53.404 209.554,51.653 C209.554,50.025 209.192,48.66 208.468,47.559 C207.743,46.458 206.59,45.537 205.005,44.799 C203.422,44.059 201.287,43.448 198.602,42.965 M131.77,42.974 C128.904,42.459 126.913,41.85 125.797,41.141 C124.68,40.432 124.123,39.474 124.123,38.266 C124.123,36.97 124.696,35.906 125.842,35.077 C126.989,34.248 128.603,33.832 130.685,33.832 C132.795,33.832 134.486,34.24 135.753,35.054 C136.437,35.493 137.058,36.077 137.618,36.806 C138.442,37.876 140.002,38.017 141.006,37.113 C141.954,36.258 142.036,34.794 141.184,33.845 C140.156,32.696 138.986,31.787 137.677,31.117 C135.731,30.122 133.384,29.624 130.639,29.624 C128.286,29.624 126.227,30.031 124.463,30.845 C122.697,31.659 121.347,32.762 120.412,34.15 C119.477,35.537 119.009,37.075 119.009,38.765 C119.009,40.364 119.377,41.723 120.118,42.837 C120.857,43.954 122.042,44.891 123.67,45.644 C125.299,46.399 127.487,47.032 130.232,47.545 C132.977,48.058 134.9,48.647 136.002,49.31 C137.103,49.973 137.654,50.848 137.654,51.934 C137.654,53.292 137.043,54.34 135.822,55.079 C134.599,55.819 132.917,56.188 130.776,56.188 C128.512,56.188 126.618,55.766 125.095,54.92 C124.226,54.438 123.446,53.798 122.758,53.001 C121.89,51.993 120.331,51.97 119.39,52.911 L119.383,52.919 C118.523,53.777 118.457,55.17 119.268,56.074 C121.85,58.957 125.701,60.396 130.821,60.396 C133.174,60.396 135.248,60.042 137.043,59.333 C138.838,58.625 140.233,57.605 141.229,56.278 C142.225,54.952 142.722,53.411 142.722,51.662 C142.722,50.033 142.36,48.669 141.636,47.567 C140.911,46.465 139.759,45.546 138.173,44.807 C136.59,44.067 134.456,43.457 131.77,42.974 M176.343,32.059 L176.343,48.487 C176.343,52.408 175.302,55.372 173.221,57.379 C171.139,59.386 168.107,60.388 164.126,60.388 C160.142,60.388 157.11,59.386 155.028,57.379 C152.947,55.372 151.907,52.408 151.907,48.487 L151.907,32.059 C151.907,30.709 152.999,29.616 154.349,29.616 C155.698,29.616 156.794,30.709 156.794,32.059 L156.794,47.898 C156.794,50.735 157.389,52.824 158.581,54.166 C159.773,55.508 161.62,56.18 164.126,56.18 C166.63,56.18 168.476,55.508 169.668,54.166 C170.86,52.824 171.456,50.735 171.456,47.898 L171.456,32.059 C171.456,30.709 172.551,29.616 173.899,29.616 C175.249,29.616 176.343,30.709 176.343,32.059" fill="#0C322C"/> - <path d="M101.408,42.16 C101.003,42.429 100.461,42.429 100.055,42.16 C99.391,41.719 99.327,40.797 99.863,40.264 C100.339,39.771 101.124,39.771 101.6,40.263 C102.135,40.797 102.07,41.719 101.408,42.16 M103.344,39.473 C104.116,42.757 101.164,45.71 97.88,44.938 C96.208,44.546 94.881,43.219 94.488,41.548 C93.718,38.266 96.669,35.315 99.952,36.084 C101.623,36.475 102.951,37.801 103.344,39.473 M81.232,57.135 C81.607,57.674 81.919,58.195 82.094,58.716 C82.218,59.086 82.377,59.574 82.742,59.77 C82.762,59.781 82.78,59.791 82.802,59.797 C83.472,60.041 85.195,60 85.195,60 L88.364,60 C88.635,60.004 91.016,59.997 90.956,59.731 C90.669,58.458 89.197,58.23 88.076,57.564 C87.043,56.948 86.063,56.25 85.619,55.049 C85.387,54.429 85.524,52.999 85.923,52.478 C86.214,52.101 86.643,51.851 87.104,51.75 C87.615,51.641 88.145,51.735 88.656,51.787 C89.286,51.851 89.908,51.965 90.536,52.043 C91.748,52.201 92.972,52.264 94.194,52.231 C96.211,52.175 98.233,51.854 100.145,51.202 C101.48,50.754 102.795,50.148 103.93,49.305 C105.22,48.346 104.881,48.436 103.573,48.57 C102.007,48.73 100.427,48.754 98.856,48.661 C97.39,48.576 95.944,48.403 94.618,47.722 C93.574,47.183 92.677,46.643 91.849,45.808 C91.725,45.682 91.648,45.314 91.875,45.079 C92.095,44.851 92.561,44.983 92.704,45.105 C94.148,46.312 96.302,47.306 98.532,47.414 C99.738,47.474 100.911,47.496 102.118,47.444 C102.721,47.416 103.631,47.42 104.236,47.414 C104.548,47.41 105.399,47.5 105.558,47.169 C105.606,47.073 105.602,46.962 105.598,46.854 C105.421,42.028 105.064,36.585 100.014,34.278 C96.246,32.555 90.597,29.886 88.211,28.779 C87.658,28.516 87.011,28.932 87.011,29.548 C87.011,31.16 87.093,33.476 87.094,35.584 C85.951,34.42 84.026,33.685 82.559,33.011 C80.893,32.247 79.173,31.599 77.423,31.054 C73.9,29.963 70.254,29.291 66.587,28.927 C62.428,28.512 58.199,28.711 54.104,29.557 C47.36,30.955 40.731,34.198 35.699,38.941 C32.61,41.851 30.186,45.988 30.022,50.187 C29.788,56.13 31.453,59.321 34.513,62.61 C39.393,67.852 49.896,68.586 54.15,62.37 C56.064,59.572 56.479,55.777 55.09,52.684 C53.701,49.593 50.508,47.359 47.123,47.245 C44.496,47.158 41.697,48.494 40.69,50.923 C39.922,52.778 40.359,55.07 41.758,56.51 C42.303,57.072 43.041,57.532 43.847,57.352 C44.322,57.246 44.719,56.889 44.791,56.407 C44.897,55.696 44.275,55.235 43.892,54.689 C43.201,53.704 43.341,52.225 44.206,51.388 C44.936,50.681 46.017,50.472 47.033,50.475 C47.979,50.477 48.946,50.646 49.762,51.123 C50.909,51.798 51.671,53.034 51.935,54.34 C52.722,58.243 49.549,61.414 45.247,61.663 C43.046,61.793 40.806,61.215 39.088,59.832 C34.737,56.332 33.671,49.178 38.644,45.359 C43.365,41.734 49.325,42.668 52.839,44.552 C55.651,46.059 57.747,48.525 59.335,51.251 C60.132,52.622 60.811,54.055 61.441,55.51 C62.047,56.909 62.614,58.319 63.827,59.344 C64.63,60.024 65.621,60 66.673,60 L72.678,60 C73.494,60 73.295,59.456 72.943,59.096 C72.147,58.282 71.003,58.098 69.944,57.807 C67.523,57.141 67.77,53.936 68.44,53.936 C70.604,53.936 70.672,54.001 72.568,53.977 C75.304,53.939 76.132,53.78 78.272,54.571 C79.416,54.995 80.515,56.113 81.232,57.135" fill="#30BA78"/> - </g> - </g> -</svg> diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.html b/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.html index 66a45dbc77..57577ea756 100644 --- a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.html @@ -1,43 +1,58 @@ -<div class="suse-login" id="app-login-page"> - <div class="suse-login__header"> - <div class="suse-login__title">SUSE Stratos Console</div> - </div> - <div class="suse-login__content" [ngClass]="{'suse-login-sso': ssoLogin}"> +<div class="suse-login" id="app-login-page" [ngClass]="{'suse-login-sso': ssoLogin}" > + <div class="suse-login__content"> <div class="suse-login__intro"> - <h1 class="suse-login__headline">SUSE<br>Stratos<br>Console</h1> + <img class="suse-login__intro-img-left" src="/core/assets/custom/login/suse-logo-light.svg"> + <img class="suse-login__intro-img" src="/core/assets/custom/login/sign-in-lock-1.svg"> + <h1 class="suse-login__headline">SUSE<br>Stratos Console</h1> <p class="suse-login__tagline">Manage and monitor Cloud Foundry and Kubernetes systems and workloads.</p> - </div> - <div class="suse-login__box" [ngClass]="{'suse-login__busy': busy$ | async }"> - <div *ngIf="!ssoLogin" class="suse-login__form-title">Sign in</div> - <div class="suse-login__form-outer"> - <form class="suse-login__form" name="loginForm" (ngSubmit)="login()" #loginForm="ngForm"> - <mat-form-field *ngIf="!ssoLogin" [hideRequiredMarker]="true"> - <input matInput required [(ngModel)]="username" name="username" placeholder="Username"> - </mat-form-field> - <mat-form-field *ngIf="!ssoLogin" [hideRequiredMarker]="true"> - <input matInput required type="password" [(ngModel)]="password" name="password" placeholder="Password"> - </mat-form-field> - <button class="suse-login__submit" color="primary" *ngIf="!loggedIn" type="submit" mat-button - mat-raised-button [disabled]="!loginForm.valid"> - {{ !ssoLogin ? 'Login' : 'Login with SSO' }} - </button> - </form> + <div class="suse-login__line"> + <img src="/core/assets/custom/login/sign-in-line.svg"> </div> - <div id="login__loading" class="suse-login__loading"> - <mat-progress-bar mode="indeterminate"></mat-progress-bar> + <div *ngIf="ssoLogin" class="suse-login__sso-container" [ngClass]="{'suse-login__busy': busy$ | async }"> + <ng-container *ngTemplateOutlet="loginform"></ng-container> </div> - <div id="login-error-message" class="suse-login__message" - [ngClass]="{'suse-login__message--show': !!message, 'suse-login__message-error': this.error}"> - {{ message }} + <div class="suse-login__footer"> + <div class="suse-login__copyright"> + <span *ngIf="config.copyright" [innerHTML]="config.copyright"></span> + </div> </div> </div> - </div> - <div class="suse-login__footer"> - <div class="suse-login__copyright"> - <span *ngIf="config.copyright" [innerHTML]="config.copyright"></span> - </div> - <div class="suse-login__logo"> - <a href="https://www.suse.com" target="_blank"><img src="/core/assets/custom/suse_login_logo.svg"></a> + <div class="suse-login__right" *ngIf="!ssoLogin"> + <img class="suse-login__logo-mobile" src="/core/assets/custom/login/suse-logo-dark.svg"> + <div class="suse-login__footer-mobile"> + <div class="suse-login__copyright"> + <span *ngIf="config.copyright" [innerHTML]="config.copyright"></span> + </div> + </div> + + <div class="suse-login__box" [ngClass]="{'suse-login__busy': busy$ | async }"> + <div *ngIf="!ssoLogin" class="suse-login__form-title">Sign in</div> + <ng-container *ngTemplateOutlet="loginform"></ng-container> + </div> </div> </div> -</div> \ No newline at end of file +</div> + +<ng-template #loginform> + <div class="suse-login__form-outer"> + <form class="suse-login__form" name="loginForm" (ngSubmit)="login()" #loginForm="ngForm"> + <mat-form-field *ngIf="!ssoLogin" [hideRequiredMarker]="true"> + <input matInput required [(ngModel)]="username" name="username" placeholder="Username"> + </mat-form-field> + <mat-form-field *ngIf="!ssoLogin" [hideRequiredMarker]="true"> + <input matInput required type="password" [(ngModel)]="password" name="password" placeholder="Password"> + </mat-form-field> + <button class="suse-login__submit" [ngClass]="{'suse-login__sso-button': ssoLogin}" color="primary" *ngIf="!loggedIn" type="submit" mat-button + mat-raised-button [disabled]="!loginForm.valid"> + {{ !ssoLogin ? 'Login' : 'Login with SSO' }} + </button> + </form> + </div> + <div id="login__loading" class="suse-login__loading"> + <mat-progress-bar mode="indeterminate"></mat-progress-bar> + </div> + <div id="login-error-message" class="suse-login__message" + [ngClass]="{'suse-login__message--show': !!message, 'suse-login__message-error': this.error}"> + {{ message }} + </div> +</ng-template> \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss b/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss index c84aae5cfa..a7b3338ac5 100644 --- a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss @@ -1,13 +1,21 @@ @import '../../../../suse-theme/sass/colors'; +app-suse-login { + background: url(/core/assets/custom/login/sign-in-bg.svg) #7AD4AA no-repeat center fixed; + background-size: cover; + display: flex; + justify-content: center; + height: 100vh; + width: 100%; +} + .suse-login { align-items: center; - background-color: $suse-gray; + align-self: center; + background-color: #fff; color: $suse-gray-fg; display: flex; flex-direction: column; - width: 100%; - height: 100%; &__header, &__content, &__footer { @@ -28,43 +36,60 @@ align-items: flex-start; color: $suse-text; flex: 1; - margin-top: 100px; - overflow-y: auto; - width: 90%; + width: 1120px; } &__footer { flex: 0 0 auto; - width: 90%; - padding: 20px 0; + padding: 20px 0 0 0; } &__title { font-family: "Work Sans"; width: 90%; } &__copyright { - color: $suse-gray-fg; flex: 1; + font-size: 12px; + opacity: 0.7; } &__logo { img { width: 128px; } } + &__footer-mobile { + display: none; + } &__intro { - flex: 1; + background-color: $suse-dark-green; + display: flex; + flex: 0 0 445px; + flex-direction: column; font-family: "Work Sans"; - margin-right: 24px; + padding: 32px; + &-img { + margin-bottom: 1rem; + } + &-img-left { + align-self: flex-start; + margin-bottom: 1rem; + } + } + &__line { + margin: 32px -32px; + overflow: hidden; } &__box { display: flex; - flex: 1; flex-direction: column; justify-content: center; - margin-left: 24px; + width: 75%; } &__form { display: flex; flex-direction: column; + .mat-form-field { + margin-bottom: 12px; + } .mat-form-field:not(.mat-form-field-invalid).mat-form-field-appearance-legacy { color: $suse-gray-fg; .mat-form-field-label { @@ -81,37 +106,44 @@ } margin-top: 24px; } + &__right { + align-self: center; + display: flex; + justify-content: center; + flex: 1; + } + &__logo-mobile { + display: none; + } &__headline { color: $suse-primary-color; font-family: "Work Sans"; - font-size: 52px; + font-size: 28px; font-weight: 300; + line-height: 1.5; margin: 0; + margin-bottom: .5rem; } &__tagline { - color: $suse-gray-fg; font-family: "Work Sans"; - font-size: 18px; + font-size: 14px; font-weight: 300; line-height: 1.5; - margin: 24px 0; + margin: 0; } &__form-outer { - max-width: 400px; opacity: 1; transition: opacity 250ms linear; } &__form-title { - color: $suse-gray-fg; - font-size: 30px; + color: $suse-primary-color; + font-size: 28px; font-weight: 300; margin-bottom: 24px; transition: opacity 250ms linear; } &__loading { - display: none; margin-top: 24px; - max-width: 400px; opacity: 0; transition: opacity 250ms linear; } @@ -129,42 +161,103 @@ } &__message { color: $suse-gray-fg; - font-size: 18px; - font-weight: 300; - height: 20px; + font-size: 14px; + height: 24px; max-width: 400px; padding-top: 20px; } + + &__sso-button.mat-button.suse-login__submit { + align-self: center; + padding: 0 60px; + } + } -// If SSO Login, move the form underneath the intro +// If SSO Login change the presentation .suse-login-sso { - &.suse-login__content { - flex-direction: column; - .suse-login__intro { - flex: 0; - } - .suse-login__box { - justify-content: flex-start; - margin-left: 0; - } + .suse-login__intro { + flex: 1; + }; + .suse-login__line IMG { + height: 0; + padding: 20px; + } + .suse-login__sso-button.mat-button.suse-login__submit { + align-self: flex-start; + } + + +} + +@media only screen and (max-width: 1120px) { + + .suse-login { + align-self: unset; + height: 100%; + width: 100%; + } + + .suse-login__content { + width: 100%; + } + + .suse-login__intro { + flex: 0; + height: 100%; + width: 335px; + } + + .suse-login__right { + align-self: unset; + margin-top: 120px; } } -// On small screens, hide the intro and just show the login box -@media(max-width: 768px) { - .suse-login:not(.suse-login-sso) { +// Mobile - login form only (unless SSO) +@media only screen and (max-width: 768px) { + + .suse-login:not(.suse-login-sso) { + .suse-login { - &__intro { - display: none; - } - &__box { - margin: 0; - } - &__content { - max-width: 400px; - width: 80%; - } + + &__intro { + display: none; + } + + &__logo-mobile { + display: inline; + left: 30px; + position: absolute; + top: 30px; + } + + &__footer-mobile { + align-items: flex-end; + background-color: #fff; + bottom: 0; + color: #000; + display: flex; + height: 60px; + padding: 30px; + position: fixed; + width: 100%; + z-index: 1; + } + + &__right { + margin-bottom: 60px; } } } +} + + +// Sticky copyright footer +@media only screen and (min-height: 580px) and (max-width: 1120px) { + + .suse-login__footer { + position: fixed; + bottom: 20px; + } +} From 008570bbfcd0863411b123639af1b8af79053d2e Mon Sep 17 00:00:00 2001 From: Neil MacDougall <nwmac@users.noreply.github.com> Date: Thu, 27 Aug 2020 15:35:07 +0100 Subject: [PATCH 600/648] Show caasp node versions and indicate if nodes have updates available (#451) * Show caasp node versions and indicate if nodes have updates available * Fix hasBooleanAnnoation typo, ensure CAASP_SECURITY_UPDATES_ANNOTATION is used * Add caasp update info to nodes list and node summary * Fix boolean-indicator bug - subtle, if set late, was not taken into account * Move caasp update info to the summary card * Change title of summary card to general information - avoids summary title x 2 (page title and card title) * Revert "Move caasp update info to the summary card" This reverts commit 82212987e878248fef613cb8c37188ca66b23c7f. * Move caasp update info into summary container Co-authored-by: Richard Cox <richard.cox@suse.com> --- .../boolean-indicator.component.ts | 11 ++- .../suse-extensions/sass/_all-theme.scss | 4 + .../kubernetes-node-link.component.html | 5 +- .../kubernetes-node-link.component.scss | 8 ++ .../kubernetes-node-link.component.theme.scss | 18 +++++ .../kubernetes-node-link.component.ts | 25 +++++- ...ubernetes-node-condition-card.component.ts | 29 ++++++- .../kubernetes-node-condition.component.html | 4 +- .../kubernetes-node-condition.component.ts | 20 ++++- ...ubernetes-node-summary-card.component.html | 24 +++++- ...ubernetes-node-summary-card.component.scss | 4 +- .../kubernetes-node-summary-card.component.ts | 31 ++++++-- .../services/kubernetes-endpoint.service.ts | 77 +++++++++++++++++++ .../services/kubernetes-node.service.ts | 5 +- .../src/custom/kubernetes/store/kube.types.ts | 10 ++- .../kubernetes-summary.component.html | 20 +++++ .../kubernetes-summary.component.scss | 26 +++++++ .../kubernetes-summary.component.theme.scss | 19 +++-- .../kubernetes-summary.component.ts | 6 +- 19 files changed, 314 insertions(+), 32 deletions(-) create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.theme.scss diff --git a/src/frontend/packages/core/src/shared/components/boolean-indicator/boolean-indicator.component.ts b/src/frontend/packages/core/src/shared/components/boolean-indicator/boolean-indicator.component.ts index 4a544a0d27..6a1675dc5f 100644 --- a/src/frontend/packages/core/src/shared/components/boolean-indicator/boolean-indicator.component.ts +++ b/src/frontend/packages/core/src/shared/components/boolean-indicator/boolean-indicator.component.ts @@ -38,7 +38,16 @@ export class BooleanIndicatorComponent { // Invert the text labels with the icons (No text for yes value and vice-versa) @Input() inverse = false; // Should we use a subtle display - this won't show the No option as danger (typically red) - @Input() subtle = true; + private pSubtle = true; + @Input() + get subtle(): boolean { + return this.pSubtle; + } + set subtle(subtle: boolean) { + this.pSubtle = subtle; + this.updateBooleanOutput(); + } + @Input() showText = true; private pType: BooleanIndicatorType; diff --git a/src/frontend/packages/suse-extensions/sass/_all-theme.scss b/src/frontend/packages/suse-extensions/sass/_all-theme.scss index 9f21f4317f..5afbb47729 100644 --- a/src/frontend/packages/suse-extensions/sass/_all-theme.scss +++ b/src/frontend/packages/suse-extensions/sass/_all-theme.scss @@ -1,16 +1,20 @@ // Theming for the copmponents in the Kubernetes package +@import '../src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.theme'; @import '../src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.theme'; @import '../src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.theme'; @import '../src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.theme'; +@import '../src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.theme'; @mixin apply-theme-suse-extensions($stratos-theme) { $theme: map-get($stratos-theme, theme); $app-theme: map-get($stratos-theme, app-theme); + @include kube-summary-theme($theme, $app-theme); @include kube-analysis-report-theme($theme, $app-theme); @include kube-analysis-card-theme($theme, $app-theme); @include monocular-chart-card($theme, $app-theme); + @include kube-node-link-theme($theme, $app-theme); } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html index 99161c715c..c425c5f39e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html @@ -1 +1,4 @@ -<a [routerLink]="nodeLink">{{row.metadata.name}}</a> +<div class="node-link"> + <a [routerLink]="nodeLink">{{row.metadata.name}}</a> + <mat-icon *ngIf="icon" class="{{icon.class}}" [matTooltip]="icon.message">{{icon.icon}}</mat-icon> +</div> \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.scss index e69de29bb2..24cbdb0c59 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.scss @@ -0,0 +1,8 @@ +.node-link { + align-items: center; + display: flex; + flex-direction: row; + mat-icon { + padding-left: 10px; + } +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.theme.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.theme.scss new file mode 100644 index 0000000000..387f85d461 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.theme.scss @@ -0,0 +1,18 @@ + +@mixin kube-node-link-theme($theme, $app-theme) { + + $status-colors: map-get($app-theme, status); + $error: map-get($status-colors, danger); + $warning: map-get($status-colors, warning); + + .node-link { + .mat-icon { + &.error { + color: $error; + } + &.warning { + color: $warning; + } + } + } +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.ts index 39dc89844f..d5c05226f8 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.ts @@ -11,7 +11,14 @@ import { KubernetesNode } from '../../../store/kube.types'; }) export class KubernetesNodeLinkComponent extends TableCellCustom<KubernetesNode> implements OnInit { - public nodeLink; + public nodeLink: string; + + public icon: { + icon: string, + class: string, + message: string + }; + constructor( private kubeEndpointService: KubernetesEndpointService ) { @@ -20,6 +27,22 @@ export class KubernetesNodeLinkComponent extends TableCellCustom<KubernetesNode> ngOnInit() { this.nodeLink = `/kubernetes/${this.kubeEndpointService.kubeGuid}/nodes/${this.row.metadata.name}`; + const caaspNodeData = this.kubeEndpointService.getCaaspNodeData(this.row); + if (caaspNodeData) { + if (caaspNodeData.securityUpdates) { + this.icon = { + icon: 'error', + class: 'error', + message: 'Node has security updates' + }; + } else if (caaspNodeData.disruptiveUpdates) { + this.icon = { + icon: 'warning', + class: 'warning', + message: 'Node has disruptive updates' + }; + } + } } } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.ts index 1046605a5f..b778e37518 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.ts @@ -1,8 +1,35 @@ import { Component } from '@angular/core'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { CaaspNodeData, KubernetesEndpointService } from '../../../../services/kubernetes-endpoint.service'; +import { KubernetesNodeService } from '../../../../services/kubernetes-node.service'; @Component({ selector: 'app-kubernetes-node-condition-card', templateUrl: './kubernetes-node-condition-card.component.html', styleUrls: ['./kubernetes-node-condition-card.component.scss'] }) -export class KubernetesNodeConditionCardComponent { } +export class KubernetesNodeConditionCardComponent { + public caaspNode$: Observable<CaaspNodeData>; + public caaspNodeDisruptive$: Observable<boolean>; + public caaspNodSecurity$: Observable<boolean>; + + constructor( + public kubeEndpointService: KubernetesEndpointService, + public kubeNodeService: KubernetesNodeService + ) { + + this.caaspNode$ = this.kubeNodeService.nodeEntity$.pipe( + map(node => kubeEndpointService.getCaaspNodeData(node)), + ); + + this.caaspNodeDisruptive$ = this.caaspNode$.pipe( + map(node => node.disruptiveUpdates) + ) + + this.caaspNodSecurity$ = this.caaspNode$.pipe( + map(node => node.securityUpdates) + ) + } +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.html index ea848be216..ae2fa2e27d 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.html @@ -1,4 +1,4 @@ -<div *ngIf="condition$ | async as conditionValue" class="kube-node-condition"> +<div *ngIf="hasCondition$ | async" class="kube-node-condition" [ngStyle]="{'padding-top': paddingTop}"> <div class="kube-node-condition__title"> <mat-icon [fontSet]="icons[condition][1]">{{ icons[condition][0] }}</mat-icon> <div> @@ -6,5 +6,5 @@ </div> </div> <app-boolean-indicator class="kube-node-condition__indicator" [subtle]="subtle" [inverse]="inverse" - [isTrue]="conditionValue" type="yes-no"></app-boolean-indicator> + [isTrue]="condition$ | async" [type]="type"></app-boolean-indicator> </div> \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.ts index 08b17950bf..010f8acf94 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.ts @@ -16,6 +16,13 @@ export class KubernetesNodeConditionComponent implements OnInit { @Input() condition: ConditionType; condition$: Observable<boolean>; + hasCondition$: Observable<boolean>; + + @Input() + overrideCondition$: Observable<boolean>; + + @Input() + type = 'yes-no' @Input() inverse = false; @@ -23,6 +30,9 @@ export class KubernetesNodeConditionComponent implements OnInit { @Input() subtle = false; + @Input() + paddingTop = '20px'; + public titles = ConditionTypeLabels; public icons = { @@ -31,7 +41,10 @@ export class KubernetesNodeConditionComponent implements OnInit { MemoryPressure: ['memory', 'material-icons'], DiskPressure: ['storage', 'material-icons'], PIDPressure: ['vertical_align_center', 'material-icons'], - NetworkUnavailable: ['settings_ethernet', 'material-icons'] + NetworkUnavailable: ['settings_ethernet', 'material-icons'], + CaaspUpdates: ['vertical_align_top', 'material-icons'], + CaaspDisruptive: ['warning', 'material-icons'], + CaaspSecurity: ['security', 'material-icons'] }; constructor( @@ -39,13 +52,16 @@ export class KubernetesNodeConditionComponent implements OnInit { ) { } ngOnInit() { - this.condition$ = this.kubeNodeService.node$.pipe( + this.condition$ = this.overrideCondition$ ? this.overrideCondition$ : this.kubeNodeService.node$.pipe( filter(p => !!p && !!p.entity), map(p => p.entity.status.conditions), map(conditions => conditions.filter(o => o.type === this.condition)), filter(conditions => !!conditions.length), map(conditions => this.shouldBeGreen(conditions[0])) ); + this.hasCondition$ = this.condition$.pipe( + map(() => true) + ) } shouldBeGreen(condition: KubernetesCondition) { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.html index d5347278d9..c0d7270b8f 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.html @@ -1,14 +1,30 @@ <div class="kubernetes-node-summary-card"> <mat-card> <mat-card-header> - <mat-card-title>Summary</mat-card-title> + <mat-card-title>General Information</mat-card-title> </mat-card-header> <mat-card-content> <div class="kubernetes-node-summary-card__content"> - <app-metadata-item icon="title" label="Name">{{ (kubeNodeService.nodeEntity$ | async)?.metadata.name }}</app-metadata-item> - <app-metadata-item icon="today" label="Created">{{ (kubeNodeService.nodeEntity$ | async)?.metadata.creationTimestamp | date:'medium' }}</app-metadata-item> + <app-metadata-item icon="title" label="Name">{{ (kubeNodeService.nodeEntity$ | async)?.metadata.name }} + </app-metadata-item> + <app-metadata-item icon="today" label="Created"> + {{ (kubeNodeService.nodeEntity$ | async)?.metadata.creationTimestamp | date:'medium' }}</app-metadata-item> + <ng-container *ngIf="caaspNode$ | async as caaspNode"> + <app-metadata-item icon="info_outline" label="CaaSP Version" *ngIf="caaspNode.version"> + {{ caaspNode.version }}</app-metadata-item> + <app-kubernetes-node-condition [inverse]="true" [subtle]="false" type="no-yes" condition="CaaspUpdates" + [overrideCondition$]="caaspNodeUpdates$" [paddingTop]="'0'"> + </app-kubernetes-node-condition> + <app-kubernetes-node-condition [inverse]="true" [subtle]="false" type="no-yes" condition="CaaspDisruptive" + [overrideCondition$]="caaspNodeDisruptive$" [paddingTop]="'12px'"> + </app-kubernetes-node-condition> + <app-kubernetes-node-condition [inverse]="true" [subtle]="false" type="no-yes" condition="CaaspSecurity" + [overrideCondition$]="caaspNodeSecurity$" [paddingTop]="'12px'"> + </app-kubernetes-node-condition> + </ng-container> + </div> </mat-card-content> </mat-card> -</div> +</div> \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.scss index 42591c3cc6..f3257a9b10 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.scss @@ -1,3 +1,3 @@ .kubernetes-node-summary-card { - height: 100% -} \ No newline at end of file + height: 100%; +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.ts index 1f18811511..8b088c3d70 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.ts @@ -1,10 +1,9 @@ import { Component } from '@angular/core'; import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; -import { AppChip } from '../../../../../../../../core/src/shared/components/chips/chips.component'; -import { KubernetesEndpointService } from '../../../../services/kubernetes-endpoint.service'; +import { CaaspNodeData, KubernetesEndpointService } from '../../../../services/kubernetes-endpoint.service'; import { KubernetesNodeService } from '../../../../services/kubernetes-node.service'; -import { KubernetesNode } from '../../../../store/kube.types'; @Component({ selector: 'app-kubernetes-node-summary-card', @@ -12,12 +11,30 @@ import { KubernetesNode } from '../../../../store/kube.types'; styleUrls: ['./kubernetes-node-summary-card.component.scss'] }) export class KubernetesNodeSummaryCardComponent { - node$: Observable<KubernetesNode>; - labels$: Observable<AppChip[]>; - annotations$: Observable<AppChip[]>; + public caaspVersion$: Observable<string>; + public caaspNode$: Observable<CaaspNodeData>; + public caaspNodeUpdates$: Observable<boolean>; + public caaspNodeDisruptive$: Observable<boolean>; + public caaspNodeSecurity$: Observable<boolean>; constructor( public kubeEndpointService: KubernetesEndpointService, public kubeNodeService: KubernetesNodeService - ) { } + ) { + this.caaspNode$ = this.kubeNodeService.nodeEntity$.pipe( + map(node => kubeEndpointService.getCaaspNodeData(node)), + ); + + this.caaspNodeUpdates$ = this.caaspNode$.pipe( + map(node => node.updates) + ) + + this.caaspNodeDisruptive$ = this.caaspNode$.pipe( + map(node => node.disruptiveUpdates) + ) + + this.caaspNodeSecurity$ = this.caaspNode$.pipe( + map(node => node.securityUpdates) + ) + } } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-endpoint.service.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-endpoint.service.ts index 8b8927e376..2fe71f3803 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-endpoint.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-endpoint.service.ts @@ -20,6 +20,28 @@ import { KubeService, } from '../store/kube.types'; import { KubeDashboardStatus } from '../store/kubernetes.effects'; +import { Annotations } from './../store/kube.types'; + +const CAASP_VERSION_ANNOTATION = 'caasp.suse.com/caasp-release-version'; +const CAASP_DISRUPTIVE_UPDATES_ANNOTATION = 'caasp.suse.com/has-disruptive-updates'; +const CAASP_SECURITY_UPDATES_ANNOTATION = 'caasp.suse.com/has-security-updates'; +const CAASP_HAS_UPDATES_ANNOTATION = 'caasp.suse.com/has-updates'; + +export interface CaaspNodesData { + version: string; + versionMismatch: boolean; + updates: number; + disruptiveUpdates: number; + securityUpdates: number; +} + +export interface CaaspNodeData { + version: string; + updates: boolean; + disruptiveUpdates: boolean; + securityUpdates: boolean; +} + @Injectable() export class KubernetesEndpointService { @@ -65,6 +87,61 @@ export class KubernetesEndpointService { this.constructCoreObservables(); } + getCaaspNodesData(nodes$: Observable<KubernetesNode[]> = this.nodes$): Observable<CaaspNodesData> { + return nodes$.pipe( + map(nodes => { + const info: CaaspNodesData = { + version: 'Unknown', + versionMismatch: false, + updates: 0, + disruptiveUpdates: 0, + securityUpdates: 0 + }; + const versions = {}; + + nodes.forEach(n => { + const nodeData = this.getCaaspNodeData(n); + if (!nodeData) { + return; + } + + if (!versions[nodeData.version]) { + versions[nodeData.version] = 0; + } + versions[nodeData.version]++; + + info.updates += nodeData.updates ? 1 : 0; + info.disruptiveUpdates += nodeData.disruptiveUpdates ? 1 : 0; + info.securityUpdates += nodeData.securityUpdates ? 1 : 0; + }); + + if (Object.keys(versions).length === 0) { + return null; + } + + info.version = Object.keys(versions).join(', '); + info.versionMismatch = Object.keys(versions).length !== 1; + return info; + }) + ) + } + + getCaaspNodeData(n: KubernetesNode): CaaspNodeData { + if (n && n.metadata && n.metadata.annotations) { + return { + version: n.metadata.annotations[CAASP_VERSION_ANNOTATION], + updates: this.hasBooleanAnnotation(n.metadata.annotations, CAASP_HAS_UPDATES_ANNOTATION), + disruptiveUpdates: this.hasBooleanAnnotation(n.metadata.annotations, CAASP_DISRUPTIVE_UPDATES_ANNOTATION), + securityUpdates: this.hasBooleanAnnotation(n.metadata.annotations, CAASP_SECURITY_UPDATES_ANNOTATION) + } + } + } + + // Check for the specified annotation with a value of 'yes' + private hasBooleanAnnotation(annotations: Annotations, annotation: string): boolean { + return annotations[annotation] && annotations[annotation] === 'yes' ? true : false + } + getNodeKubeVersions(nodes$: Observable<KubernetesNode[]> = this.nodes$) { return nodes$.pipe( map(nodes => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-node.service.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-node.service.ts index 3dd564f2e0..855173aef9 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-node.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-node.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; -import { filter, first, map, shareReplay } from 'rxjs/operators'; +import { filter, first, map, publishReplay, refCount } from 'rxjs/operators'; import { getIdFromRoute } from '../../../../../core/src/core/utils.service'; import { MetricQueryConfig, MetricsAction } from '../../../../../store/src/actions/metrics.actions'; @@ -42,7 +42,8 @@ export class KubernetesNodeService { this.node$ = nodeEntityService.entityObs$.pipe( filter(p => !!p && !!p.entity), first(), - shareReplay(1), + publishReplay(1), + refCount() ); this.nodeEntity$ = this.node$.pipe( diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kube.types.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kube.types.ts index 3a4a5e60b9..9192f7014c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kube.types.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kube.types.ts @@ -136,7 +136,10 @@ export enum ConditionType { DiskPressure = 'DiskPressure', Ready = 'Ready', PIDPressure = 'PIDPressure', - NetworkUnavailable = 'NetworkUnavailable' + NetworkUnavailable = 'NetworkUnavailable', + CaaspUpdates = 'CaaspUpdates', + CaaspDisruptive = 'CaaspDisruptive', + CaaspSecurity = 'CaaspSecurity' } export const ConditionTypeLabels = { [ConditionType.Ready]: 'Ready', @@ -144,7 +147,10 @@ export const ConditionTypeLabels = { [ConditionType.MemoryPressure]: 'Memory Pressure', [ConditionType.DiskPressure]: 'Disk Pressure', [ConditionType.PIDPressure]: 'PID Pressure', - [ConditionType.NetworkUnavailable]: 'Network Unavailable' + [ConditionType.NetworkUnavailable]: 'Network Unavailable', + [ConditionType.CaaspUpdates]: 'Updates Available', + [ConditionType.CaaspDisruptive]: 'Disruptive Update', + [ConditionType.CaaspSecurity]: 'Security Update' }; export enum ConditionStatus { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.html index d13d1c2b45..6029db052e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.html @@ -55,6 +55,26 @@ </div> </div> + <!-- Show CaaSP metadata --> + <div class="app-metadata" *ngIf="caaspData$ | async as data"> + <div class="app-metadata__two-cols"> + <app-metadata-item label="CaaSP Version"> + <div class="caasp-version"> + <div>{{ data.version }}</div><mat-icon class="text-warning" matTooltip="Nodes report multiple versions" *ngIf="data.versionMismatch">warning</mat-icon> + </div> + </app-metadata-item> + </div> + <div class="app-metadata__two-cols"> + <app-metadata-item label="Nodes with Updates"> + <div class="caasp-updates"> + <div>{{ data.updates }}</div> + <div *ngIf="data.securityUpdates > 0" class="caasp-updates__security">Security: <b>{{ data.securityUpdates }}</b></div> + <div *ngIf="data.disruptiveUpdates > 0" class="caasp-updates__disruptive">Disruptive: <b>{{ data.disruptiveUpdates }}</b></div> + </div> + </app-metadata-item> + </div> + </div> + <div class="kube-details__graphs"> <div class="kube-details__graphs-group"> <div class="kube-details__graph"> diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.scss index 9ec58f91b6..ff59086255 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.scss @@ -63,4 +63,30 @@ margin-top: 0; } } + + .caasp-version { + display: flex; + + mat-icon { + cursor: help; + font-size: 20px; + height: 20px; + margin-left: 12px; + width: 20px; + } + } + + .caasp-updates { + align-items: center; + display: flex; + + &__security, + &__disruptive { + border-radius: 8px; + font-size: 12px; + margin-left: 10px; + padding: 1px 8px; + } + } + } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.theme.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.theme.scss index 93ed3f107c..6b9afbebb3 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.theme.scss +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.theme.scss @@ -1,10 +1,17 @@ -// Not currently used but should be - NJ @mixin kube-summary-theme($theme, $app-theme) { - $backgrounds: map-get($theme, background); - $background: mat-color($backgrounds, card); - .kube-details__graphs { - background: linear-gradient(to bottom, $background 0%, $background 40%, transparent 70%); + $status-colors: map-get($app-theme, status); + $status-warning: map-get($status-colors, warning); + $status-danger: map-get($status-colors, danger); + + .caasp-updates { + &__security { + border: 1px solid $status-danger; + color: $status-danger; + } + &__disruptive { + border: 1px solid $status-warning; + color: $status-warning; + } } } - diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts index 7548a95d02..b5a69213bb 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts @@ -17,7 +17,7 @@ import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/p import { getCurrentPageRequestInfo } from '../../../../../../store/src/reducers/pagination-reducer/pagination-reducer.types'; import { PaginatedAction, PaginationEntityState } from '../../../../../../store/src/types/pagination.types'; import { kubeEntityCatalog } from '../../kubernetes-entity-catalog'; -import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; +import { CaaspNodesData, KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; interface IValueLabels { usedLabel?: string; @@ -30,6 +30,7 @@ interface IEndpointDetails { label: string; name: string; } + @Component({ selector: 'app-kubernetes-summary', templateUrl: './kubernetes-summary.component.html', @@ -73,6 +74,7 @@ export class KubernetesSummaryTabComponent implements OnInit, OnDestroy { public nodesReady$: Observable<ISimpleUsageChartData>; public networkUnavailable$: Observable<ISimpleUsageChartData>; public kubeNodeVersions$: Observable<string>; + public caaspData$: Observable<CaaspNodesData>; public pressureChartThresholds: IChartThresholds = { danger: 90, @@ -165,6 +167,8 @@ export class KubernetesSummaryTabComponent implements OnInit, OnDestroy { this.kubeNodeVersions$ = this.kubeEndpointService.getNodeKubeVersions(nodes$).pipe(startWith('-')); + this.caaspData$ = this.kubeEndpointService.getCaaspNodesData(nodes$); + this.isLoading$ = combineLatest([ this.endpointDetails$, this.podCount$, From 4843986a75e5d2555d1d7aa86fe55b9fb2be23e2 Mon Sep 17 00:00:00 2001 From: Neil MacDougall <nwmac@users.noreply.github.com> Date: Thu, 27 Aug 2020 15:37:31 +0100 Subject: [PATCH 601/648] Don't do expensive helm repo update when starting terminal (#453) --- deploy/containers/kube-terminal/kubeconsole.bashrc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/deploy/containers/kube-terminal/kubeconsole.bashrc b/deploy/containers/kube-terminal/kubeconsole.bashrc index 0a1edfdd56..c07ef129f5 100644 --- a/deploy/containers/kube-terminal/kubeconsole.bashrc +++ b/deploy/containers/kube-terminal/kubeconsole.bashrc @@ -6,9 +6,7 @@ RESET="\033[0m" BOLD="\033[1m" DIM="\033[2m" -echo -e "${BOLD}${GREEN}SUSE Stratos Console${RESET}" -echo "" -echo -e "${CYAN}Kubernetes Terminal${RESET}" +echo -e "${CYAN}Stratos Kubernetes Terminal${RESET}" echo "" # Only do these on first run @@ -51,7 +49,9 @@ if [ ! -f "/stratos/.firstrun" ]; then if [ -f "${HOME}/.stratos/helm-setup" ]; then echo "Setting up Helm repositories ..." source "${HOME}/.stratos/helm-setup" > /dev/null - helm repo update 2>&1 > /dev/null + + # helm repo update can take a while, so get the user to do that if they want to use helm + echo -e "You may need to run ${CYAN}helm repo update${RESET} to update your repositories before using Helm" echo "" fi @@ -73,5 +73,5 @@ touch "/stratos/.firstrun" unset `compgen -A variable | grep KUBERNETES` echo -echo -e "Ready - ${CYAN}kubectl${RESET} and ${CYAN}helm${RESET} commands are available" +echo -e "${CYAN}kubectl${RESET} and ${CYAN}helm${RESET} commands are available" echo "" From 8fa12b0d2775d57a80e5cfa98f5ed05dc0684524 Mon Sep 17 00:00:00 2001 From: Neil MacDougall <nwmac@users.noreply.github.com> Date: Thu, 27 Aug 2020 15:53:35 +0100 Subject: [PATCH 602/648] Improve presentation and fix issue with development versions (#456) --- .../chart-details-info.component.html | 4 +- .../chart-details-info.component.scss | 12 ++++++ .../chart-details-usage.component.html | 39 ++----------------- .../chart-details-usage.component.scss | 7 ++++ .../chart-details-versions.component.html | 16 +++++--- .../chart-details-versions.component.scss | 27 +++++++++++-- .../chart-details-versions.component.ts | 12 +++++- .../chart-item/chart-item.component.html | 9 +---- .../chart-item/chart-item.component.scss | 5 +++ .../list-item/list-item.component.html | 2 +- .../list-item/list-item.component.scss | 9 +++++ .../list-item/list-item.component.ts | 5 ++- .../chartsvc/foundationdb/handler.go | 16 +++++++- 13 files changed, 105 insertions(+), 58 deletions(-) diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.html b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.html index 457d4edd57..0329e95550 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.html @@ -13,7 +13,7 @@ <h1>Home</h1> <a href="{{ chart.attributes.home }}" target="_blank">{{chart.attributes.home}}</a> </div> </div> - <div class="chartInfo__maintainers"> + <div class="chartInfo__maintainers" *ngIf="maintainers.length"> <h1>Maintainers</h1> <div *ngFor="let maintainer of maintainers"> <a href="{{ maintainerUrl(maintainer) }}" target="_blank">{{ maintainer.name }}</a> @@ -22,7 +22,7 @@ <h1>Maintainers</h1> <div class="chartInfo__related" *ngIf="sources.length"> <h1>Related</h1> <div *ngFor="let source of sources"> - <a href="{{ source }}" target="_blank">{{ source }}</a> + <a href="{{ source }}" class="chartInfo__related-link" target="_blank">{{ source }}</a> </div> </div> </mat-card> diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.scss b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.scss index 5ff001e7c6..1b1558c02c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.scss @@ -1,5 +1,9 @@ .chartInfo { + h1 { + font-size: 18px; + } + app-panel { box-shadow: 1px 1px 10px rgba(black, 0.07); border: 0 !important; @@ -11,6 +15,14 @@ } } + &__related { + max-width: 280px; + } + + &__related-link { + font-size: 14px; + } + &__properties { overflow: auto; display: block; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.html b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.html index 4058bb6801..1e374b0705 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.html @@ -1,35 +1,4 @@ -<mat-card class="chart-details-usage"> - <h1>Install</h1> - <mat-tab-group> - <mat-tab label="UI" *ngIf="endpointsService.hasConnectedEndpointType('k8s') | async"> - <p>You can install this Helm Chart from here.</p> - <button class="chart-details-usage__install" color="primary" mat-button mat-raised-button - routerLink="/monocular/install/{{chart.id}}/{{currentVersion}}">Install</button> - </mat-tab> - - <mat-tab label="Helm CLI"> - <div *ngIf="showRepoInstructions" class="chart-details-usage__repository chart-details-usage__section"> - <h2 class="chart-details-usage__label">Add {{ chart.attributes.repo.name }} repository</h2> - <mat-form-field> - <input matInput floatingPlaceholder=false [value]=repoAddInstructions> - </mat-form-field> - <button mat-button angulartics2On="click" angularticsEvent="RepositoryCopy"> - <mat-icon svgIcon="content-copy"></mat-icon> - </button> - </div> - <div class="chart-details-usage__section"> - <h2 class="chart-details-usage__label">Install chart</h2> - <mat-form-field> - <input matInput readonly floatingPlaceholder=false [value]=installInstructions> - </mat-form-field> - <button mat-button ngxClipboard angulartics2On="click"> - <mat-icon svgIcon="content-copy"></mat-icon> - </button> - </div> - <p class="help-link"> - <a href="https://github.com/kubernetes/helm/blob/master/docs/quickstart.md" target="_blank" - title="How to install Helm">Need Helm?</a> - </p> - </mat-tab> - </mat-tab-group> -</mat-card> \ No newline at end of file +<div class="chart-details-usage__install"> + <button class="chart-details-usage__install-btn" color="primary" mat-button mat-raised-button + routerLink="/monocular/install/{{chart.id}}/{{currentVersion}}">Install Chart</button> +</div> diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.scss b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.scss index f74db6a81e..955e8885c7 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.scss @@ -9,6 +9,7 @@ mat-tab-body { margin: 2em 0; box-shadow: 1px 1px 10px rgba(black, 0.07); h1 { + font-size: 18px; margin: 0; } @@ -26,6 +27,12 @@ mat-tab-body { flex-direction: column; } + &__install { + display: flex; + justify-content: flex-end; + margin: 35px 0; + } + .mat-input-element { .mat-input-wrapper { margin: 1em 0 .5em; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.html b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.html index 3707dcd9c6..68e0d21e6f 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.html @@ -1,11 +1,15 @@ <div class="versions"> <h1>Chart Versions</h1> - <div class="version" *ngFor="let version of shownVersions()"> - <span class='number' [class.selected]="isSelected(version)"> - <a [href]="goToVersionUrl(version)" [routerLink]="goToVersionUrl(version)"> - {{ version.attributes.version }}</a> - </span> - - <span class='creation-date'>{{ version.attributes.created | date: 'MMM d, y' }}</span> + <div class="version__table"> + <div class="version__row" *ngFor="let version of shownVersions()"> + <div class='version__cell number' [class.selected]="isSelected(version)"> + <a [href]="goToVersionUrl(version)" [routerLink]="goToVersionUrl(version)"> + <span *ngIf="version.attributes.version.length>15">{{ version.attributes.version | slice:0:16 }}...</span> + <span *ngIf="version.attributes.version.length<16">{{ version.attributes.version }}</span> + </a> + </div> + <div class='version__cell creation-date'>{{ version.attributes.created | date: 'MMM d, y' }}</div> + </div> </div> <div class="more-link" *ngIf="showMoreLink()"> <a (click)="setShowAllVersions()">show all...</a> diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.scss b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.scss index e334c053b2..b3002ff9d3 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.scss @@ -3,6 +3,7 @@ .versions { h1 { + font-size: 18px; margin-top: 0; } .more-link { @@ -10,14 +11,32 @@ } } -.version { - display: block; +.version__cell { - .creation-date { + &.creation-date { color: lighten($layout-base, 20%); } - .number.selected { + &.number.selected { font-weight: bold; } } + +.version { + &__table { + display: table; + width: 100%; + } + &__row { + display: table-row; + } + &__cell { + display: table-cell; + } +} + +.app-version { + h1 { + font-size: 18px; + } +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.ts index 5b563f203e..9e17557519 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.ts @@ -32,8 +32,18 @@ export class ChartDetailsVersionsComponent { shownVersions(): ChartVersion[] { if (this.versions) { - return this.showAllVersions ? this.versions : this.versions.slice(0, 5); + return this.showAllVersions ? this.versions : this.getNonDevelopmentVersion().slice(0, 5); } return []; } + + getNonDevelopmentVersion(): ChartVersion[] { + const nonDevel: ChartVersion[] = []; + for (const version of this.versions) { + if (version.attributes.version.indexOf('-') === -1) { + nonDevel.push(version); + } + } + return nonDevel; + } } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.html b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.html index fc0074d65c..b94c5bc9ef 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.html @@ -1,18 +1,13 @@ -<app-list-item class="chart-item" [detailUrl]="goToDetailUrl()"> +<app-list-item class="chart-item" [detailUrl]="goToDetailUrl()" height="auto"> <img class="list-item-logo chart-item-logo" alt="{{ chart.attributes.name }}'s logo" src="{{ iconUrl }}" /> <div class="list-item-info chart-item-info"> <h2 class="chart-item-info__title"> - <a [href]="goToDetailUrl()" [routerLink]="goToDetailUrl()"> - {{ chart.attributes.name }}</a> + <a [href]="goToDetailUrl()" [routerLink]="goToDetailUrl()">{{ chart.attributes.repo.name }}/{{ chart.attributes.name }}</a> </h2> <p> <span class="chart-item-info__version" *ngIf="showVersion && chart.relationships.latestChartVersion.data.app_version"> {{ chart.relationships.latestChartVersion.data.app_version }}</span> - <span class="chart-item-info__repo repo-{{chart.attributes.repo.name}}"> - <a [href]="goToRepoUrl()" [routerLink]="goToRepoUrl()"> - {{ chart.attributes.repo.name }}</a> - </span> </p> <div class="chart-item-info__description" *ngIf="showDescription"> {{ chart.attributes.description }} diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.scss b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.scss index 0c06d1a026..e42c43a10e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.scss @@ -16,6 +16,7 @@ $chart-item-logo-width: 80px; &__title { margin: 0; font-weight: 500; + font-size: 16px; cursor: pointer; margin-bottom: 4px; @@ -28,6 +29,10 @@ $chart-item-logo-width: 80px; margin: 0; } + &__version { + font-size: 14px; + } + // Remove this when chips are ready: // https://github.com/angular/material2/issues/120 &__repo { diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.html b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.html index 1e02c4fde2..5964674ba8 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.html @@ -1,6 +1,6 @@ <div class="list-item"> <a [href]="detailUrl" [routerLink]="detailUrl"> - <div class="list-item-content"> + <div class="list-item-content" [ngClass]="{'list-item__auto-height': height === 'auto'}"> <div class="list-item-logo"> <ng-content select=".list-item-logo"></ng-content> </div> diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.scss b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.scss index cebb26fe0a..15e6632b19 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.scss @@ -3,6 +3,7 @@ // Vars $list-item-content-height: 200px; +$list-item-content-min-height: 140px; .list-item { border-radius: $border-radius; @@ -47,4 +48,12 @@ $list-item-content-height: 200px; word-wrap: break-word; text-align: center; } + + &__auto-height { + height: auto; + + .list-item-info { + padding: 10px 0; + } + } } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.ts index 91745aab75..8052238ffb 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, Input } from '@angular/core'; @Component({ selector: 'app-list-item', @@ -8,5 +8,8 @@ import { Component } from '@angular/core'; inputs: ['detailUrl'] }) export class ListItemComponent { + + @Input() height = 'default'; + public detailUrl: string; } diff --git a/src/jetstream/plugins/monocular/chartsvc/foundationdb/handler.go b/src/jetstream/plugins/monocular/chartsvc/foundationdb/handler.go index fde7d07fde..a0fb5863bf 100644 --- a/src/jetstream/plugins/monocular/chartsvc/foundationdb/handler.go +++ b/src/jetstream/plugins/monocular/chartsvc/foundationdb/handler.go @@ -23,6 +23,7 @@ import ( "net/http" "sort" "strconv" + "strings" "github.com/helm/monocular/chartsvc/foundationdb/datastore" "github.com/helm/monocular/chartsvc/models" @@ -461,7 +462,11 @@ func SearchCharts(w http.ResponseWriter, req *http.Request, params Params) { } func newChartResponse(c *models.Chart) *utils.ApiResponse { - latestCV := c.ChartVersions[0] + + // NWM: This was: latestCV := c.ChartVersions[0] - but this includes development charts + // FIX: This ignores development versions + latestCV := findFirstNonDevelopmentVersion(c) + return &utils.ApiResponse{ Type: "chart", ID: c.ID, @@ -476,6 +481,15 @@ func newChartResponse(c *models.Chart) *utils.ApiResponse { } } +func findFirstNonDevelopmentVersion(c *models.Chart) models.ChartVersion { + for _, chartVersion := range c.ChartVersions { + if strings.Index(chartVersion.Version, "-") == -1 { + return chartVersion + } + } + return c.ChartVersions[0] +} + func newChartListResponse(charts []*models.Chart) utils.ApiListResponse { cl := utils.ApiListResponse{} for _, c := range charts { From d479e80ee33796822980cae804789dbd35134153 Mon Sep 17 00:00:00 2001 From: Neil MacDougall <nwmac@users.noreply.github.com> Date: Thu, 27 Aug 2020 15:55:42 +0100 Subject: [PATCH 603/648] Fix: Kubernetes unit tests aren't being run (#459) --- angular.json | 17 +++++++++ package.json | 1 + .../packages/suse-extensions/karma.conf.js | 8 +++++ .../demo-helper/demo-helper.component.spec.ts | 4 +-- .../create-release.component.spec.ts | 4 +-- .../src/custom/helm/helm-testing.module.ts | 8 ++--- .../monocular-chart-card.component.spec.ts | 2 +- .../monocular-tab-base.component.spec.ts | 4 +-- .../chart-details-usage.component.spec.ts | 6 ++-- .../chart-details.component.spec.ts | 6 ++-- .../chart-item/chart-item.component.spec.ts | 2 +- .../monocular/charts/charts.component.spec.ts | 2 +- .../monocular/loader/loader.component.spec.ts | 2 +- .../analysis-report-runner.component.spec.ts | 17 ++++++++- ...analysis-report-selector.component.spec.ts | 8 ++--- .../analysis-report-viewer.component.spec.ts | 6 +++- ...kube-score-report-viewer.component.spec.ts | 8 ++--- .../popeye-report-viewer.component.spec.ts | 8 ++--- .../resource-alert-view.component.spec.ts | 8 ++--- ...kubernetes-aws-auth-form.component.spec.ts | 3 +- ...bernetes-certs-auth-form.component.spec.ts | 2 +- ...ernetes-config-auth-form.component.spec.ts | 2 +- ...kubernetes-gke-auth-form.component.spec.ts | 2 +- .../kube-config-table-name.component.spec.ts | 4 ++- .../kube-console.component.spec.ts | 6 ++-- .../kubedash-configuration.component.spec.ts | 8 ++--- .../kubernetes-dashboard.component.spec.ts | 2 +- ...amespace-analysis-report.component.spec.ts | 14 ++++---- ...netes-namespace-services.component.spec.ts | 2 +- .../kubernetes-node.component.spec.ts | 2 +- ...bernetes-resource-viewer.component.spec.ts | 6 ++-- .../kubernetes-tab-base.component.spec.ts | 2 +- .../kubernetes/kubernetes.testing.module.ts | 2 +- .../kubernetes/kubernetes.component.spec.ts | 2 +- .../analysis-status-cell.component.spec.ts | 6 ++-- .../condition-cell.component.spec.ts | 2 +- ...kubernetes-node-capacity.component.spec.ts | 2 +- .../kubernetes-node-ips.component.spec.ts | 2 +- .../kubernetes-node-labels.component.spec.ts | 2 +- ...kubernetes-node-pressure.component.spec.ts | 2 +- .../pod-metrics/pod-metrics.component.spec.ts | 2 +- ...kubernetes-analysis-info.component.spec.ts | 14 +++++--- ...bernetes-analysis-report.component.spec.ts | 18 +++++++--- .../kubernetes-analysis-report.component.ts | 4 +++ .../kubernetes-analysis-tab.component.spec.ts | 12 +++---- .../kubernetes-summary.component.spec.ts | 2 +- ...helm-release-summary-tab.component.spec.ts | 12 ++++--- .../suse-login/suse-login.component.spec.ts | 4 +-- .../packages/suse-extensions/src/test.ts | 36 +++++++++++++++++++ .../suse-extensions/tsconfig.spec.json | 17 +++++++++ 50 files changed, 219 insertions(+), 98 deletions(-) create mode 100644 src/frontend/packages/suse-extensions/karma.conf.js create mode 100644 src/frontend/packages/suse-extensions/src/test.ts create mode 100644 src/frontend/packages/suse-extensions/tsconfig.spec.json diff --git a/angular.json b/angular.json index b557dc7a20..95d3bd1b49 100644 --- a/angular.json +++ b/angular.json @@ -351,7 +351,24 @@ } } } + }, + "extensions": { + "root": "src/frontend/packages/suse-extensions", + "sourceRoot": "src/frontend/packages/suse-extensions/src", + "projectType": "library", + "prefix": "lib", + "architect": { + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/frontend/packages/suse-extensions/src/test.ts", + "tsConfig": "src/frontend/packages/suse-extensions/tsconfig.spec.json", + "karmaConfig": "src/frontend/packages/suse-extensions/karma.conf.js" + } + } + } } + }, "defaultProject": "stratos", "schematics": { diff --git a/package.json b/package.json index 011b676947..79fdb92362 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "test-frontend:store": "NG_TEST_SUITE=store ng test store --code-coverage --watch=false", "test-frontend:cloud-foundry": "NG_TEST_SUITE=cloud-foundry ng test cloud-foundry --code-coverage --watch=false", "test-frontend:cf-autoscaler": "NG_TEST_SUITE=autoscaler ng test cf-autoscaler --code-coverage --watch=false", + "test-frontend:extensions": "NG_TEST_SUITE=extensions ng test extensions --code-coverage --watch=false", "posttest": "nyc report --reporter=html --reporter=lcovonly --reporter=json --tempDir=coverage/nyc", "codecov": "codecov -f coverage/coverage-final.json", "lint": "ng lint --format stylish", diff --git a/src/frontend/packages/suse-extensions/karma.conf.js b/src/frontend/packages/suse-extensions/karma.conf.js new file mode 100644 index 0000000000..c23e417786 --- /dev/null +++ b/src/frontend/packages/suse-extensions/karma.conf.js @@ -0,0 +1,8 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + ...require('../../../../build/karma.conf.creator.js')('extensions')(config) + }) +} diff --git a/src/frontend/packages/suse-extensions/src/custom/demo/demo-helper/demo-helper.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/demo/demo-helper/demo-helper.component.spec.ts index 2d6d578d43..847f32c442 100644 --- a/src/frontend/packages/suse-extensions/src/custom/demo/demo-helper/demo-helper.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/demo/demo-helper/demo-helper.component.spec.ts @@ -1,8 +1,8 @@ import { HttpClientModule } from '@angular/common/http'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../tab-nav.service'; -import { BaseTestModules } from '../../../../test-framework/core-test.helper'; +import { TabNavService } from '../../../../../core/tab-nav.service'; +import { BaseTestModules } from '../../../../../core/test-framework/core-test.helper'; import { DemoHelperComponent } from './demo-helper.component'; describe('DemoHelperComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.spec.ts index 26e121fdac..6cf6078bec 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.spec.ts @@ -3,11 +3,11 @@ import { HttpClient } from '@angular/common/http'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ConfirmationDialogService } from '../../../../../core/src/shared/components/confirmation-dialog.service'; +import { TabNavService } from '../../../../../core/tab-nav.service'; import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; import { InternalEventMonitorFactory } from '../../../../../store/src/monitors/internal-event-monitor.factory'; import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; -import { TabNavService } from '../../../../tab-nav.service'; -import { ConfirmationDialogService } from '../../../shared/components/confirmation-dialog.service'; import { KubernetesBaseTestModules } from '../../kubernetes/kubernetes.testing.module'; import { CreateReleaseComponent } from './create-release.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm-testing.module.ts b/src/frontend/packages/suse-extensions/src/custom/helm/helm-testing.module.ts index 2bace2cf52..d995b63748 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm-testing.module.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/helm-testing.module.ts @@ -1,16 +1,16 @@ import { HttpClient, HttpClientModule, HttpHandler } from '@angular/common/http'; import { NgModule } from '@angular/core'; +import { CoreModule } from '@angular/flex-layout'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; +import { SharedModule } from '@stratosui/core'; +import { AppTestModule } from '../../../../core/test-framework/core-test.helper'; import { CATALOGUE_ENTITIES, EntityCatalogFeatureModule } from '../../../../store/src/entity-catalog.module'; import { entityCatalog, TestEntityCatalog } from '../../../../store/src/entity-catalog/entity-catalog'; +import { generateStratosEntities } from '../../../../store/src/stratos-entity-generator'; import { createBasicStoreModule } from '../../../../store/testing/public-api'; -import { AppTestModule } from '../../../test-framework/core-test.helper'; -import { generateStratosEntities } from '../../base-entity-types'; -import { CoreModule } from '../../core/core.module'; -import { SharedModule } from '../../shared/shared.module'; import { HelmReleaseGuid } from '../kubernetes/workloads/workload.types'; import { generateHelmEntities } from './helm-entity-generator'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.spec.ts index 08de859911..14b4272b60 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseTestModulesNoShared } from '../../../../../test-framework/core-test.helper'; +import { BaseTestModulesNoShared } from '../../../../../../core/test-framework/core-test.helper'; import { ChartItemComponent } from '../../monocular/chart-item/chart-item.component'; import { ListItemComponent } from '../../monocular/list-item/list-item.component'; import { ChartsService } from '../../monocular/shared/services/charts.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.spec.ts index 153b0b9cfb..410d613753 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../tab-nav.service'; -import { BaseTestModulesNoShared } from '../../../../test-framework/core-test.helper'; +import { TabNavService } from '../../../../../core/tab-nav.service'; +import { BaseTestModulesNoShared } from '../../../../../core/test-framework/core-test.helper'; import { HelmModule } from '../helm.module'; import { MonocularTabBaseComponent } from './monocular-tab-base.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.spec.ts index 979905d767..0964fec6e2 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.spec.ts @@ -1,10 +1,10 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; +import { EndpointsService } from '../../../../../../../core/src/core/endpoints.service'; +import { UtilsService } from '../../../../../../../core/src/core/utils.service'; +import { BaseTestModulesNoShared } from '../../../../../../../core/test-framework/core-test.helper'; import { PaginationMonitorFactory } from '../../../../../../../store/src/monitors/pagination-monitor.factory'; -import { BaseTestModulesNoShared } from '../../../../../../test-framework/core-test.helper'; -import { EndpointsService } from '../../../../../core/endpoints.service'; -import { UtilsService } from '../../../../../core/utils.service'; import { ChartDetailsUsageComponent } from './chart-details-usage.component'; describe('Component: ChartDetailsUsage', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.spec.ts index d670ba09c3..93812f2d5b 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.spec.ts @@ -3,11 +3,11 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BrowserModule } from '@angular/platform-browser'; import { RouterTestingModule } from '@angular/router/testing'; -import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; -import { BaseTestModulesNoShared } from '../../../../../test-framework/core-test.helper'; import { EntitySummaryTitleComponent, -} from '../../../../shared/components/entity-summary-title/entity-summary-title.component'; +} from '../../../../../../core/src/shared/components/entity-summary-title/entity-summary-title.component'; +import { BaseTestModulesNoShared } from '../../../../../../core/test-framework/core-test.helper'; +import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; import { ChartItemComponent } from '../chart-item/chart-item.component'; import { ListItemComponent } from '../list-item/list-item.component'; import { LoaderComponent } from '../loader/loader.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.spec.ts index 6a4f2723fe..47188d55a5 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.spec.ts @@ -3,8 +3,8 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; +import { LoggerService } from '../../../../../../core/src/core/logger.service'; import { createBasicStoreModule } from '../../../../../../store/testing/public-api'; -import { LoggerService } from '../../../../core/logger.service'; import { ChartsService } from '../shared/services/charts.service'; import { ConfigService } from '../shared/services/config.service'; import { ChartItemComponent } from './chart-item.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/charts/charts.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/charts/charts.component.spec.ts index d634d2c8de..493bebfb1d 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/charts/charts.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/charts/charts.component.spec.ts @@ -6,8 +6,8 @@ import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { of } from 'rxjs'; +import { LoggerService } from '../../../../../../core/src/core/logger.service'; import { createBasicStoreModule } from '../../../../../../store/testing/public-api'; -import { LoggerService } from '../../../../core/logger.service'; import { ChartItemComponent } from '../chart-item/chart-item.component'; import { ChartListComponent } from '../chart-list/chart-list.component'; import { LoaderComponent } from '../loader/loader.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/loader/loader.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/loader/loader.component.spec.ts index 0154562121..61bd1aa4ce 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/loader/loader.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/loader/loader.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { CoreModule } from '@stratosui/core'; -import { CoreModule } from '../../../../core/core.module'; import { LoaderComponent } from './loader.component'; describe('LoaderComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.spec.ts index f2d53545a3..8757c86532 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.spec.ts @@ -1,5 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../kubernetes.testing.module'; +import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; +import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; +import { SidePanelService } from './../../../../../../core/src/shared/services/side-panel.service'; +import { SharedModule } from './../../../../../../core/src/shared/shared.module'; import { AnalysisReportRunnerComponent } from './analysis-report-runner.component'; describe('AnalysisReportRunnerComponent', () => { @@ -8,7 +13,17 @@ describe('AnalysisReportRunnerComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ AnalysisReportRunnerComponent ] + declarations: [ AnalysisReportRunnerComponent ], + imports: [ + SharedModule, + KubernetesBaseTestModules, + ], + providers: [ + KubernetesAnalysisService, + KubernetesEndpointService, + KubeBaseGuidMock, + SidePanelService, + ] }) .compileComponents(); })); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.spec.ts index d23be84d77..4d421a38e4 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.spec.ts @@ -1,10 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { MDAppModule } from './../../../../core/md.module'; -import { AnalysisReportSelectorComponent } from './analysis-report-selector.component'; -import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; -import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; +import { MDAppModule } from '../../../../../../core/src/public-api'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../kubernetes.testing.module'; +import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; +import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; +import { AnalysisReportSelectorComponent } from './analysis-report-selector.component'; describe('AnalysisReportSelectorComponent', () => { let component: AnalysisReportSelectorComponent; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.spec.ts index ed39ab0acf..6b49e51116 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.spec.ts @@ -1,7 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { AnalysisReportViewerComponent } from './analysis-report-viewer.component'; import { KubernetesBaseTestModules } from '../kubernetes.testing.module'; +import { KubernetesAnalysisService } from '../services/kubernetes.analysis.service'; +import { AnalysisReportViewerComponent } from './analysis-report-viewer.component'; describe('AnalysisReportViewerComponent', () => { let component: AnalysisReportViewerComponent; @@ -12,6 +13,9 @@ describe('AnalysisReportViewerComponent', () => { declarations: [ AnalysisReportViewerComponent ], imports: [ KubernetesBaseTestModules, + ], + providers: [ + KubernetesAnalysisService ] }) .compileComponents(); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.spec.ts index a93f22f618..babd043739 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.spec.ts @@ -1,10 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { MDAppModule } from './../../../../core/md.module'; -import { KubeScoreReportViewerComponent } from './kube-score-report-viewer.component'; -import { KubernetesBaseTestModules, KubeBaseGuidMock } from '../../kubernetes.testing.module'; -import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; +import { MDAppModule } from '../../../../../../core/src/public-api'; +import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; +import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; +import { KubeScoreReportViewerComponent } from './kube-score-report-viewer.component'; describe('KubeScoreReportViewerComponent', () => { let component: KubeScoreReportViewerComponent; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.spec.ts index ccbe5c5081..192d66f8da 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.spec.ts @@ -1,10 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { MDAppModule } from './../../../../core/md.module'; -import { PopeyeReportViewerComponent } from './popeye-report-viewer.component'; -import { KubernetesBaseTestModules, KubeBaseGuidMock } from '../../kubernetes.testing.module'; -import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; +import { MDAppModule } from '../../../../../../core/src/public-api'; +import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; +import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; +import { PopeyeReportViewerComponent } from './popeye-report-viewer.component'; describe('PopeyeReportViewerComponent', () => { let component: PopeyeReportViewerComponent; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.spec.ts index 0ffbf5f5ff..9f158d36cf 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.spec.ts @@ -1,10 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { MDAppModule } from './../../../../../core/md.module'; -import { ResourceAlertViewComponent } from './resource-alert-view.component'; -import { KubernetesBaseTestModules, KubeBaseGuidMock } from '../../../kubernetes.testing.module'; -import { KubernetesAnalysisService } from '../../../services/kubernetes.analysis.service'; +import { MDAppModule } from '../../../../../../../core/src/public-api'; +import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; +import { KubernetesAnalysisService } from '../../../services/kubernetes.analysis.service'; +import { ResourceAlertViewComponent } from './resource-alert-view.component'; describe('ResourceAlertViewComponent', () => { let component: ResourceAlertViewComponent; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.spec.ts index 84179dbe41..f9ef26134c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.spec.ts @@ -2,8 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { FormBuilder } from '@angular/forms'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { MDAppModule } from '../../../../core/md.module'; -import { SharedModule } from '../../../../shared/shared.module'; +import { MDAppModule, SharedModule } from '../../../../../../core/src/public-api'; import { KubernetesAWSAuthFormComponent } from './kubernetes-aws-auth-form.component'; describe('KubernetesAWSAuthFormComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.spec.ts index af9ca17150..74ccf5ef71 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.spec.ts @@ -3,7 +3,7 @@ import { FormBuilder } from '@angular/forms'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { CoreModule } from 'frontend/packages/core/src/core/core.module'; -import { SharedModule } from '../../../../shared/shared.module'; +import { SharedModule } from './../../../../../../core/src/shared/shared.module'; import { KubernetesCertsAuthFormComponent } from './kubernetes-certs-auth-form.component'; describe('KubernetesCertsAuthFormComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.spec.ts index 0fd0b060d0..756e42f1bc 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.spec.ts @@ -2,7 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { FormBuilder } from '@angular/forms'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { SharedModule } from '../../../../shared/shared.module'; +import { SharedModule } from './../../../../../../core/src/shared/shared.module'; import { KubernetesConfigAuthFormComponent } from './kubernetes-config-auth-form.component'; describe('KubernetesConfigAuthFormComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.spec.ts index 1327730757..32c5fa6b31 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.spec.ts @@ -2,7 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { FormBuilder } from '@angular/forms'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { SharedModule } from '../../../../shared/shared.module'; +import { SharedModule } from './../../../../../../core/src/shared/shared.module'; import { KubernetesGKEAuthFormComponent } from './kubernetes-gke-auth-form.component'; describe('KubernetesGKEAuthFormComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.spec.ts index c37211591f..4e4f7f59c7 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.spec.ts @@ -1,6 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { IListDataSource } from '../../../../../shared/components/list/data-sources-controllers/list-data-source-types'; +import { + IListDataSource, +} from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; import { KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; import { KubeConfigFileCluster } from '../../kube-config.types'; import { KubeConfigTableName } from './kube-config-table-name.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-terminal/kube-console.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-terminal/kube-console.component.spec.ts index 8671538bc9..65f7e4ba32 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-terminal/kube-console.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-terminal/kube-console.component.spec.ts @@ -4,9 +4,9 @@ import { createBasicStoreModule } from '@stratosui/store/testing'; import { ApplicationService } from '../../../../../cloud-foundry/src/features/applications/application.service'; import { ApplicationServiceMock } from '../../../../../cloud-foundry/test-framework/application-service-helper'; -import { TabNavService } from '../../../../tab-nav.service'; -import { CoreModule } from '../../../core/core.module'; -import { SharedModule } from '../../../shared/shared.module'; +import { SharedModule } from '../../../../../core/src/public-api'; +import { TabNavService } from '../../../../../core/tab-nav.service'; +import { CoreModule } from './../../../../../core/src/core/core.module'; import { KubeConsoleComponent } from './kube-console.component'; describe('KubeConsoleComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.spec.ts index c8f461034f..84062f4fc2 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.spec.ts @@ -1,10 +1,10 @@ +import { HttpClient, HttpHandler } from '@angular/common/http'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; -import { KubedashConfigurationComponent } from './kubedash-configuration.component'; +import { TabNavService } from '../../../../../../core/tab-nav.service'; import { KubernetesBaseTestModules } from '../../kubernetes.testing.module'; -import { TabNavService } from '../../../../../tab-nav.service'; -import { HttpClient, HttpHandler } from '@angular/common/http'; -import { ActivatedRoute } from '@angular/router'; +import { KubedashConfigurationComponent } from './kubedash-configuration.component'; describe('KubedashConfigurationComponent', () => { let component: KubedashConfigurationComponent; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.spec.ts index 9bd1d38afa..f918ee294b 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { TabNavService } from '../../../../tab-nav.service'; +import { TabNavService } from '../../../../../core/tab-nav.service'; import { KubernetesBaseTestModules } from '../kubernetes.testing.module'; import { KubernetesDashboardTabComponent } from './kubernetes-dashboard.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.spec.ts index 5e5fdfaf98..4834ac63ec 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.spec.ts @@ -1,16 +1,16 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { MDAppModule } from './../../../../core/md.module'; +import { TabNavService } from 'frontend/packages/core/tab-nav.service'; -import { KubernetesNamespaceAnalysisReportComponent } from './kubernetes-namespace-analysis-report.component'; -import { KubernetesBaseTestModules, KubeBaseGuidMock } from '../../kubernetes.testing.module'; -import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; +import { MDAppModule } from '../../../../../../core/src/public-api'; +import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; +import { KubernetesNamespaceService } from '../../services/kubernetes-namespace.service'; +import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; import { - AnalysisReportSelectorComponent + AnalysisReportSelectorComponent, } from './../../analysis-report-viewer/analysis-report-selector/analysis-report-selector.component'; import { AnalysisReportViewerComponent } from './../../analysis-report-viewer/analysis-report-viewer.component'; -import { KubernetesNamespaceService } from '../../services/kubernetes-namespace.service'; -import { TabNavService } from 'frontend/packages/core/tab-nav.service'; +import { KubernetesNamespaceAnalysisReportComponent } from './kubernetes-namespace-analysis-report.component'; describe('KubernetesNamespaceAnalysisReportComponent', () => { let component: KubernetesNamespaceAnalysisReportComponent; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.spec.ts index 8733dc7999..d638b3b663 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../tab-nav.service'; +import { TabNavService } from '../../../../../../core/tab-nav.service'; import { BaseKubeGuid } from '../../kubernetes-page.types'; import { KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node.component.spec.ts index 277bf2ca96..b334471044 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { TabNavService } from '../../../../tab-nav.service'; +import { TabNavService } from '../../../../../core/tab-nav.service'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../kubernetes.testing.module'; import { KubernetesNodeComponent } from './kubernetes-node.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.spec.ts index 75fb79c994..38b577beaa 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.spec.ts @@ -1,11 +1,13 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { SidePanelService } from '../../../shared/services/side-panel.service'; +import { SidePanelService } from '../../../../../core/src/shared/services/side-panel.service'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../kubernetes.testing.module'; import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; +import { + ResourceAlertViewComponent, +} from './../analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component'; import { KubernetesResourceViewerComponent } from './kubernetes-resource-viewer.component'; -import { ResourceAlertViewComponent } from './../analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component'; describe('KubernetesResourceViewerComponent', () => { let component: KubernetesResourceViewerComponent; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.spec.ts index 22ffd1698a..0d8e5a109e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { TabNavService } from '../../../../tab-nav.service'; +import { TabNavService } from '../../../../../core/tab-nav.service'; import { KubernetesBaseTestModules } from '../kubernetes.testing.module'; import { KubernetesTabBaseComponent } from './kubernetes-tab-base.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.testing.module.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.testing.module.ts index 82cb57d3e8..e1967ff707 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.testing.module.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.testing.module.ts @@ -3,12 +3,12 @@ import { NgModule } from '@angular/core'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { generateStratosEntities } from '../../../../core/src/base-entity-types'; import { CoreModule } from '../../../../core/src/core/core.module'; import { SharedModule } from '../../../../core/src/shared/shared.module'; import { AppTestModule } from '../../../../core/test-framework/core-test.helper'; import { CATALOGUE_ENTITIES, EntityCatalogFeatureModule } from '../../../../store/src/entity-catalog.module'; import { entityCatalog, TestEntityCatalog } from '../../../../store/src/entity-catalog/entity-catalog'; +import { generateStratosEntities } from '../../../../store/src/stratos-entity-generator'; import { createBasicStoreModule } from '../../../../store/testing/public-api'; import { HelmReleaseActivatedRouteMock, HelmReleaseGuidMock } from '../helm/helm-testing.module'; import { generateKubernetesEntities } from './kubernetes-entity-generator'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes/kubernetes.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes/kubernetes.component.spec.ts index 99112c8c5c..f715faab3a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes/kubernetes.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes/kubernetes.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../tab-nav.service'; +import { TabNavService } from '../../../../../core/tab-nav.service'; import { KubernetesBaseTestModules } from '../kubernetes.testing.module'; import { KubernetesComponent } from './kubernetes.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.spec.ts index 3c1fb10915..aca29a6d84 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.spec.ts @@ -1,10 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { MDAppModule } from './../../../../core/md.module'; -import { AnalysisStatusCellComponent } from './analysis-status-cell.component'; +import { MDAppModule } from '../../../../../../core/src/public-api'; import { - AnalysisReportSelectorComponent + AnalysisReportSelectorComponent, } from './../../analysis-report-viewer/analysis-report-selector/analysis-report-selector.component'; +import { AnalysisStatusCellComponent } from './analysis-status-cell.component'; describe('AnalysisStatusCellComponent', () => { let component: AnalysisStatusCellComponent; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.spec.ts index bd66e4e87b..382f3b21ba 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseTestModules } from '../../../../../../test-framework/core-test.helper'; +import { BaseTestModules } from '../../../../../../../core/test-framework/core-test.helper'; import { ConditionCellComponent } from './condition-cell.component'; describe('ConditionCellComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.spec.ts index d6d5d7d6c2..e71a76120f 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseTestModules } from '../../../../../../test-framework/core-test.helper'; +import { BaseTestModules } from '../../../../../../../core/test-framework/core-test.helper'; import { KubernetesNodeCapacityComponent } from './kubernetes-node-capacity.component'; describe('KubernetesNodeCapacityComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.spec.ts index b55778d677..cbb540177f 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseTestModules } from '../../../../../../test-framework/core-test.helper'; +import { BaseTestModules } from '../../../../../../../core/test-framework/core-test.helper'; import { KubernetesNodeIpsComponent } from './kubernetes-node-ips.component'; describe('KubernetesNodeIpsComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.spec.ts index f36e05a977..2f7e112135 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseTestModules } from '../../../../../../test-framework/core-test.helper'; +import { BaseTestModules } from '../../../../../../../core/test-framework/core-test.helper'; import { KubernetesNodeLabelsComponent } from './kubernetes-node-labels.component'; describe('KubernetesNodeLabelsComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.spec.ts index c5e1367b72..0a3579cd55 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseTestModules } from '../../../../../../test-framework/core-test.helper'; +import { BaseTestModules } from '../../../../../../../core/test-framework/core-test.helper'; import { KubernetesNodePressureComponent } from './kubernetes-node-pressure.component'; describe('KubernetesNodePressureComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/pod-metrics/pod-metrics.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/pod-metrics/pod-metrics.component.spec.ts index c999020533..3efdf94c24 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/pod-metrics/pod-metrics.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/pod-metrics/pod-metrics.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { TabNavService } from '../../../../tab-nav.service'; +import { TabNavService } from '../../../../../core/tab-nav.service'; import { KubernetesBaseTestModules } from '../kubernetes.testing.module'; import { PodMetricsComponent } from './pod-metrics.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.spec.ts index 0a35b86689..d959dc1a83 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.spec.ts @@ -1,11 +1,13 @@ - import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { KubernetesAnalysisInfoComponent } from './kubernetes-analysis-info.component'; -import { AnalysisInfoCardComponent } from './analysis-info-card/analysis-info-card.component'; -import { KubernetesBaseTestModules, KubeBaseGuidMock } from '../../../kubernetes.testing.module'; -import { KubernetesAnalysisService } from '../../../services/kubernetes.analysis.service'; +import { SidePanelService } from '../../../../../../../core/src/shared/services/side-panel.service'; +import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; +import { KubernetesAnalysisService } from '../../../services/kubernetes.analysis.service'; +import { SharedModule } from './../../../../../../../core/src/shared/shared.module'; +import { AnalysisInfoCardComponent } from './analysis-info-card/analysis-info-card.component'; +import { KubernetesAnalysisInfoComponent } from './kubernetes-analysis-info.component'; + describe('KubernetesAnalysisInfoComponent', () => { let component: KubernetesAnalysisInfoComponent; @@ -15,11 +17,13 @@ describe('KubernetesAnalysisInfoComponent', () => { TestBed.configureTestingModule({ declarations: [ KubernetesAnalysisInfoComponent, AnalysisInfoCardComponent ], imports: [ + SharedModule, KubernetesBaseTestModules, ], providers: [ KubernetesAnalysisService, KubernetesEndpointService, + SidePanelService, KubeBaseGuidMock, ] }) diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.spec.ts index f1f2d4fd8c..642176aa68 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.spec.ts @@ -1,9 +1,13 @@ - import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { KubernetesAnalysisReportComponent } from './kubernetes-analysis-report.component'; -import { KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; +import { TabNavService } from '../../../../../../../core/tab-nav.service'; +import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; +import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; +import { KubernetesAnalysisService } from '../../../services/kubernetes.analysis.service'; +import { CoreModule } from './../../../../../../../core/src/core/core.module'; import { AnalysisReportViewerComponent } from './../../../analysis-report-viewer/analysis-report-viewer.component'; +import { KubernetesAnalysisReportComponent } from './kubernetes-analysis-report.component'; + describe('KubernetesAnalysisReportComponent', () => { let component: KubernetesAnalysisReportComponent; @@ -14,7 +18,13 @@ describe('KubernetesAnalysisReportComponent', () => { declarations: [ KubernetesAnalysisReportComponent, AnalysisReportViewerComponent ], imports: [ KubernetesBaseTestModules, -// MDAppModule + CoreModule, + ], + providers: [ + KubernetesAnalysisService, + KubernetesEndpointService, + KubeBaseGuidMock, + TabNavService, ] }) .compileComponents(); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.ts index 4ca7a3fc19..ab39ef6700 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.ts @@ -42,6 +42,10 @@ export class KubernetesAnalysisReportComponent implements OnInit { } ngOnInit() { + if (!this.id) { + return; + } + this.report$ = this.analysisService.getByID(this.kubeEndpointService.baseKube.guid, this.id).pipe( map((response: any) => { if (!response.type) { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.spec.ts index b13da9b832..6e6255dd8c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.spec.ts @@ -1,13 +1,13 @@ - import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { MDAppModule } from './../../../../core/md.module'; +import { TabNavService } from 'frontend/packages/core/tab-nav.service'; -import { KubernetesAnalysisTabComponent } from './kubernetes-analysis-tab.component'; -import { KubernetesBaseTestModules, KubeBaseGuidMock } from '../../kubernetes.testing.module'; -import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; +import { MDAppModule } from '../../../../../../core/src/public-api'; +import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; +import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; import { AnalysisReportViewerComponent } from './../../analysis-report-viewer/analysis-report-viewer.component'; -import { TabNavService } from 'frontend/packages/core/tab-nav.service'; +import { KubernetesAnalysisTabComponent } from './kubernetes-analysis-tab.component'; + describe('KubernetesAnalysisTabComponent', () => { let component: KubernetesAnalysisTabComponent; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.spec.ts index 8e64596d2e..539312f0a8 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.spec.ts @@ -1,7 +1,7 @@ import { HttpClient, HttpHandler } from '@angular/common/http'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../tab-nav.service'; +import { TabNavService } from '../../../../../../core/tab-nav.service'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; import { KubernetesSummaryTabComponent } from './kubernetes-summary.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts index ebe873f1d2..4582dd922d 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts @@ -1,12 +1,14 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TabNavService } from 'frontend/packages/core/tab-nav.service'; -import { HelmReleaseProviders, KubernetesBaseTestModules, KubeBaseGuidMock } from '../../../../kubernetes.testing.module'; -import { HelmReleaseSummaryTabComponent } from './helm-release-summary-tab.component'; -import { KubernetesAnalysisService } from '../../../../services/kubernetes.analysis.service'; +import { SidePanelService } from '../../../../../../../../core/src/shared/services/side-panel.service'; +import { HelmReleaseProviders, KubeBaseGuidMock, KubernetesBaseTestModules } from '../../../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../../../services/kubernetes-endpoint.service'; -import { AnalysisReportSelectorComponent } from './../../../../analysis-report-viewer/analysis-report-selector/analysis-report-selector.component'; -import { SidePanelService } from './../../../../../../shared/services/side-panel.service'; +import { KubernetesAnalysisService } from '../../../../services/kubernetes.analysis.service'; +import { + AnalysisReportSelectorComponent, +} from './../../../../analysis-report-viewer/analysis-report-selector/analysis-report-selector.component'; +import { HelmReleaseSummaryTabComponent } from './helm-release-summary-tab.component'; describe('HelmReleaseSummaryTabComponent', () => { let component: HelmReleaseSummaryTabComponent; diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.spec.ts index 1906b66ac5..2955b20df0 100644 --- a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.spec.ts @@ -4,9 +4,9 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; import { StoreModule } from '@ngrx/store'; -import { CoreModule } from '../../core/core.module'; -import { SharedModule } from '../../shared/shared.module'; +import { CoreModule } from '../../../../core/src/core/core.module'; import { appReducers } from '../../../../store/src/reducers.module'; +import { SharedModule } from './../../../../core/src/shared/shared.module'; import { SuseLoginComponent } from './suse-login.component'; describe('SuseLoginComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/test.ts b/src/frontend/packages/suse-extensions/src/test.ts new file mode 100644 index 0000000000..f205020844 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/test.ts @@ -0,0 +1,36 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files +import 'core-js/es/reflect'; +import 'zone.js/dist/zone'; +import 'zone.js/dist/zone-testing'; + +import { APP_BASE_HREF } from '@angular/common'; +import { getTestBed } from '@angular/core/testing'; +import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; + + +declare const require: any; + +// First, initialize the Angular testing environment. +const testBed = getTestBed(); +testBed.initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); + +beforeEach(() => { + testBed.configureTestingModule({ + providers: [{ provide: APP_BASE_HREF, useValue: '/' }] + }); +}); + +/** + * Bump up the Jasmine timeout from 5 seconds + */ +beforeAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; +}); + +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); diff --git a/src/frontend/packages/suse-extensions/tsconfig.spec.json b/src/frontend/packages/suse-extensions/tsconfig.spec.json new file mode 100644 index 0000000000..cb4a7be918 --- /dev/null +++ b/src/frontend/packages/suse-extensions/tsconfig.spec.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../tsconfig.spec.json", + "compilerOptions": { + "outDir": "../../../../out-tsc/spec", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "src/test.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} From 758f920f4bc68272f96c1913de9716bb7fccb821 Mon Sep 17 00:00:00 2001 From: Richard Cox <richard-cox@users.noreply.github.com> Date: Tue, 1 Sep 2020 15:54:59 +0100 Subject: [PATCH 604/648] Fix issue where kube dependency vbom.ml/util is no longer available (#462) * Fix issue where kube depedency vbom.ml/util is no longer available - caused BE unit tests to fail on unrelated PRs - route error ``` go: vbom.ml/util@v0.0.0-20180919145318-efcd4e0f9787: unrecognized import path "vbom.ml/util" (https fetch: Get https://vbom.ml/util?go-get=1: dial tcp: lookup vbom.ml on 127.0.0.53:53: no such host) ``` * Update go.mod --- src/jetstream/go.sum | 132 ------------------------ src/jetstream/plugins/kubernetes/go.mod | 1 - 2 files changed, 133 deletions(-) diff --git a/src/jetstream/go.sum b/src/jetstream/go.sum index ca7f76c6fb..ed4bbbd391 100644 --- a/src/jetstream/go.sum +++ b/src/jetstream/go.sum @@ -7,21 +7,15 @@ code.cloudfoundry.org/bytefmt v0.0.0-20180906201452-2aa6f33b730c h1:VzwteSWGbW9m code.cloudfoundry.org/bytefmt v0.0.0-20180906201452-2aa6f33b730c/go.mod h1:wN/zk7mhREp/oviagqUXY3EwuHhWyOvAdsn5Y4CzOrc= code.cloudfoundry.org/cfnetworking-cli-api v0.0.0-20190103195135-4b04f26287a6 h1:Yc9r1p21kEpni9WlG4mwOZw87TB2QlyS9sAEebZ3+ak= code.cloudfoundry.org/cfnetworking-cli-api v0.0.0-20190103195135-4b04f26287a6/go.mod h1:u5FovqC5GGAEbFPz+IdjycDA+gIjhUwqxnu0vbHwVeM= -code.cloudfoundry.org/cli v6.43.0+incompatible h1:jAaPyHN5Hb2r2sR9i8Y8ejKPiPpuBYMaHBFyKVmQ7T4= -code.cloudfoundry.org/cli v6.43.0+incompatible/go.mod h1:e4d+EpbwevNhyTZKybrLlyTvpH+W22vMsmdmcTxs/Fo= code.cloudfoundry.org/cli v6.49.0+incompatible h1:lUuYux9EXLe8EBzlvckJLpHKhc8szJfWiEc3SXdM8+o= code.cloudfoundry.org/cli v6.49.0+incompatible/go.mod h1:e4d+EpbwevNhyTZKybrLlyTvpH+W22vMsmdmcTxs/Fo= -code.cloudfoundry.org/diego-ssh v0.0.0-20200312183824-517d22c5d890 h1:sr3sHuZSH6puBqQgatzM3hYRrfOc+D8eVv0ykLYwd6o= code.cloudfoundry.org/diego-ssh v0.0.0-20200312183824-517d22c5d890/go.mod h1:L2/glHnSK+wKnsG8oZZqdV2sgYY9NDo/I1aDJGhcWaM= code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f h1:UrKzEwTgeiff9vxdrfdqxibzpWjxLnuXDI5m6z3GJAk= code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f/go.mod h1:sk5LnIjB/nIEU7yP5sDQExVm62wu0pBh3yrElngUisI= -code.cloudfoundry.org/inigo v0.0.0-20200318144131-597cd5dbfe8b h1:TSoj8216L9LEU1MKAI9GSpOpLesalhitn8R4fEn38P8= code.cloudfoundry.org/inigo v0.0.0-20200318144131-597cd5dbfe8b/go.mod h1:1ZB1JCh2FAp+SqX79ve6dc8YREvbsziULEOncAilX4Q= -code.cloudfoundry.org/lager v2.0.0+incompatible h1:WZwDKDB2PLd/oL+USK4b4aEjUymIej9My2nUQ9oWEwQ= code.cloudfoundry.org/lager v2.0.0+incompatible/go.mod h1:O2sS7gKP3HM2iemG+EnwvyNQK7pTSC6Foi4QiMp9sSk= code.cloudfoundry.org/ykk v0.0.0-20170424192843-e4df4ce2fd4d h1:M+zXqtXJqcsmpL76aU0tdl1ho23eYa4axYoM4gD62UA= code.cloudfoundry.org/ykk v0.0.0-20170424192843-e4df4ce2fd4d/go.mod h1:YUJiVOr5xl0N/RjMxM1tHmgSpBbi5UM+KoVR5AoejO0= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= @@ -33,14 +27,12 @@ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbt github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DATA-DOG/go-sqlmock v1.1.3 h1:sVIxMSTMnnVzl9Bn6BMjW6p5lSbpjHL80mqnxMWJ/oE= github.com/DATA-DOG/go-sqlmock v1.1.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e h1:eb0Pzkt15Bm7f2FFYv7sjY7NPFi3cPkS3tv1CcrFBWA= github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= @@ -52,14 +44,10 @@ github.com/Masterminds/sprig/v3 v3.0.0 h1:KSQz7Nb08/3VU9E4ns29dDxcczhOD1q7O1UfM4 github.com/Masterminds/sprig/v3 v3.0.0/go.mod h1:NEUY/Qq8Gdm2xgYA+NwJM6wmfdRV9xkh8h/Rld20R0U= github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/go-winio v0.4.12 h1:xAfWHN1IrQ0NJ9TBC0KBZoqLjzDTr1ML+4MywiUOryc= github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46 h1:lsxEuwrXEAokXB9qhlbKWPpo3KMLZQ5WB5WLQRW1uq0= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -68,7 +56,6 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/SermoDigital/jose v0.9.2-0.20180104203859-803625baeddc h1:MhBvG7RLaLqlyjxMR6of35vt6MVQ+eXMcgn9X/sy0FE= github.com/SermoDigital/jose v0.9.2-0.20180104203859-803625baeddc/go.mod h1:ARgCUhI1MHQH+ONky/PAtmVHQrP5JlGY0F3poXOp/fA= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -76,7 +63,6 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/antonlindstrom/pgstore v0.0.0-20170604072116-a407030ba6d0 h1:e6PEaXbztY0ViaKotCICNnBQDUeNEJgrQ5UAHWlloh4= github.com/antonlindstrom/pgstore v0.0.0-20170604072116-a407030ba6d0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw= -github.com/apoydence/eachers v0.0.0-20181020210610-23942921fe77 h1:afT88tB6u9JCKQZVAAaa9ICz/uGn5Uw9ekn6P22mYKM= github.com/apoydence/eachers v0.0.0-20181020210610-23942921fe77/go.mod h1:bXvGk6IkT1Agy7qzJ+DjIw/SJ1AaB3AvAuMDVV+Vkoo= github.com/arschles/assert v1.0.0/go.mod h1:m/u69zW43x0h8dTHcv3JJZljINyEYgBuf5fYJP6WikI= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= @@ -84,12 +70,10 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.17.5 h1:WW9Hm3KYo48iZHpmBc+b7sgyS0h32zgCvya28SLW4BU= github.com/aws/aws-sdk-go v1.17.5/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= @@ -107,14 +91,12 @@ github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tj github.com/cf-stratos/mysqlstore v0.0.0-20170822100912-304308519d13 h1:WwIvjUUodNoZduhdhotbKnrLSFoIn5vD3QgNZv0hjvo= github.com/cf-stratos/mysqlstore v0.0.0-20170822100912-304308519d13/go.mod h1:GgQT0ToC+7JLnMKdDB5d434WwCLC2dpNR2AgTJj/08o= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= -github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 h1:HD4PLRzjuCVW79mQ0/pdsalOLHJ+FaEoqJLxfltpb2U= github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= github.com/charlievieth/fs v0.0.0-20170613215519-7dc373669fa1 h1:vTlpHKxJqykyKdW9bkrDJNWeKNuSIAJ0TP/K4lRsz/Q= github.com/charlievieth/fs v0.0.0-20170613215519-7dc373669fa1/go.mod h1:sAoA1zHCH4FJPE2gne5iBiiVG66U7Nyp6JqlOo+FEyg= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudfoundry-community/go-cfenv v1.17.0 h1:qfxEfn8qKkaHY3ZEk/Y2noY79HBASvNgmtHK9x4+6GY= github.com/cloudfoundry-community/go-cfenv v1.17.0/go.mod h1:2UgWvQTRXUuIZ/x3KnW6fk6CgPBhcV4UQb/UGIrUyyI= -github.com/cloudfoundry-incubator/stratos v2.0.0-beta-001+incompatible h1:UUxNbLjhv2cfymub5yNN1tjjqYkteHBBagb4jcbXEIQ= github.com/cloudfoundry/bosh-cli v5.4.0+incompatible h1:KpT2PBB7nP1QnK8guXeZ/D2k7FZYAOxcveKgYTDEDBI= github.com/cloudfoundry/bosh-cli v5.4.0+incompatible/go.mod h1:rzIB+e1sn7wQL/TJ54bl/FemPKRhXby5BIMS3tLuWFM= github.com/cloudfoundry/bosh-utils v0.0.0-20190206192830-9a0affed2bf1 h1:TxcGamw+BaxXTFwZFORzkWNMCHAWjhThxnI24CLS7iE= @@ -131,17 +113,13 @@ github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMX github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M= github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.15+incompatible h1:+9RjdC18gMxNQVvSiXvObLu29mOFmkgdsB4cRTlV+EE= github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea h1:n2Ltr3SrfQlf/9nOna1DoGKxLx3qTSI8Ttl6Xrqp6mw= github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cppforlife/go-patch v0.2.0 h1:Y14MnCQjDlbw7WXT4k+u6DPAA9XnygN4BfrSpI/19RU= github.com/cppforlife/go-patch v0.2.0/go.mod h1:67a7aIi94FHDZdoeGSJRRFDp66l9MhaAG1yGxpUoFD8= -github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= @@ -170,7 +148,6 @@ github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82/go.mod h1:/u0gXw github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s= @@ -183,17 +160,13 @@ github.com/dsnet/compress v0.0.0-20171208185109-cc9eb1d7ad76/go.mod h1:KjxHHirfL github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= -github.com/elazarl/goproxy v0.0.0-20200315184450-1f3cb6622dad h1:zPs0fNF2Io1Qytf92EI2CDJ9oCXZr+NmjEVexrUEdq4= github.com/elazarl/goproxy v0.0.0-20200315184450-1f3cb6622dad/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= -github.com/elazarl/goproxy/ext v0.0.0-20200315184450-1f3cb6622dad h1:3OG8xVCbdeebrE5IsoWl0TP25DWiHDbLUy+EKif7hDE= github.com/elazarl/goproxy/ext v0.0.0-20200315184450-1f3cb6622dad/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.11.1+incompatible h1:CjKsv3uWcCMvySPQYKxO8XX3f9zD4FeZRsW4G0B4ffE= github.com/emicklei/go-restful v2.11.1+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/evanphx/json-patch v4.1.0+incompatible h1:K1MDoo4AZ4wU0GIU/fPmtZg7VpzLjCxu+UwBD1FvwOc= -github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -202,9 +175,7 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZM github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc= github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -226,14 +197,12 @@ github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQH github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= -github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= -github.com/go-openapi/jsonreference v0.17.0 h1:yJW3HCkTHg7NOA+gZ83IPHzUSnUzGXhGmsdiCcMexbA= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= @@ -247,7 +216,6 @@ github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6 github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.18.0 h1:aIjeyG5mo5/FrvDkpKKEGZPmF9MPHahS72mzfVqeQXQ= github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo= @@ -256,7 +224,6 @@ github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pL github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= -github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= @@ -264,7 +231,6 @@ github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tF github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= -github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= @@ -273,24 +239,19 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE= github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= @@ -300,7 +261,6 @@ github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -311,33 +271,22 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= -github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= -github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.0 h1:Jf4mxPC/ziBnoPIdpQdPJ9OeiomAUHLvxmPRSPH9m4s= -github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= -github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e h1:XWcjeEtTFTOVA9Fs1w7n2XBftk5ib4oZrhzWk0B+3eA= github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA= github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -346,13 +295,9 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+ github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU= github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosuri/uitable v0.0.1 h1:M9sMNgSZPyAu1FJZJLpJ16ofL8q5ko2EDUkICsynvlY= -github.com/gosuri/uitable v0.0.1 h1:M9sMNgSZPyAu1FJZJLpJ16ofL8q5ko2EDUkICsynvlY= -github.com/gosuri/uitable v0.0.1/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gosuri/uitable v0.0.1/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/govau/cf-common v0.0.7 h1:uhp1P6XM6GGzu1+A4C7LELLX/9mCmH6W5DpJZC0kWmo= github.com/govau/cf-common v0.0.7/go.mod h1:5xL/OfE7wxeyHlXb7iei0rAbdQ/5v6dF18BZknPv7NQ= @@ -361,10 +306,8 @@ github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:Fecb github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc h1:f8eY6cV/x1x+HLjOp4r72s/31/V2aTUtg5oKRRPf8/Q= github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= -github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= @@ -374,16 +317,13 @@ github.com/helm/monocular v1.4.0/go.mod h1:PpkCN0v4zVVigsIHnsQdJytKFmaUkwfhxB7z3 github.com/heptio/authenticator v0.3.0/go.mod h1:Q86X8hc61JXhE5XxYLKmrSRWby/Oe8IIYZIBgmGVkTA= github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40 h1:GT4RsKmHh1uZyhmTkWJTDALRjSHYQp6FRKrotf0zhAs= github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40/go.mod h1:NtmN9h8vrTveVQRLHcX2HQ5wIPBDCsZ351TGbZWgg38= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -397,27 +337,21 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro= github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kat-co/vala v0.0.0-20170210184112-42e1d8b61f12 h1:DQVOxR9qdYEybJUr/c7ku34r3PfajaMYXZwgDM7KuSk= github.com/kat-co/vala v0.0.0-20170210184112-42e1d8b61f12/go.mod h1:u9MdXq/QageOOSGp7qG4XAQsYUMP+V5zEel/Vrl6OOc= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kubeapps/common v0.0.0-20181107174310-61d8eb6f11b4 h1:wdTBUArlqtBYGN2Dd4+zsaFxFH0m4iGCHToW10jPX0k= -github.com/kubeapps/common v0.0.0-20181107174310-61d8eb6f11b4/go.mod h1:TsgmjeDpbftqhwPKInJ3v+l+xbHs4goiB6DFb2WqY9c= github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a h1:VqeX/fehAB6FtBox0TVYcjOMXGE56INQIfbXegditX4= github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a/go.mod h1:TsgmjeDpbftqhwPKInJ3v+l+xbHs4goiB6DFb2WqY9c= github.com/kubernetes-sigs/aws-iam-authenticator v0.3.1-0.20190111160901-390d9087a4bc h1:Ttr4Z3ZrMv4rAXn10UAqOC8ACx+F1omvcyV1a3hRArE= @@ -451,7 +385,6 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-shellwords v1.0.5/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c= github.com/mattn/go-sqlite3 v1.13.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -460,7 +393,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU= github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU= github.com/miekg/dns v0.0.0-20181005163659-0d29b283ac0f/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.4 h1:rCMZsU2ScVSYcAsOXgmC6+AKOK+6pmQTOcw03nfwYV0= github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= @@ -483,12 +415,9 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE= github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d h1:7PxY7LVfSZm7PEeBTyK1rj1gABdCO2mbri6GKO1cMDs= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs= github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= @@ -497,15 +426,11 @@ github.com/nwmac/sqlitestore v0.0.0-20180824125213-7d2ab221fb3f/go.mod h1:GVvWHl github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -513,13 +438,9 @@ github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVo github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc2.0.20190611121236-6cc515888830 h1:yvQ/2Pupw60ON8TYEIGGTAI77yZsWYkiOeHFZWkwlCk= -github.com/opencontainers/runc v1.0.0-rc2.0.20190611121236-6cc515888830/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/phayes/freeport v0.0.0-20171002181615-b8543db493a5 h1:rZQtoozkfsiNs36c7Tdv/gyGNzD1X1XWKO8rptVNZuM= github.com/phayes/freeport v0.0.0-20171002181615-b8543db493a5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= @@ -529,28 +450,23 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/poy/eachers v0.0.0-20181020210610-23942921fe77 h1:SNdqPRvRsVmYR0gKqFvrUKhFizPJ6yDiGQ++VAJIoDg= github.com/poy/eachers v0.0.0-20181020210610-23942921fe77/go.mod h1:x1vqpbcMW9T/KRcQ4b48diSiSVtYgvwQ5xzDByEg4WE= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI= github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190129233650-316cf8ccfec5/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -562,7 +478,6 @@ github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNue github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 h1:G04eS0JkAIVZfaJLjla9dNxkJCPiKIGZlw9AfOhzOD0= github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94/go.mod h1:b18R55ulyQ/h3RaWyloPyER7fWQVZvimKKhnI5OfrJQ= -github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY= github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= @@ -571,9 +486,7 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3 h1:hBSHahWMEgzwRyS6dRpxY0XyjZsHyQ61s084wo5PJe0= github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20160503033757-d4c757aa9afd h1:ZDVcuZGxvWB1ooKj1e31P/ktQK4A2WumM+LucMENpds= github.com/smartystreets/goconvey v0.0.0-20160503033757-d4c757aa9afd/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -582,7 +495,6 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -596,7 +508,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245/go.mod h1:O1c8HleITsZqzNZDjSNzirUGsMT0oGu9LhHKoJrqO+A= -github.com/tedsuo/ifrit v0.0.0-20191009134036-9a97d0632f00 h1:mujcChM89zOHwgZBBNr5WZ77mBXP1yR+gLThGCYZgAg= github.com/tedsuo/ifrit v0.0.0-20191009134036-9a97d0632f00/go.mod h1:eyZnKCc955uh98WQvzOm0dgAeLnf2O0Rz0LPoC5ze+0= github.com/tedsuo/rata v1.0.0 h1:Sf9aZrYy6ElSTncjnGkyC2yuVvz5YJetBIUKJ4CmeKE= github.com/tedsuo/rata v1.0.0/go.mod h1:X47ELzhOoLbfFIY0Cql9P6yo3Cdwf2CMX3FVZxRzJPc= @@ -625,43 +536,33 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1: github.com/xeipuuv/gojsonschema v1.1.0 h1:ngVtJC9TY/lg0AA/1k48FYhBrhRoFlEmWzsehpNAaZg= github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xenolf/lego v0.0.0-20160613233155-a9d8cec0e656/go.mod h1:fwiGnfsIjG7OHPfOvgK7Y/Qo6+2Ox0iozjNTkZICKbY= -github.com/xenolf/lego v0.3.2-0.20160613233155-a9d8cec0e656 h1:BTvU+npm3/yjuBd53EvgiFLl5+YLikf2WvHsjRQ4KrY= github.com/xenolf/lego v0.3.2-0.20160613233155-a9d8cec0e656/go.mod h1:fwiGnfsIjG7OHPfOvgK7Y/Qo6+2Ox0iozjNTkZICKbY= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 h1:p7OofyZ509h8DmPLh8Hn+EIIZm/xYhdZHJ9GnXHdr6U= github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= -github.com/yvasiyarov/gorelic v0.0.6 h1:qMJQYPNdtJ7UNYHjX38KXZtltKTqimMuoQjNnSVIuJg= github.com/yvasiyarov/gorelic v0.0.6/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.mongodb.org/mongo-driver v1.1.3 h1:++7u8r9adKhGR+I79NfEtYrk2ktjenErXM99PSufIoI= go.mongodb.org/mongo-driver v1.1.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569 h1:nSQar3Y0E3VQF/VdZ8PTAilaXpER+d7ypdABCrpwMdg= go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df h1:shvkWr0NAZkg4nPuE3XrKP0VuBPijjk3TfX6Y6acFNg= go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15 h1:Z2sc4+v0JHV6Mn4kX1f2a5nruNjmV+Th32sugE8zwz8= go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152 h1:ZC1Xn5A1nlpSmQCIva4bZ3ob3lmhYIefc+GU+DLg1Ow= golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191205161847-0a08dada0ff9 h1:abxekknhS/Drh3uoQDk5Hc7BgeiyI39Crb7vhf/1j5s= golang.org/x/crypto v0.0.0-20191205161847-0a08dada0ff9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -691,17 +592,13 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271 h1:N66aaryRB3Ax92gH0v3hp1QYZ3zWWCCUR/j8Ifh45Ss= golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226191147-529b322ea346 h1:zxGQKdHVCsCsJpbd7ijKsVC27CyETheUBql7Br2TGmA= -golang.org/x/oauth2 v0.0.0-20190226191147-529b322ea346/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -712,27 +609,22 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191028164358-195ce5e7f934 h1:u/E0NqCIWRDAo9WCFo6Ko49njPFDLSd3z+X1HgWDMpE= golang.org/x/sys v0.0.0-20191028164358-195ce5e7f934/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -750,7 +642,6 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= @@ -758,11 +649,8 @@ gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmK google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190128161407-8ac453e89fca/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -771,7 +659,6 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20191028173616-919d9bdd9fe6 h1:UXl+Zk3jqqcbEVV7ace5lrt4YdA4tXiz3f/KbmD29Vo= google.golang.org/genproto v0.0.0-20191028173616-919d9bdd9fe6/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= -google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= @@ -779,39 +666,26 @@ google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.27 h1:kJdccidYzt3CaHD1crCFTS1hxyhSi059NhOFUf03YFo= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/square/go-jose.v1 v1.1.2 h1:/5jmADZB+RiKtZGr4HxsEFOEfbfsjTKsVnqpThUpE30= gopkg.in/square/go-jose.v1 v1.1.2/go.mod h1:QpYS+a4WhS+DTlyQIi6Ka7MS3SuR9a055rgXNEe6EiA= -gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= helm.sh/helm/v3 v3.0.0 h1:or/9cs1GgfcTQeEnR2CVJNw893/rmqIG1KsNHmUiSFw= helm.sh/helm/v3 v3.0.0/go.mod h1:sI7B9yfvMgxtTPMWdk1jSKJ2aa59UyP9qhPydqW6mgo= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -833,14 +707,12 @@ k8s.io/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-201910010437 k8s.io/kubernetes/staging/src/k8s.io/apiextensions-apiserver v0.0.0-20191001043732-d647ddbd755f/go.mod h1:f1tFT2pOqPzfckbG1GjHIzy3G+T2LW7rchcruNoLaiM= k8s.io/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20191001043732-d647ddbd755f h1:X3br+JCtf40mnzQsKAnHnezd1CvCENgG5uLJTbAspZ4= k8s.io/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20191001043732-d647ddbd755f/go.mod h1:PNw+FbGH4/s3zK9V3rAeMiHTbQz2CU/yqAkfQ2UgLVs= -k8s.io/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20191001043732-d647ddbd755f h1:QIhu1g7jmiv/90qGiPiCOTHFYEcrL0HA5P/6G/pt7zM= k8s.io/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20191001043732-d647ddbd755f/go.mod h1:WmFoxjELD2xtWb77Yj9RPibT5ACkQYEW9lPQtNkGtbE= k8s.io/kubernetes/staging/src/k8s.io/cli-runtime v0.0.0-20191001043732-d647ddbd755f h1:6CkT409OUoX4ZiP++1N3id3PCcOoktBvclNsDKPKrfc= k8s.io/kubernetes/staging/src/k8s.io/cli-runtime v0.0.0-20191001043732-d647ddbd755f/go.mod h1:nBogvbgjMgo7AeVA6CuqVO13LVIfmlQ11t6xzAJdBN8= k8s.io/kubernetes/staging/src/k8s.io/client-go v0.0.0-20191001043732-d647ddbd755f h1:ksJC2cpBqkCP8bzmfDYXr65JRpt9JmANvaKIR3qggt4= k8s.io/kubernetes/staging/src/k8s.io/client-go v0.0.0-20191001043732-d647ddbd755f/go.mod h1:GiGfbsjtP4tOW6zgpL8/vCUoyXAV5+9X2onLursPi08= k8s.io/kubernetes/staging/src/k8s.io/code-generator v0.0.0-20191001043732-d647ddbd755f/go.mod h1:L8deZCu6NpzgKzY91TOGKJ1JtAoHd8WyJ/HdoxqZCGo= -k8s.io/kubernetes/staging/src/k8s.io/component-base v0.0.0-20191001043732-d647ddbd755f h1:fwZSUxpQ99UBEkIhHbzY2pE3SPU9Zn4yZkMSolEt6Jw= k8s.io/kubernetes/staging/src/k8s.io/component-base v0.0.0-20191001043732-d647ddbd755f/go.mod h1:spPP+vRNS8EsnNNIhFCZTTuRO3XhV1WoF18HJySoZn8= k8s.io/kubernetes/staging/src/k8s.io/kubectl v0.0.0-20191001043732-d647ddbd755f h1:vH4+rTRLDI8z9dQCZ6cJcIi3RMGZ6JwJWyLbrSNHBCE= k8s.io/kubernetes/staging/src/k8s.io/kubectl v0.0.0-20191001043732-d647ddbd755f/go.mod h1:ellVfoCz8MlDjTnkqsTkU5svJOIjcK3XNx/onmixgDk= @@ -853,14 +725,10 @@ modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= -rsc.io/letsencrypt v0.0.1 h1:DV0d09Ne9E7UUa9ZqWktZ9L2VmybgTgfq7xlfFR/bbU= rsc.io/letsencrypt v0.0.1/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY= sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0= sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= -sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca h1:6dsH6AYQWbyZmtttJNe8Gq1cXOeS1BdV3eW37zHilAQ= sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 h1:O69FD9pJA4WUZlEwYatBEEkRWKQ5cKodWpdKTrCS/iQ= -vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= diff --git a/src/jetstream/plugins/kubernetes/go.mod b/src/jetstream/plugins/kubernetes/go.mod index 6e44da2a86..ebeac9e5a1 100644 --- a/src/jetstream/plugins/kubernetes/go.mod +++ b/src/jetstream/plugins/kubernetes/go.mod @@ -29,7 +29,6 @@ require ( k8s.io/api v0.0.0-20191016110408-35e52d86657a k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8 k8s.io/client-go v0.0.0-20191016111102-bec269661e48 - vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 // indirect ) replace ( From 1e7f1ae428a2fb561d2a7b8deeed9f5f358595ed Mon Sep 17 00:00:00 2001 From: Richard Cox <richard-cox@users.noreply.github.com> Date: Wed, 2 Sep 2020 10:47:18 +0100 Subject: [PATCH 605/648] Add a 'live reload' button to workload views (#443) * Add a 'live reload' button to workload views - move workload socket code into it's own service - create toggle component to start/stop socket * Add pause/unpause workload socket, used instead of stop/start * Changes following review * Fix unit tests --- .../kubernetes/kubernetes.testing.module.ts | 2 + .../helm-release-socket-service.ts | 209 ++++++++++++++++++ .../helm-release-tab-base.component.ts | 153 +------------ .../release/tabs/helm-release-helpers.scss | 8 + .../helm-release-pods-tab.component.html | 3 + .../helm-release-pods-tab.component.scss | 3 + .../helm-release-pods-tab.component.spec.ts | 14 +- ...helm-release-resource-graph.component.html | 2 + ...helm-release-resource-graph.component.scss | 6 +- .../helm-release-services-tab.component.html | 3 + .../helm-release-services-tab.component.scss | 3 + ...elm-release-services-tab.component.spec.ts | 14 +- .../helm-release-summary-tab.component.html | 2 + .../helm-release-summary-tab.component.scss | 2 + .../workload-live-reload.component.html | 3 + .../workload-live-reload.component.scss | 3 + .../workload-live-reload.component.spec.ts | 37 ++++ .../workload-live-reload.component.ts | 25 +++ .../kubernetes/workloads/workloads.module.ts | 2 + src/jetstream/plugins/cfapppush/deploy.go | 2 +- .../plugins/kubernetes/get_release.go | 68 +++++- 21 files changed, 405 insertions(+), 159 deletions(-) create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-socket-service.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helpers.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.html create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.spec.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.testing.module.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.testing.module.ts index e1967ff707..ea8fa319f8 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.testing.module.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.testing.module.ts @@ -5,6 +5,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { CoreModule } from '../../../../core/src/core/core.module'; import { SharedModule } from '../../../../core/src/shared/shared.module'; +import { TabNavService } from '../../../../core/tab-nav.service'; import { AppTestModule } from '../../../../core/test-framework/core-test.helper'; import { CATALOGUE_ENTITIES, EntityCatalogFeatureModule } from '../../../../store/src/entity-catalog.module'; import { entityCatalog, TestEntityCatalog } from '../../../../store/src/entity-catalog/entity-catalog'; @@ -49,6 +50,7 @@ export const HelmReleaseProviders = [ HelmReleaseHelperService, HelmReleaseActivatedRouteMock, HelmReleaseGuidMock, + TabNavService ]; export const KubeBaseGuidMock = { provide: BaseKubeGuid, useValue: { guid: 'anything' } }; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-socket-service.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-socket-service.ts new file mode 100644 index 0000000000..cb2eb017b2 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-socket-service.ts @@ -0,0 +1,209 @@ +import { Injectable } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { Subject, Subscription } from 'rxjs'; +import makeWebSocketObservable, { GetWebSocketResponses } from 'rxjs-websockets'; +import { catchError, map, share, switchMap } from 'rxjs/operators'; + +import { LoggerService } from '../../../../../../../core/src/core/logger.service'; +import { SnackBarService } from '../../../../../../../core/src/shared/services/snackbar.service'; +import { AppState } from '../../../../../../../store/src/app-state'; +import { entityCatalog } from '../../../../../../../store/src/entity-catalog/entity-catalog'; +import { EntityRequestAction, WrapperRequestActionSuccess } from '../../../../../../../store/src/types/request.types'; +import { kubeEntityCatalog } from '../../../kubernetes-entity-catalog'; +import { KubernetesPodExpandedStatusHelper } from '../../../services/kubernetes-expanded-state'; +import { KubernetesPod, KubeService } from '../../../store/kube.types'; +import { KubePaginationAction } from '../../../store/kubernetes.actions'; +import { HelmReleaseGraph, HelmReleasePod, HelmReleaseService } from '../../workload.types'; +import { workloadsEntityCatalog } from '../../workloads-entity-catalog'; +import { HelmReleaseHelperService } from '../tabs/helm-release-helper.service'; + + +enum SocketEventTypes { + PAUSE_TRUE = 20000, + PAUSE_FALSE = 20001, +} + +interface SocketMessage { + type: SocketEventTypes +} + +@Injectable() +export class HelmReleaseSocketService { + + private sub: Subscription; + private sendToSocket = new Subject<any>(); + + constructor( + private helmReleaseHelper: HelmReleaseHelperService, + private store: Store<AppState>, + private logService: LoggerService, + private snackbarService: SnackBarService, + ) { + + } + + public start() { + if (this.isStarted()) { + return; + } + + const releaseRef = this.helmReleaseHelper.guidAsUrlFragment(); + const host = window.location.host; + const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws'; + const streamUrl = ( + `${protocol}://${host}/pp/v1/helm/releases/${releaseRef}/status` + ); + + const socket$ = makeWebSocketObservable(streamUrl).pipe(catchError(e => { + this.logService.error( + 'Error while connecting to socket: ' + JSON.stringify(e) + ); + return []; + }), + share(), + ); + + const messages = socket$.pipe( + switchMap((getResponses: GetWebSocketResponses) => { + return getResponses(this.sendToSocket); + }), + map((message: string) => message), + catchError(e => { + console.error('Workload WS error: ', e); + return []; + }) + ); + + let prefix = ''; + this.sub = messages.subscribe(jsonString => { + const messageObj = JSON.parse(jsonString); + if (messageObj) { + if (messageObj.kind === 'ReleasePrefix') { + prefix = messageObj.data; + } else if (messageObj.kind === 'Pods') { + const pods: KubernetesPod[] = messageObj.data || []; + const podsWithInfo: KubernetesPod[] = pods.map(pod => KubernetesPodExpandedStatusHelper.updatePodWithExpandedStatus(pod)); + const releasePodsAction = kubeEntityCatalog.pod.actions.getInWorkload( + this.helmReleaseHelper.endpointGuid, + this.helmReleaseHelper.releaseTitle + ); + this.populateList(releasePodsAction, podsWithInfo); + } else if (messageObj.kind === 'Graph') { + const graph: HelmReleaseGraph = messageObj.data; + graph.endpointId = this.helmReleaseHelper.endpointGuid; + graph.releaseTitle = this.helmReleaseHelper.releaseTitle; + const releaseGraphAction = workloadsEntityCatalog.graph.actions.get(graph.releaseTitle, graph.endpointId); + this.addResource(releaseGraphAction, graph); + } else if (messageObj.kind === 'Manifest' || messageObj.kind === 'Resources') { + // Store all of the services + const manifest = messageObj.data; + const svcs: KubeService[] = []; + // Store ALL resources for the release + manifest.forEach(resource => { + if (resource.kind === 'Service' && prefix) { + svcs.push(resource); + } + }); + if (svcs.length > 0) { + const releaseServicesAction = kubeEntityCatalog.service.actions.getInWorkload( + this.helmReleaseHelper.releaseTitle, + this.helmReleaseHelper.endpointGuid, + ); + this.populateList(releaseServicesAction, svcs); + } + + // const resources = { ...manifest }; + // kind === 'Resources' is an array, really they should go into a pagination section + messageObj.endpointId = this.helmReleaseHelper.endpointGuid; + messageObj.releaseTitle = this.helmReleaseHelper.releaseTitle; + + const releaseResourceAction = workloadsEntityCatalog.resource.actions.get( + this.helmReleaseHelper.releaseTitle, + this.helmReleaseHelper.endpointGuid, + ); + this.addResource(releaseResourceAction, messageObj); + } else if (messageObj.kind === 'ManifestErrors') { + if (messageObj.data) { + this.snackbarService.show('Errors were found when parsing this workload. Not all resources may be shown', 'Dismiss'); + } + } + } + }); + } + + public stop() { + if (this.sub) { + this.sub.unsubscribe() + this.sub = null; + } + } + + public enable(enable: boolean) { + if (enable) { + this.start() + } else { + this.stop() + } + } + + public isStarted(): boolean { + return !!this.sub; + } + + public pause(pause: boolean) { + if (pause != this.isPaused) { + const message: SocketMessage = { + type: pause ? SocketEventTypes.PAUSE_TRUE : SocketEventTypes.PAUSE_FALSE + } + this.sendToSocket.next(JSON.stringify(message)); + this.isPaused = pause; + } + } + + public isPaused = false; + + ngOnDestroy() { + this.sub.unsubscribe(); + this.snackbarService.hide(); + } + + private addResource(action: EntityRequestAction, data: any) { + const catalogEntity = entityCatalog.getEntity(action); + const response = { + entities: { + [catalogEntity.entityKey]: { + [action.guid]: data + } + }, + result: [ + action.guid + ] + }; + const successWrapper = new WrapperRequestActionSuccess(response, action); + this.store.dispatch(successWrapper); + } + + private populateList(action: KubePaginationAction, resources: any) { + const entity = entityCatalog.getEntity(action); + const newResources = {}; + resources.forEach(resource => { + const newResource: HelmReleasePod | HelmReleaseService = { + endpointId: action.kubeGuid, + releaseTitle: this.helmReleaseHelper.releaseTitle, + ...resource + }; + newResource.metadata.kubeId = action.kubeGuid; + // The service entity from manifest is missing this, but apply here to ensure any others are caught + newResource.metadata.namespace = this.helmReleaseHelper.namespace; + const entityId = action.entity[0].getId(resource); + newResources[entityId] = newResource; + }); + + const releasePods = { + entities: { [entity.entityKey]: newResources }, + result: Object.keys(newResources) + }; + const successWrapper = new WrapperRequestActionSuccess(releasePods, action, 'fetch', releasePods.result.length, 1); + this.store.dispatch(successWrapper); + } +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts index b84d708c7c..8bad3751c8 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts @@ -1,25 +1,15 @@ import { Component, OnDestroy } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { Store } from '@ngrx/store'; -import { Observable, Subject, Subscription } from 'rxjs'; -import makeWebSocketObservable, { GetWebSocketResponses } from 'rxjs-websockets'; -import { catchError, map, share, switchMap } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; -import { LoggerService } from '../../../../../../../core/src/core/logger.service'; import { IPageSideNavTab } from '../../../../../../../core/src/features/dashboard/page-side-nav/page-side-nav.component'; import { SessionService } from '../../../../../../../core/src/shared/services/session.service'; import { SnackBarService } from '../../../../../../../core/src/shared/services/snackbar.service'; -import { AppState } from '../../../../../../../store/src/app-state'; -import { entityCatalog } from '../../../../../../../store/src/entity-catalog/entity-catalog'; -import { EntityRequestAction, WrapperRequestActionSuccess } from '../../../../../../../store/src/types/request.types'; -import { kubeEntityCatalog } from '../../../kubernetes-entity-catalog'; -import { KubernetesPodExpandedStatusHelper } from '../../../services/kubernetes-expanded-state'; import { KubernetesAnalysisService } from '../../../services/kubernetes.analysis.service'; -import { KubernetesPod, KubeService } from '../../../store/kube.types'; -import { KubePaginationAction } from '../../../store/kubernetes.actions'; -import { HelmReleaseGraph, HelmReleaseGuid, HelmReleasePod, HelmReleaseService } from '../../workload.types'; -import { workloadsEntityCatalog } from '../../workloads-entity-catalog'; +import { HelmReleaseGuid } from '../../workload.types'; import { HelmReleaseHelperService } from '../tabs/helm-release-helper.service'; +import { HelmReleaseSocketService } from './helm-release-socket-service'; @Component({ @@ -37,15 +27,14 @@ import { HelmReleaseHelperService } from '../tabs/helm-release-helper.service'; deps: [ ActivatedRoute ] - } + }, + HelmReleaseSocketService ] }) export class HelmReleaseTabBaseComponent implements OnDestroy { isFetching$: Observable<boolean>; - private sub: Subscription; - public breadcrumbs = [{ breadcrumbs: [ { value: 'Workloads', routerLink: '/workloads' } @@ -58,11 +47,10 @@ export class HelmReleaseTabBaseComponent implements OnDestroy { constructor( public helmReleaseHelper: HelmReleaseHelperService, - private store: Store<AppState>, - private logService: LoggerService, private analysisService: KubernetesAnalysisService, private snackbarService: SnackBarService, - sessionService: SessionService + sessionService: SessionService, + private socketService: HelmReleaseSocketService ) { this.title = this.helmReleaseHelper.releaseTitle; @@ -77,132 +65,11 @@ export class HelmReleaseTabBaseComponent implements OnDestroy { { link: 'services', label: 'Services', icon: 'service', iconFont: 'stratos-icons' } ]; - const releaseRef = this.helmReleaseHelper.guidAsUrlFragment(); - const host = window.location.host; - const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws'; - const streamUrl = ( - `${protocol}://${host}/pp/v1/helm/releases/${releaseRef}/status` - ); - - const socket$ = makeWebSocketObservable(streamUrl).pipe(catchError(e => { - this.logService.error( - 'Error while connecting to socket: ' + JSON.stringify(e) - ); - return []; - }), - share(), - ); - - const messages = socket$.pipe( - switchMap((getResponses: GetWebSocketResponses) => { - return getResponses(new Subject<string>()); - }), - map((message: string) => message), - catchError(e => { - console.error('Workload WS error: ', e); - return []; - }) - ); - - let prefix = ''; - this.sub = messages.subscribe(jsonString => { - const messageObj = JSON.parse(jsonString); - if (messageObj) { - if (messageObj.kind === 'ReleasePrefix') { - prefix = messageObj.data; - } else if (messageObj.kind === 'Pods') { - const pods: KubernetesPod[] = messageObj.data || []; - const podsWithInfo: KubernetesPod[] = pods.map(pod => KubernetesPodExpandedStatusHelper.updatePodWithExpandedStatus(pod)); - const releasePodsAction = kubeEntityCatalog.pod.actions.getInWorkload( - this.helmReleaseHelper.endpointGuid, - this.helmReleaseHelper.releaseTitle - ); - this.populateList(releasePodsAction, podsWithInfo); - } else if (messageObj.kind === 'Graph') { - const graph: HelmReleaseGraph = messageObj.data; - graph.endpointId = this.helmReleaseHelper.endpointGuid; - graph.releaseTitle = this.helmReleaseHelper.releaseTitle; - const releaseGraphAction = workloadsEntityCatalog.graph.actions.get(graph.releaseTitle, graph.endpointId); - this.addResource(releaseGraphAction, graph); - } else if (messageObj.kind === 'Manifest' || messageObj.kind === 'Resources') { - // Store all of the services - const manifest = messageObj.data; - const svcs: KubeService[] = []; - // Store ALL resources for the release - manifest.forEach(resource => { - if (resource.kind === 'Service' && prefix) { - svcs.push(resource); - } - }); - if (svcs.length > 0) { - const releaseServicesAction = kubeEntityCatalog.service.actions.getInWorkload( - this.helmReleaseHelper.releaseTitle, - this.helmReleaseHelper.endpointGuid, - ); - this.populateList(releaseServicesAction, svcs); - } - - // const resources = { ...manifest }; - // kind === 'Resources' is an array, really they should go into a pagination section - messageObj.endpointId = this.helmReleaseHelper.endpointGuid; - messageObj.releaseTitle = this.helmReleaseHelper.releaseTitle; - - const releaseResourceAction = workloadsEntityCatalog.resource.actions.get( - this.helmReleaseHelper.releaseTitle, - this.helmReleaseHelper.endpointGuid, - ); - this.addResource(releaseResourceAction, messageObj); - } else if (messageObj.kind === 'ManifestErrors') { - if (messageObj.data) { - this.snackbarService.show('Errors were found when parsing this workload. Not all resources may be shown', 'Dismiss'); - } - } - } - }); - } - - private addResource(action: EntityRequestAction, data: any) { - const catalogEntity = entityCatalog.getEntity(action); - const response = { - entities: { - [catalogEntity.entityKey]: { - [action.guid]: data - } - }, - result: [ - action.guid - ] - }; - const successWrapper = new WrapperRequestActionSuccess(response, action); - this.store.dispatch(successWrapper); - } - - private populateList(action: KubePaginationAction, resources: any) { - const entity = entityCatalog.getEntity(action); - const newResources = {}; - resources.forEach(resource => { - const newResource: HelmReleasePod | HelmReleaseService = { - endpointId: action.kubeGuid, - releaseTitle: this.helmReleaseHelper.releaseTitle, - ...resource - }; - newResource.metadata.kubeId = action.kubeGuid; - // The service entity from manifest is missing this, but apply here to ensure any others are caught - newResource.metadata.namespace = this.helmReleaseHelper.namespace; - const entityId = action.entity[0].getId(resource); - newResources[entityId] = newResource; - }); - - const releasePods = { - entities: { [entity.entityKey]: newResources }, - result: Object.keys(newResources) - }; - const successWrapper = new WrapperRequestActionSuccess(releasePods, action, 'fetch', releasePods.result.length, 1); - this.store.dispatch(successWrapper); + this.socketService.start() } ngOnDestroy() { - this.sub.unsubscribe(); + this.socketService.stop() this.snackbarService.hide(); } } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helpers.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helpers.scss new file mode 100644 index 0000000000..8b6c1eeb83 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helpers.scss @@ -0,0 +1,8 @@ + +@mixin add-life-update-style { + app-workload-live-reload { + display: flex; + flex: 1; + justify-content: flex-end; + } +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.html index dd2b6cb351..0bb77b9833 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.html @@ -1 +1,4 @@ +<app-page-sub-nav> + <app-workload-live-reload></app-workload-live-reload> +</app-page-sub-nav> <app-list></app-list> \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.scss index e69de29bb2..81703c5b2a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.scss @@ -0,0 +1,3 @@ +@import '../helm-release-helpers'; + +@include add-life-update-style diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.spec.ts index 2dc9718ec8..a312703b4e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.spec.ts @@ -1,6 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { HelmReleaseGuidMock } from '../../../../../helm/helm-testing.module'; import { HelmReleaseProviders, KubernetesBaseTestModules } from '../../../../kubernetes.testing.module'; +import { HelmReleaseSocketService } from '../../helm-release-tab-base/helm-release-socket-service'; +import { WorkloadLiveReloadComponent } from '../../workload-live-reload/workload-live-reload.component'; +import { HelmReleaseHelperService } from '../helm-release-helper.service'; import { HelmReleasePodsTabComponent } from './helm-release-pods-tab.component'; describe('HelmReleasePodsTabComponent', () => { @@ -12,9 +16,15 @@ describe('HelmReleasePodsTabComponent', () => { imports: [ ...KubernetesBaseTestModules ], - declarations: [HelmReleasePodsTabComponent], + declarations: [ + HelmReleasePodsTabComponent, + WorkloadLiveReloadComponent + ], providers: [ - ...HelmReleaseProviders + ...HelmReleaseProviders, + HelmReleaseSocketService, + HelmReleaseHelperService, + HelmReleaseGuidMock ] }) .compileComponents(); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.html index 1070d1925c..9087c4666a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.html @@ -10,6 +10,8 @@ <app-analysis-report-selector *ngIf="analyzerService.enabled$ | async" (selected)="analysisChanged($event)" [endpoint]="helper.endpointGuid" [path]="path"></app-analysis-report-selector> + + <app-workload-live-reload></app-workload-live-reload> </app-page-sub-nav> <ngx-graph *ngIf="nodes && nodes.length" class="chart-container" [draggingEnabled]="false" [links]="links" diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.scss index beb8c49098..a41e57f918 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.scss @@ -1,3 +1,5 @@ +@import '../helm-release-helpers'; + :host { display: flex; height: 100%; @@ -8,4 +10,6 @@ to { stroke-dashoffset: 1000; } -} \ No newline at end of file +} + +@include add-life-update-style diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.html index dd2b6cb351..0bb77b9833 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.html @@ -1 +1,4 @@ +<app-page-sub-nav> + <app-workload-live-reload></app-workload-live-reload> +</app-page-sub-nav> <app-list></app-list> \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.scss index e69de29bb2..81703c5b2a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.scss @@ -0,0 +1,3 @@ +@import '../helm-release-helpers'; + +@include add-life-update-style diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.spec.ts index 392dd17d4b..b54d40a909 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.spec.ts @@ -1,6 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { HelmReleaseGuidMock } from '../../../../../helm/helm-testing.module'; import { HelmReleaseProviders, KubernetesBaseTestModules } from '../../../../kubernetes.testing.module'; +import { HelmReleaseSocketService } from '../../helm-release-tab-base/helm-release-socket-service'; +import { WorkloadLiveReloadComponent } from '../../workload-live-reload/workload-live-reload.component'; +import { HelmReleaseHelperService } from '../helm-release-helper.service'; import { HelmReleaseServicesTabComponent } from './helm-release-services-tab.component'; @@ -13,9 +17,15 @@ describe('HelmReleaseServicesTabComponent', () => { imports: [ ...KubernetesBaseTestModules ], - declarations: [HelmReleaseServicesTabComponent], + declarations: [ + HelmReleaseServicesTabComponent, + WorkloadLiveReloadComponent + ], providers: [ - ...HelmReleaseProviders + ...HelmReleaseProviders, + HelmReleaseSocketService, + HelmReleaseHelperService, + HelmReleaseGuidMock ] }) .compileComponents(); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html index 0bfbe0568a..ad0b133c87 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html @@ -10,6 +10,8 @@ <app-analysis-report-selector *ngIf="analyzerService.enabled$ | async" (selected)="analysisChanged($event)" [endpoint]="helmReleaseHelper.endpointGuid" [path]="path"></app-analysis-report-selector> + + <app-workload-live-reload></app-workload-live-reload> </app-page-sub-nav> <app-loading-page [isLoading]="isBusy$" [text]="loadingMessage"> diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss index 1e7019d68d..d57c1bb42f 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss @@ -1,5 +1,6 @@ @import '../../../../../../../../core/sass/mixins'; +@import '../helm-release-helpers'; .resources { padding: 0 10px; @@ -67,3 +68,4 @@ } } +@include add-life-update-style diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.html new file mode 100644 index 0000000000..70f1d6801e --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.html @@ -0,0 +1,3 @@ +<mat-slide-toggle [color]="'primary'" [checked]="checked" labelPosition="before" (change)="onChange($event)"> + Live Updates +</mat-slide-toggle> \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.scss new file mode 100644 index 0000000000..dc5ff7f622 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.scss @@ -0,0 +1,3 @@ +mat-slide-toggle { + font-size: 15px; +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.spec.ts new file mode 100644 index 0000000000..df86d31d29 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.spec.ts @@ -0,0 +1,37 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HelmReleaseGuidMock } from '../../../../helm/helm-testing.module'; +import { KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; +import { HelmReleaseSocketService } from '../helm-release-tab-base/helm-release-socket-service'; +import { HelmReleaseHelperService } from '../tabs/helm-release-helper.service'; +import { WorkloadLiveReloadComponent } from './workload-live-reload.component'; + +describe('WorkloadLiveReloadComponent', () => { + let component: WorkloadLiveReloadComponent; + let fixture: ComponentFixture<WorkloadLiveReloadComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + ...KubernetesBaseTestModules + ], + declarations: [WorkloadLiveReloadComponent], + providers: [ + HelmReleaseSocketService, + HelmReleaseHelperService, + HelmReleaseGuidMock + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WorkloadLiveReloadComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.ts new file mode 100644 index 0000000000..35dd56caad --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.ts @@ -0,0 +1,25 @@ +import { Component, OnInit } from '@angular/core'; + +import { HelmReleaseSocketService } from '../helm-release-tab-base/helm-release-socket-service'; + +@Component({ + selector: 'app-workload-live-reload', + templateUrl: './workload-live-reload.component.html', + styleUrls: ['./workload-live-reload.component.scss'] +}) +export class WorkloadLiveReloadComponent implements OnInit { + public checked = false; + + constructor( + private socketService: HelmReleaseSocketService + ) { } + + ngOnInit(): void { + this.checked = this.socketService.isStarted(); + } + + public onChange(event) { + this.socketService.pause(!event.checked) + } + +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts index aaed18524c..05804cb596 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts @@ -19,6 +19,7 @@ import { HelmReleasesTabComponent } from './releases-tab/releases-tab.component' import { WorkloadsStoreModule } from './store/workloads.store.module'; import { WorkloadsRouting } from './workloads.routing'; import { HelmReleaseAnalysisTabComponent } from './release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component'; +import { WorkloadLiveReloadComponent } from './release/workload-live-reload/workload-live-reload.component'; @NgModule({ imports: [ @@ -41,6 +42,7 @@ import { HelmReleaseAnalysisTabComponent } from './release/tabs/helm-release-ana HelmReleaseResourceGraphComponent, HelmReleaseCardComponent, HelmReleaseAnalysisTabComponent, + WorkloadLiveReloadComponent, ], entryComponents: [ HelmReleaseCardComponent diff --git a/src/jetstream/plugins/cfapppush/deploy.go b/src/jetstream/plugins/cfapppush/deploy.go index d68b09019e..e28c37d8b9 100644 --- a/src/jetstream/plugins/cfapppush/deploy.go +++ b/src/jetstream/plugins/cfapppush/deploy.go @@ -223,7 +223,7 @@ func (cfAppPush *CFAppPush) deploy(echoContext echo.Context) error { sendEvent(clientWebSocket, CLOSE_SUCCESS) - log.Debug("Waiting for close ackhowledgement from the client") + log.Debug("Waiting for close acknowledgement from the client") wait := 30 * time.Second clientWebSocket.SetReadDeadline(time.Now().Add(wait)) diff --git a/src/jetstream/plugins/kubernetes/get_release.go b/src/jetstream/plugins/kubernetes/get_release.go index e4627830ab..4e1c527107 100644 --- a/src/jetstream/plugins/kubernetes/get_release.go +++ b/src/jetstream/plugins/kubernetes/get_release.go @@ -3,6 +3,7 @@ package kubernetes import ( "encoding/json" "fmt" + "io/ioutil" "time" "github.com/gorilla/websocket" @@ -15,6 +16,17 @@ import ( "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" ) +const ( + PauseTrue int = iota + 20000 + PauseFalse +) + +// ResourceMessage ... Incoming content of socket +type ResourceMessage struct { + MessageType int `json:"type"` +} + +// ResourceResponse ... Outgoing content of socket type ResourceResponse struct { Kind string `json:"kind"` Data json.RawMessage `json:"data"` @@ -98,7 +110,7 @@ func (c *KubernetesSpecification) GetReleaseStatus(ec echo.Context) error { // ws is the websocket ready for use - // Write the release info first - we will then go fetch the status of evertyhing in the release and send + // Write the release info first - we will then go fetch the status of everything in the release and send // this back incrementally // Parse the manifest @@ -145,16 +157,20 @@ func (c *KubernetesSpecification) GetReleaseStatus(ec echo.Context) error { sendResource(ws, "ManifestErrors", rel.ManifestErrors) stopchan := make(chan bool) + pausechan := make(chan bool) - go readLoop(ws, stopchan) + go readLoop(ws, stopchan, pausechan) var sleep = 1 * time.Second + var paused = false // Now we have everything, so loop, polling to get status for { - log.Debug("Polling for release - wait 10 seconds") select { + case pause := <-pausechan: + paused = pause + break case <-stopchan: ws.Close() return nil @@ -162,7 +178,12 @@ func (c *KubernetesSpecification) GetReleaseStatus(ec echo.Context) error { break } - log.Debug("Polling for release ....") + if paused { + log.Debug("Updating release resources paused ....") + continue + } + + log.Debug("Updating release resources ....") // Pods rel.UpdatePods(c.portalProxy) @@ -180,15 +201,42 @@ func (c *KubernetesSpecification) GetReleaseStatus(ec echo.Context) error { sleep = 10 * time.Second } - - ws.Close() - - return nil } -func readLoop(c *websocket.Conn, stopchan chan<- bool) { +func readLoop(c *websocket.Conn, stopchan chan<- bool, pausechan chan<- bool) { for { - if _, _, err := c.NextReader(); err != nil { + + messageType, r, err := c.NextReader() + if err != nil { + c.Close() + close(stopchan) + break + } + + switch messageType { + case websocket.TextMessage: + data, err := ioutil.ReadAll(r) + if err != nil { + log.Warnf("Failed to read content of helm resource websocket message: %+v", err) + break + } + + message := ResourceMessage{} + err = json.Unmarshal(data, &message) + if err != nil { + log.Warnf("Failed to parse content of helm resource websocket message: %+v", err) + break + } + + switch message.MessageType { + case PauseTrue: + pausechan <- true + break + case PauseFalse: + pausechan <- false + break + } + default: c.Close() close(stopchan) break From 463a6fc648a667324f52bc3e4ba203b8404453dd Mon Sep 17 00:00:00 2001 From: Richard Cox <richard-cox@users.noreply.github.com> Date: Wed, 2 Sep 2020 10:52:38 +0100 Subject: [PATCH 606/648] Update Kube Resource Icons (#461) --- .../kubernetes/workloads/release/icon-helper.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/icon-helper.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/icon-helper.ts index ea2d9e6df0..1bfb949f64 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/icon-helper.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/icon-helper.ts @@ -6,7 +6,7 @@ export function getIcon(kind: string) { return iconMappings.default; } } - + const iconMappings = { Namespace: { name: 'namespace', @@ -62,14 +62,23 @@ const iconMappings = { font: 'stratos-icons' }, Secret: { - name: 'config_map', - font: 'stratos-icons' + name: 'lock', + font: 'Material Icons', + fontSet: 'material-icons' }, ServiceAccount: { - name: 'lock', + name: 'account_circle', font: 'Material Icons', fontSet: 'material-icons' }, + Job: { + name: 'job', + font: 'stratos-icons' + }, + PersistentVolumeClaim: { + name: 'persistent_volume', + font: 'stratos-icons' + }, default: { name: 'collocation', font: 'stratos-icons' From 49e741c14429af773609bfc087430661c4516866 Mon Sep 17 00:00:00 2001 From: Neil MacDougall <nwmac@users.noreply.github.com> Date: Tue, 8 Sep 2020 11:12:43 +0100 Subject: [PATCH 607/648] Indicate if the Helm chart has a schema (#464) * Indicate if the Helm chart has a schema * Fixes * Fix unit tests * Update comment * Fix typo --- .../chart-details-info.component.html | 16 +++++++++-- .../chart-details-info.component.scss | 9 ++---- .../chart-details-info.component.ts | 28 ++++++++++++++++++- .../chart-details-usage.component.html | 9 +++++- .../chart-details-versions.component.scss | 13 +++++++-- .../monocular/shared/models/chart-version.ts | 1 + .../shared/services/charts.service.ts | 13 +++++++++ .../chartsvc/foundationdb/handler.go | 2 ++ 8 files changed, 78 insertions(+), 13 deletions(-) diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.html b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.html index 0329e95550..52ab1483fe 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.html @@ -10,19 +10,29 @@ <div class="chartInfo__home" *ngIf="chart.attributes.home"> <h1>Home</h1> <div> - <a href="{{ chart.attributes.home }}" target="_blank">{{chart.attributes.home}}</a> + <a class="chartInfo__link" href="{{ chart.attributes.home }}" target="_blank">{{chart.attributes.home}}</a> </div> </div> <div class="chartInfo__maintainers" *ngIf="maintainers.length"> <h1>Maintainers</h1> <div *ngFor="let maintainer of maintainers"> - <a href="{{ maintainerUrl(maintainer) }}" target="_blank">{{ maintainer.name }}</a> + <a href="{{ maintainerUrl(maintainer) }}" class="chartInfo__link" target="_blank">{{ maintainer.name }}</a> </div> </div> <div class="chartInfo__related" *ngIf="sources.length"> <h1>Related</h1> <div *ngFor="let source of sources"> - <a href="{{ source }}" class="chartInfo__related-link" target="_blank">{{ source }}</a> + <a href="{{ source }}" class="chartInfo__link" target="_blank">{{ source }}</a> + </div> + </div> + <div class="chartInfo__related" *ngIf="schema"> + <h1>Schema</h1> + <div class="chartInfo__link">This chart contains a values schema</div> + </div> + <div class="chartInfo__related" *ngIf="currentVersion"> + <h1>Chart</h1> + <div *ngFor="let url of currentVersion.attributes.urls"> + <a href="{{ url }}" class="chartInfo__link" target="_blank">{{ url }}</a> </div> </div> </mat-card> diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.scss b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.scss index 1b1558c02c..558bef53f1 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.scss @@ -1,7 +1,7 @@ .chartInfo { h1 { - font-size: 18px; + font-size: 16px; } app-panel { @@ -15,11 +15,7 @@ } } - &__related { - max-width: 280px; - } - - &__related-link { + &__link { font-size: 14px; } @@ -27,5 +23,6 @@ overflow: auto; display: block; margin-bottom: 2em; + max-width: 260px; } } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.ts index c8c46032a7..fad154c9d7 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.ts @@ -1,4 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; +import { of } from 'rxjs'; +import { catchError, first } from 'rxjs/operators'; import { Chart } from '../../shared/models/chart'; import { ChartVersion } from '../../shared/models/chart-version'; @@ -12,8 +14,22 @@ import { ChartsService } from '../../shared/services/charts.service'; }) export class ChartDetailsInfoComponent implements OnInit { @Input() chart: Chart; - @Input() currentVersion: ChartVersion; versions: ChartVersion[]; + schema: any = null; + + _currentVersion: ChartVersion; + + get currentVersion(): ChartVersion { + return this._currentVersion; + } + + @Input() set currentVersion (version: ChartVersion) { + this._currentVersion = version; + if (version) { + this.getSchema(this._currentVersion); + } + } + constructor(private chartsService: ChartsService) { } ngOnInit() { @@ -52,4 +68,14 @@ export class ChartDetailsInfoComponent implements OnInit { repoURL === 'https://kubernetes-charts-incubator.storage.googleapis.com' ); } + + private getSchema(currentVersion: ChartVersion) { + this.chartsService.getChartSchema(currentVersion).pipe( + first(), + catchError(() => of(null)) + ).subscribe(schema => { + this.schema = schema; + }); + } + } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.html b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.html index 1e374b0705..5801bc679a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.html @@ -1,4 +1,11 @@ <div class="chart-details-usage__install"> - <button class="chart-details-usage__install-btn" color="primary" mat-button mat-raised-button + <button *ngIf="endpointsService.hasConnectedEndpointType('k8s') | async; else nok8s" class="chart-details-usage__install-btn" color="primary" mat-button mat-raised-button routerLink="/monocular/install/{{chart.id}}/{{currentVersion}}">Install Chart</button> </div> + +<!-- If there are no Kubernetes endpoints connected, show a disabled buitton with a tool tip--> +<ng-template #nok8s> + <div matTooltip="Add a Kubernetes endpoint to install charts"> + <button disabled="true" class="chart-details-usage__install-btn" color="primary" mat-button mat-raised-button>Install Chart</button> + </div> +</ng-template> diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.scss b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.scss index b3002ff9d3..7a82f45467 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.scss @@ -3,11 +3,17 @@ .versions { h1 { - font-size: 18px; + font-size: 16px; margin-top: 0; } .more-link { + cursor: pointer; + font-size: 14px; margin-top: .5em; + + &:hover { + text-decoration: underline; + } } } @@ -23,6 +29,8 @@ } .version { + font-size: 14px; + &__table { display: table; width: 100%; @@ -32,11 +40,12 @@ } &__cell { display: table-cell; + font-size: 14px; } } .app-version { h1 { - font-size: 18px; + font-size: 16px; } } \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/models/chart-version.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/models/chart-version.ts index b3e7671f9a..56a2030021 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/models/chart-version.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/models/chart-version.ts @@ -13,6 +13,7 @@ export class ChartVersionAttributes { icons: ChartVersionIcon[]; readme: string; version: string; + schema?: string; /* tslint:disable-next-line:variable-name */ app_version: string; urls: string[]; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts index c56a82df08..85cfae1317 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts @@ -109,6 +109,19 @@ export class ChartsService { responseType: 'text' }); } + + /** + * Get a chart's Schema using the API + * + * @param repo Repository name + * @param chartName Chart name + * @param version Chart version + * @return An observable that will be the json schema + */ + getChartSchema(chartVersion: ChartVersion): Observable<any> { + return this.http.get(`${this.hostname}${chartVersion.attributes.schema}`); + } + /** * Get chart versions using the API * diff --git a/src/jetstream/plugins/monocular/chartsvc/foundationdb/handler.go b/src/jetstream/plugins/monocular/chartsvc/foundationdb/handler.go index a0fb5863bf..d937f43c06 100644 --- a/src/jetstream/plugins/monocular/chartsvc/foundationdb/handler.go +++ b/src/jetstream/plugins/monocular/chartsvc/foundationdb/handler.go @@ -501,6 +501,8 @@ func newChartListResponse(charts []*models.Chart) utils.ApiListResponse { func chartVersionAttributes(cid string, cv models.ChartVersion) models.ChartVersion { cv.Readme = pathPrefix + "/assets/" + cid + "/versions/" + cv.Version + "/README.md" cv.Values = pathPrefix + "/assets/" + cid + "/versions/" + cv.Version + "/values.yaml" + cv.Schema = pathPrefix + "/assets/" + cid + "/versions/" + cv.Version + "/values.schema.json" + return cv } From aa675d36bc7b1e564b864b751abc6add830b097b Mon Sep 17 00:00:00 2001 From: Richard Cox <richard-cox@users.noreply.github.com> Date: Tue, 8 Sep 2020 11:13:03 +0100 Subject: [PATCH 608/648] Enable linting for suse-extensions package (#465) * Enable linting for suse-extensions package * Fix linting --- angular.json | 12 + package-lock.json | 4448 ++++++++++------- package.json | 2 +- .../chart-item/chart-item.component.ts | 2 +- .../list-filters/list-filters.component.ts | 4 +- .../list-item/list-item.component.ts | 2 +- .../helm/monocular/loader/loader.component.ts | 2 +- .../helm/monocular/panel/panel.component.ts | 2 +- .../src/custom/helm/store/helm.effects.ts | 60 +- .../kube-config-selection.component.ts | 22 +- .../kube-config-table-name.component.spec.ts | 14 +- .../kube-config-table-name.component.ts | 2 +- .../kube-config.types.ts | 6 +- .../kubernetes/kubernetes-entity-generator.ts | 16 +- .../kubernetes-resource-viewer.component.ts | 7 +- .../kubernetes/kubernetes.setup.module.ts | 6 +- .../analysis-reports-list-source.ts | 8 +- .../kubernetes-pod-containers.component.ts | 17 +- .../services/kubernetes.analysis.service.ts | 14 +- .../kubernetes/services/route.helper.ts | 2 +- .../action-builders/kube.action-builders.ts | 44 +- ...naylsis.actions.ts => analysis.actions.ts} | 4 +- .../kubernetes/store/analysis.effects.ts | 4 +- .../helm-release-socket-service.ts | 19 +- .../workloads/release/icon-helper.ts | 20 +- .../helm-release-analysis-tab.component.ts | 5 +- .../tabs/helm-release-helper.service.ts | 7 +- .../helm-release-resource-graph.component.ts | 12 +- .../store/workloads-entity-factory.ts | 14 +- .../suse-welcome/suse-welcome.component.ts | 2 +- .../packages/suse-extensions/tslint.json | 3 + tslint.json | 1 - 32 files changed, 2863 insertions(+), 1920 deletions(-) rename src/frontend/packages/suse-extensions/src/custom/kubernetes/store/{anaylsis.actions.ts => analysis.actions.ts} (97%) create mode 100644 src/frontend/packages/suse-extensions/tslint.json diff --git a/angular.json b/angular.json index 95d3bd1b49..001160c86a 100644 --- a/angular.json +++ b/angular.json @@ -365,6 +365,18 @@ "tsConfig": "src/frontend/packages/suse-extensions/tsconfig.spec.json", "karmaConfig": "src/frontend/packages/suse-extensions/karma.conf.js" } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "src/tsconfig.json" + ], + "tslintConfig": "src/frontend/packages/suse-extensions/tslint.json", + "files": [ + "src/frontend/packages/suse-extensions/src/**/*.ts" + ] + } } } } diff --git a/package-lock.json b/package-lock.json index 1823c4b66e..77791d470f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,29 +40,29 @@ } }, "@angular-devkit/build-angular": { - "version": "0.901.7", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.901.7.tgz", - "integrity": "sha512-NiBwapx/XJqYGzSmENff78i6Yif9PjYDJ9BB+59t2eDofkCZUcPFrhQmRgliO7rt6RATvT81lDP89+LBXCTQPw==", + "version": "0.901.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.901.12.tgz", + "integrity": "sha512-enK+u1Lg1a/KWUs3r8Tc7Igduu5ph9fgziV8bjQeVrswrqFb0m0eEhxe/zV8rvE92H3NBZp5Z+uzOYIcg4eirw==", "dev": true, "requires": { - "@angular-devkit/architect": "0.901.7", - "@angular-devkit/build-optimizer": "0.901.7", - "@angular-devkit/build-webpack": "0.901.7", - "@angular-devkit/core": "9.1.7", + "@angular-devkit/architect": "0.901.12", + "@angular-devkit/build-optimizer": "0.901.12", + "@angular-devkit/build-webpack": "0.901.12", + "@angular-devkit/core": "9.1.12", "@babel/core": "7.9.0", "@babel/generator": "7.9.3", "@babel/preset-env": "7.9.0", "@babel/template": "7.8.6", "@jsdevtools/coverage-istanbul-loader": "3.0.3", - "@ngtools/webpack": "9.1.7", - "ajv": "6.12.0", + "@ngtools/webpack": "9.1.12", + "ajv": "6.12.3", "autoprefixer": "9.7.4", "babel-loader": "8.0.6", "browserslist": "^4.9.1", "cacache": "15.0.0", "caniuse-lite": "^1.0.30001032", "circular-dependency-plugin": "5.2.0", - "copy-webpack-plugin": "5.1.1", + "copy-webpack-plugin": "6.0.3", "core-js": "3.6.4", "css-loader": "3.5.1", "cssnano": "4.1.10", @@ -71,7 +71,7 @@ "glob": "7.1.6", "jest-worker": "25.1.0", "karma-source-map-support": "1.4.0", - "less": "3.11.1", + "less": "3.11.3", "less-loader": "5.0.0", "license-webpack-plugin": "2.1.4", "loader-utils": "2.0.0", @@ -97,7 +97,7 @@ "stylus": "0.54.7", "stylus-loader": "3.0.2", "terser": "4.6.10", - "terser-webpack-plugin": "2.3.5", + "terser-webpack-plugin": "3.0.3", "tree-kill": "1.2.2", "webpack": "4.42.0", "webpack-dev-middleware": "3.7.2", @@ -108,12 +108,197 @@ "worker-plugin": "4.0.3" }, "dependencies": { + "@angular-devkit/architect": { + "version": "0.901.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.901.12.tgz", + "integrity": "sha512-gLlsxa+3JPV1m1gRvRMujOs4xKox6I5BkYmOD1zfu+dB6y3LuBAvHfXA6FaTDVOMBrmSlWnE4PmOmB6xd7wxMA==", + "dev": true, + "requires": { + "@angular-devkit/core": "9.1.12", + "rxjs": "6.5.4" + } + }, + "@angular-devkit/core": { + "version": "9.1.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.1.12.tgz", + "integrity": "sha512-D/GnBeSlmdgGn7EhuE32HuPuRAjvUuxi7Q6WywBI8PSsXKAGnrypghBwMATNnOA24//CgbW2533Y9VWHaeXdeA==", + "dev": true, + "requires": { + "ajv": "6.12.3", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.5.4", + "source-map": "0.7.3" + } + }, + "ajv": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "copy-webpack-plugin": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-6.0.3.tgz", + "integrity": "sha512-q5m6Vz4elsuyVEIUXr7wJdIdePWTubsqVbEMvf1WQnHGv0Q+9yPRu7MtYFPt+GBOXRav9lvIINifTQ1vSCs+eA==", + "dev": true, + "requires": { + "cacache": "^15.0.4", + "fast-glob": "^3.2.4", + "find-cache-dir": "^3.3.1", + "glob-parent": "^5.1.1", + "globby": "^11.0.1", + "loader-utils": "^2.0.0", + "normalize-path": "^3.0.0", + "p-limit": "^3.0.1", + "schema-utils": "^2.7.0", + "serialize-javascript": "^4.0.0", + "webpack-sources": "^1.4.3" + }, + "dependencies": { + "cacache": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", + "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==", + "dev": true, + "requires": { + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.0", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + } + } + } + }, "core-js": { "version": "3.6.4", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==", "dev": true }, + "fast-glob": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", + "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "less": { + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/less/-/less-3.11.3.tgz", + "integrity": "sha512-VkZiTDdtNEzXA3LgjQiC3D7/ejleBPFVvq+aRI9mIj+Zhmif5TvFPM244bT4rzkvOCvJ9q4zAztok1M7Nygagw==", + "dev": true, + "requires": { + "clone": "^2.1.2", + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "promise": "^7.1.1", + "request": "^2.83.0", + "source-map": "~0.6.0", + "tslib": "^1.10.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, "parse5": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", @@ -144,6 +329,15 @@ "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", "dev": true }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, "source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", @@ -174,9 +368,9 @@ } }, "@angular-devkit/build-optimizer": { - "version": "0.901.7", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.901.7.tgz", - "integrity": "sha512-Xuce3StdxhcgLYb0BAaFGr3Bzj5EM2OsAqIT15PkikWY1k5cK50vPxoC/BkX4QDL9eXSHtqAfMBfA6h5N422vw==", + "version": "0.901.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.901.12.tgz", + "integrity": "sha512-XuXA+6y9QkIAwSVZhWmne4r7qugUUWaXobgRefbn9heiRlY8/7XkZmmvbSrxc1fgQfQar52W9fAa19fAIeNvnw==", "dev": true, "requires": { "loader-utils": "2.0.0", @@ -201,16 +395,51 @@ } }, "@angular-devkit/build-webpack": { - "version": "0.901.7", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.901.7.tgz", - "integrity": "sha512-pTLW5Eqy9cHgv78LKiH0e30lxqKzUPjh1djvNtFsEemOHsfKQdAfjLjikoaQvqMoBKVaUU7r2vmyyS17cH+1yw==", + "version": "0.901.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.901.12.tgz", + "integrity": "sha512-zrhZV2LhQ4uFl9at9i2jiedIu932HsaFN4OMMsTFlV+6CZxtEUBI85hhnPa5KQtIYQr2OMQSQf/FyhnBGs0riQ==", "dev": true, "requires": { - "@angular-devkit/architect": "0.901.7", - "@angular-devkit/core": "9.1.7", + "@angular-devkit/architect": "0.901.12", + "@angular-devkit/core": "9.1.12", "rxjs": "6.5.4" }, "dependencies": { + "@angular-devkit/architect": { + "version": "0.901.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.901.12.tgz", + "integrity": "sha512-gLlsxa+3JPV1m1gRvRMujOs4xKox6I5BkYmOD1zfu+dB6y3LuBAvHfXA6FaTDVOMBrmSlWnE4PmOmB6xd7wxMA==", + "dev": true, + "requires": { + "@angular-devkit/core": "9.1.12", + "rxjs": "6.5.4" + } + }, + "@angular-devkit/core": { + "version": "9.1.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.1.12.tgz", + "integrity": "sha512-D/GnBeSlmdgGn7EhuE32HuPuRAjvUuxi7Q6WywBI8PSsXKAGnrypghBwMATNnOA24//CgbW2533Y9VWHaeXdeA==", + "dev": true, + "requires": { + "ajv": "6.12.3", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.5.4", + "source-map": "0.7.3" + } + }, + "ajv": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "rxjs": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", @@ -219,6 +448,12 @@ "requires": { "tslib": "^1.9.0" } + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true } } }, @@ -653,9 +888,9 @@ } }, "@babel/compat-data": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.10.1.tgz", - "integrity": "sha512-CHvCj7So7iCkGKPRFUfryXIkU2gSBw7VSZFYLsqVhrS47269VK2Hfi9S/YcublPMW8k1u2bQBlbDruoQEm4fgw==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.11.0.tgz", + "integrity": "sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ==", "dev": true, "requires": { "browserslist": "^4.12.0", @@ -743,69 +978,57 @@ } }, "@babel/helper-annotate-as-pure": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.1.tgz", - "integrity": "sha512-ewp3rvJEwLaHgyWGe4wQssC2vjks3E80WiUe2BpMb0KhreTjMROCbxXcEovTrbeGVdQct5VjQfrv9EgC+xMzCw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", + "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.10.4" }, "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", - "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==", - "dev": true - }, "@babel/types": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.2.tgz", - "integrity": "sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.1", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } } } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.1.tgz", - "integrity": "sha512-cQpVq48EkYxUU0xozpGCLla3wlkdRRqLWu1ksFMXA9CM5KQmyyRpSEsYXbao7JUkOw/tAaYKCaYyZq6HOFYtyw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", + "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/helper-explode-assignable-expression": "^7.10.4", + "@babel/types": "^7.10.4" }, "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", - "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==", - "dev": true - }, "@babel/types": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.2.tgz", - "integrity": "sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.1", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } } } }, "@babel/helper-compilation-targets": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.2.tgz", - "integrity": "sha512-hYgOhF4To2UTB4LTaZepN/4Pl9LD4gfbJx8A34mqoluT8TLbof1mhUlYuNWTEebONa8+UlCC4X0TEXu7AOUyGA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz", + "integrity": "sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.10.1", + "@babel/compat-data": "^7.10.4", "browserslist": "^4.12.0", "invariant": "^2.2.4", "levenary": "^1.1.1", @@ -813,239 +1036,116 @@ } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.1.tgz", - "integrity": "sha512-Rx4rHS0pVuJn5pJOqaqcZR4XSgeF9G/pO/79t+4r7380tXFJdzImFnxMU19f83wjSrmKHq6myrM10pFHTGzkUA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz", + "integrity": "sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-regex": "^7.10.1", + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-regex": "^7.10.4", "regexpu-core": "^4.7.0" } }, "@babel/helper-define-map": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.1.tgz", - "integrity": "sha512-+5odWpX+OnvkD0Zmq7panrMuAGQBu6aPUgvMzuMGo4R+jUOvealEj2hiqI6WhxgKrTpFoFj0+VdsuA8KDxHBDg==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", + "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.1", - "@babel/types": "^7.10.1", - "lodash": "^4.17.13" + "@babel/helper-function-name": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" }, "dependencies": { "@babel/code-frame": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", - "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", "dev": true, "requires": { - "@babel/highlight": "^7.10.1" + "@babel/highlight": "^7.10.4" } }, "@babel/helper-function-name": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz", - "integrity": "sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-get-function-arity": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz", - "integrity": "sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.10.4" } }, - "@babel/helper-validator-identifier": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", - "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==", - "dev": true - }, "@babel/highlight": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz", - "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.1", + "@babel/helper-validator-identifier": "^7.10.4", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.2.tgz", - "integrity": "sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", "dev": true }, "@babel/template": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.1.tgz", - "integrity": "sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.1", - "@babel/parser": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/types": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.2.tgz", - "integrity": "sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.1", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } } } }, "@babel/helper-explode-assignable-expression": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.1.tgz", - "integrity": "sha512-vcUJ3cDjLjvkKzt6rHrl767FeE7pMEYfPanq5L16GRtrXIoznc0HykNW2aEYkcnP76P0isoqJ34dDMFZwzEpJg==", + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.11.4.tgz", + "integrity": "sha512-ux9hm3zR4WV1Y3xXxXkdG/0gxF9nvI0YVmKVhvK9AfMoaQkemL3sJpXw+Xbz65azo8qJiEz2XVDUpK3KYhH3ZQ==", "dev": true, "requires": { - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/types": "^7.10.4" }, "dependencies": { - "@babel/code-frame": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", - "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==", + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", "dev": true, "requires": { - "@babel/highlight": "^7.10.1" + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" } - }, - "@babel/generator": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.2.tgz", - "integrity": "sha512-AxfBNHNu99DTMvlUPlt1h2+Hn7knPpH5ayJ8OqDWSeLld+Fi2AYBTC/IejWDM9Edcii4UzZRCsbUt0WlSDsDsA==", - "dev": true, - "requires": { - "@babel/types": "^7.10.2", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz", - "integrity": "sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/types": "^7.10.1" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz", - "integrity": "sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==", - "dev": true, - "requires": { - "@babel/types": "^7.10.1" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz", - "integrity": "sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==", - "dev": true, - "requires": { - "@babel/types": "^7.10.1" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", - "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz", - "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.1", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.2.tgz", - "integrity": "sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ==", - "dev": true - }, - "@babel/template": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.1.tgz", - "integrity": "sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.1", - "@babel/parser": "^7.10.1", - "@babel/types": "^7.10.1" - } - }, - "@babel/traverse": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.1.tgz", - "integrity": "sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.1", - "@babel/generator": "^7.10.1", - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1", - "@babel/parser": "^7.10.1", - "@babel/types": "^7.10.1", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.2.tgz", - "integrity": "sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.1", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true } } }, @@ -1070,28 +1170,22 @@ } }, "@babel/helper-hoist-variables": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.1.tgz", - "integrity": "sha512-vLm5srkU8rI6X3+aQ1rQJyfjvCBLXP8cAGeuw04zeAM2ItKb1e7pmVmLyHb4sDaAYnLL13RHOZPLEtcGZ5xvjg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", + "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.10.4" }, "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", - "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==", - "dev": true - }, "@babel/types": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.2.tgz", - "integrity": "sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.1", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } } @@ -1262,159 +1356,79 @@ } }, "@babel/helper-plugin-utils": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz", - "integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", "dev": true }, "@babel/helper-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.1.tgz", - "integrity": "sha512-7isHr19RsIJWWLLFn21ubFt223PjQyg1HY7CZEMRr820HttHPpVvrsIN3bUOo44DEfFV4kBXO7Abbn9KTUZV7g==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz", + "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==", "dev": true, "requires": { - "lodash": "^4.17.13" + "lodash": "^4.17.19" } }, "@babel/helper-remap-async-to-generator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.1.tgz", - "integrity": "sha512-RfX1P8HqsfgmJ6CwaXGKMAqbYdlleqglvVtht0HGPMSsy2V6MqLlOJVF/0Qyb/m2ZCi2z3q3+s6Pv7R/dQuZ6A==", + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.11.4.tgz", + "integrity": "sha512-tR5vJ/vBa9wFy3m5LLv2faapJLnDFxNWff2SAYkSE4rLUdbp7CdObYFgI7wK4T/Mj4UzpjPwzR8Pzmr5m7MHGA==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-wrap-function": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-wrap-function": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" }, "dependencies": { "@babel/code-frame": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", - "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.1" - } - }, - "@babel/generator": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.2.tgz", - "integrity": "sha512-AxfBNHNu99DTMvlUPlt1h2+Hn7knPpH5ayJ8OqDWSeLld+Fi2AYBTC/IejWDM9Edcii4UzZRCsbUt0WlSDsDsA==", - "dev": true, - "requires": { - "@babel/types": "^7.10.2", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz", - "integrity": "sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/types": "^7.10.1" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz", - "integrity": "sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==", - "dev": true, - "requires": { - "@babel/types": "^7.10.1" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz", - "integrity": "sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/highlight": "^7.10.4" } }, - "@babel/helper-validator-identifier": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", - "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==", - "dev": true - }, "@babel/highlight": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz", - "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.1", + "@babel/helper-validator-identifier": "^7.10.4", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.2.tgz", - "integrity": "sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", "dev": true }, "@babel/template": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.1.tgz", - "integrity": "sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.1", - "@babel/parser": "^7.10.1", - "@babel/types": "^7.10.1" - } - }, - "@babel/traverse": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.1.tgz", - "integrity": "sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.1", - "@babel/generator": "^7.10.1", - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1", - "@babel/parser": "^7.10.1", - "@babel/types": "^7.10.1", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/types": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.2.tgz", - "integrity": "sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.1", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true } } }, @@ -1625,7 +1639,29 @@ } } }, - "@babel/helper-split-export-declaration": { + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz", + "integrity": "sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-split-export-declaration": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", @@ -1641,126 +1677,119 @@ "dev": true }, "@babel/helper-wrap-function": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.1.tgz", - "integrity": "sha512-C0MzRGteVDn+H32/ZgbAv5r56f2o1fZSA/rj/TYo8JEJNHg+9BdSmKBUND0shxWRztWhjlT2cvHYuynpPsVJwQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz", + "integrity": "sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/traverse": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/helper-function-name": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" }, "dependencies": { "@babel/code-frame": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", - "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", "dev": true, "requires": { - "@babel/highlight": "^7.10.1" + "@babel/highlight": "^7.10.4" } }, "@babel/generator": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.2.tgz", - "integrity": "sha512-AxfBNHNu99DTMvlUPlt1h2+Hn7knPpH5ayJ8OqDWSeLld+Fi2AYBTC/IejWDM9Edcii4UzZRCsbUt0WlSDsDsA==", + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", "dev": true, "requires": { - "@babel/types": "^7.10.2", + "@babel/types": "^7.11.5", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" } }, "@babel/helper-function-name": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz", - "integrity": "sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-get-function-arity": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz", - "integrity": "sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.10.4" } }, "@babel/helper-split-export-declaration": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz", - "integrity": "sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.11.0" } }, - "@babel/helper-validator-identifier": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", - "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==", - "dev": true - }, "@babel/highlight": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz", - "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.1", + "@babel/helper-validator-identifier": "^7.10.4", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.2.tgz", - "integrity": "sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", "dev": true }, "@babel/template": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.1.tgz", - "integrity": "sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.1", - "@babel/parser": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/traverse": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.1.tgz", - "integrity": "sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.1", - "@babel/generator": "^7.10.1", - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1", - "@babel/parser": "^7.10.1", - "@babel/types": "^7.10.1", + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", "debug": "^4.1.0", "globals": "^11.1.0", - "lodash": "^4.17.13" + "lodash": "^4.17.19" } }, "@babel/types": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.2.tgz", - "integrity": "sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.1", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } }, @@ -1947,95 +1976,96 @@ "dev": true }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.1.tgz", - "integrity": "sha512-vzZE12ZTdB336POZjmpblWfNNRpMSua45EYnRigE2XsZxcXcIyly2ixnTJasJE4Zq3U7t2d8rRF7XRUuzHxbOw==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz", + "integrity": "sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-remap-async-to-generator": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4", "@babel/plugin-syntax-async-generators": "^7.8.0" } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.1.tgz", - "integrity": "sha512-Cpc2yUVHTEGPlmiQzXj026kqwjEQAD9I4ZC16uzdbgWgitg/UHKHLffKNCQZ5+y8jpIZPJcKcwsr2HwPh+w3XA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz", + "integrity": "sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-dynamic-import": "^7.8.0" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.1.tgz", - "integrity": "sha512-m8r5BmV+ZLpWPtMY2mOKN7wre6HIO4gfIiV+eOmsnZABNenrt/kzYBwrh+KOfgumSWpnlGs5F70J8afYMSJMBg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz", + "integrity": "sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.0" } }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.1.tgz", - "integrity": "sha512-56cI/uHYgL2C8HVuHOuvVowihhX0sxb3nnfVRzUeVHTWmRHTZrKuAh/OBIMggGU/S1g/1D2CRCXqP+3u7vX7iA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz", + "integrity": "sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz", - "integrity": "sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz", + "integrity": "sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.1.tgz", - "integrity": "sha512-Z+Qri55KiQkHh7Fc4BW6o+QBuTagbOp9txE+4U1i79u9oWlf2npkiDx+Rf3iK3lbcHBuNy9UOkwuR5wOMH3LIQ==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz", + "integrity": "sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.10.1" + "@babel/plugin-transform-parameters": "^7.10.4" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.1.tgz", - "integrity": "sha512-VqExgeE62YBqI3ogkGoOJp1R6u12DFZjqwJhqtKc2o5m1YTUuUWnos7bZQFBhwkxIFpWYJ7uB75U7VAPPiKETA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz", + "integrity": "sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.1.tgz", - "integrity": "sha512-dqQj475q8+/avvok72CF3AOSV/SGEcH29zT5hhohqqvvZ2+boQoOr7iGldBG5YXTO2qgCgc2B3WvVLUdbeMlGA==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz", + "integrity": "sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0", "@babel/plugin-syntax-optional-chaining": "^7.8.0" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.1.tgz", - "integrity": "sha512-JjfngYRvwmPwmnbRZyNiPFI8zxCZb8euzbCG/LxyKdeTb59tVciKo9GK9bi6JYKInk1H11Dq9j/zRqIH4KigfQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz", + "integrity": "sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-async-generators": { @@ -2075,12 +2105,12 @@ } }, "@babel/plugin-syntax-numeric-separator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz", - "integrity": "sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-object-rest-spread": { @@ -2111,234 +2141,1242 @@ } }, "@babel/plugin-syntax-top-level-await": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.1.tgz", - "integrity": "sha512-hgA5RYkmZm8FTFT3yu2N9Bx7yVVOKYT6yEdXXo6j2JTm0wNxgqaGeQVaSHRjhfnQbX91DtjFB6McRFSlcJH3xQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz", + "integrity": "sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.1.tgz", - "integrity": "sha512-6AZHgFJKP3DJX0eCNJj01RpytUa3SOGawIxweHkNX2L6PYikOZmoh5B0d7hIHaIgveMjX990IAa/xK7jRTN8OA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz", + "integrity": "sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.1.tgz", - "integrity": "sha512-XCgYjJ8TY2slj6SReBUyamJn3k2JLUIiiR5b6t1mNCMSvv7yx+jJpaewakikp0uWFQSF7ChPPoe3dHmXLpISkg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz", + "integrity": "sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-remap-async-to-generator": "^7.10.1" + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4" + }, + "dependencies": { + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.1.tgz", - "integrity": "sha512-B7K15Xp8lv0sOJrdVAoukKlxP9N59HS48V1J3U/JGj+Ad+MHq+am6xJVs85AgXrQn4LV8vaYFOB+pr/yIuzW8Q==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz", + "integrity": "sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.1.tgz", - "integrity": "sha512-8bpWG6TtF5akdhIm/uWTyjHqENpy13Fx8chg7pFH875aNLwX8JxIxqm08gmAT+Whe6AOmaTeLPe7dpLbXt+xUw==", + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz", + "integrity": "sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "lodash": "^4.17.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-classes": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.1.tgz", - "integrity": "sha512-P9V0YIh+ln/B3RStPoXpEQ/CoAxQIhRSUn7aXqQ+FZJ2u8+oCtjIXR3+X0vsSD8zv+mb56K7wZW1XiDTDGiDRQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz", + "integrity": "sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-define-map": "^7.10.1", - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-optimise-call-expression": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-replace-supers": "^7.10.1", - "@babel/helper-split-export-declaration": "^7.10.1", + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-define-map": "^7.10.4", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", "globals": "^11.1.0" }, "dependencies": { "@babel/code-frame": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", - "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", "dev": true, "requires": { - "@babel/highlight": "^7.10.1" + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" } }, "@babel/helper-function-name": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz", - "integrity": "sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-get-function-arity": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz", - "integrity": "sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.10.4" } }, - "@babel/helper-split-export-declaration": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz", - "integrity": "sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==", + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.11.0" } }, - "@babel/helper-validator-identifier": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", - "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==", - "dev": true + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } }, "@babel/highlight": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz", - "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.1", + "@babel/helper-validator-identifier": "^7.10.4", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.2.tgz", - "integrity": "sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", "dev": true }, "@babel/template": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.1.tgz", - "integrity": "sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.1", - "@babel/parser": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" } }, "@babel/types": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.2.tgz", - "integrity": "sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.1", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } - } - } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } }, "@babel/plugin-transform-computed-properties": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.1.tgz", - "integrity": "sha512-mqSrGjp3IefMsXIenBfGcPXxJxweQe2hEIwMQvjtiDQ9b1IBvDUjkAtV/HMXX47/vXf14qDNedXsIiNd1FmkaQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz", + "integrity": "sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-destructuring": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.1.tgz", - "integrity": "sha512-V/nUc4yGWG71OhaTH705pU8ZSdM6c1KmmLP8ys59oOYbT7RpMYAR3MsVOt6OHL0WzG7BlTU076va9fjJyYzJMA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz", + "integrity": "sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.1.tgz", - "integrity": "sha512-19VIMsD1dp02RvduFUmfzj8uknaO3uiHHF0s3E1OHnVsNj8oge8EQ5RzHRbJjGSetRnkEuBYO7TG1M5kKjGLOA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz", + "integrity": "sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.1.tgz", - "integrity": "sha512-wIEpkX4QvX8Mo9W6XF3EdGttrIPZWozHfEaDTU0WJD/TDnXMvdDh30mzUl/9qWhnf7naicYartcEfUghTCSNpA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz", + "integrity": "sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.1.tgz", - "integrity": "sha512-lr/przdAbpEA2BUzRvjXdEDLrArGRRPwbaF9rvayuHRvdQ7lUTTkZnhZrJ4LE2jvgMRFF4f0YuPQ20vhiPYxtA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz", + "integrity": "sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-for-of": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.1.tgz", - "integrity": "sha512-US8KCuxfQcn0LwSCMWMma8M2R5mAjJGsmoCBVwlMygvmDUMkTCykc84IqN1M7t+agSfOmLYTInLCHJM+RUoz+w==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz", + "integrity": "sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-function-name": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.1.tgz", - "integrity": "sha512-//bsKsKFBJfGd65qSNNh1exBy5Y9gD9ZN+DvrJ8f7HXr4avE5POW6zB7Rj6VnqHV33+0vXWUwJT0wSHubiAQkw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz", + "integrity": "sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" }, "dependencies": { "@babel/code-frame": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", - "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==" + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/plugin-transform-literals": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz", + "integrity": "sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz", + "integrity": "sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz", + "integrity": "sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz", + "integrity": "sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz", + "integrity": "sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.10.4", + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz", + "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", + "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz", + "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz", + "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } }, "@babel/helper-function-name": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz", - "integrity": "sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.10.1", - "@babel/template": "^7.10.1", - "@babel/types": "^7.10.1" + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-get-function-arity": { @@ -2350,25 +3388,60 @@ "@babel/types": "^7.10.4" } }, - "@babel/helper-validator-identifier": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", - "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==" + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } }, "@babel/highlight": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", - "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.9.0", + "@babel/helper-validator-identifier": "^7.10.4", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.4.tgz", - "integrity": "sha512-8jHII4hf+YVDsskTF6WuMB3X4Eh+PsUkC2ljq22so5rHvH+T8BzyL94VOdyFLNR8tBSVXOTbNHOKpR4TfRxVtA==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", "dev": true }, "@babel/template": { @@ -2380,286 +3453,168 @@ "@babel/code-frame": "^7.10.4", "@babel/parser": "^7.10.4", "@babel/types": "^7.10.4" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - } + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" } }, "@babel/types": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.4.tgz", - "integrity": "sha512-UTCFOxC3FsFHb7lkRMVvgLzaRVamXuAs2Tz4wajva4WxtVY82eZeaUBtC2Zt95FU9TiznuC0Zk35tsim8jeVpg==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", - "dev": true - } } }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, "requires": { "ms": "^2.1.1" } }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, - "@babel/plugin-transform-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.1.tgz", - "integrity": "sha512-qi0+5qgevz1NHLZroObRm5A+8JJtibb7vdcPQF1KQE12+Y/xxl8coJ+TpPW9iRq+Mhw/NKLjm+5SHtAHCC7lAw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.1.tgz", - "integrity": "sha512-UmaWhDokOFT2GcgU6MkHC11i0NQcL63iqeufXWfRy6pUOGYeCGEKhvfFO6Vz70UfYJYHwveg62GS83Rvpxn+NA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.1.tgz", - "integrity": "sha512-31+hnWSFRI4/ACFr1qkboBbrTxoBIzj7qA69qlq8HY8p7+YCzkCT6/TvQ1a4B0z27VeWtAeJd6pr5G04dc1iHw==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.1.tgz", - "integrity": "sha512-AQG4fc3KOah0vdITwt7Gi6hD9BtQP/8bhem7OjbaMoRNCH5Djx42O2vYMfau7QnAzQCa+RJnhJBmFFMGpQEzrg==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-simple-access": "^7.10.1", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.1.tgz", - "integrity": "sha512-ewNKcj1TQZDL3YnO85qh9zo1YF1CHgmSTlRQgHqe63oTrMI85cthKtZjAiZSsSNjPQ5NCaYo5QkbYqEw1ZBgZA==", - "dev": true, - "requires": { - "@babel/helper-hoist-variables": "^7.10.1", - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.1.tgz", - "integrity": "sha512-EIuiRNMd6GB6ulcYlETnYYfgv4AxqrswghmBRQbWLHZxN4s7mupxzglnHqk9ZiUpDI4eRWewedJJNj67PWOXKA==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz", - "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.1.tgz", - "integrity": "sha512-MBlzPc1nJvbmO9rPr1fQwXOM2iGut+JC92ku6PbiJMMK7SnQc1rytgpopveE3Evn47gzvGYeCdgfCDbZo0ecUw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.1.tgz", - "integrity": "sha512-WnnStUDN5GL+wGQrJylrnnVlFhFmeArINIR9gjhSeYyvroGhBrSAXYg/RHsnfzmsa+onJrTJrEClPzgNmmQ4Gw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-replace-supers": "^7.10.1" - } - }, "@babel/plugin-transform-parameters": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.1.tgz", - "integrity": "sha512-tJ1T0n6g4dXMsL45YsSzzSDZCxiHXAQp/qHrucOq5gEHncTA3xDxnd5+sZcoQp+N1ZbieAaB8r/VUCG0gqseOg==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz", + "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" }, "dependencies": { "@babel/helper-get-function-arity": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz", - "integrity": "sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", "dev": true, "requires": { - "@babel/types": "^7.10.1" + "@babel/types": "^7.10.4" } }, - "@babel/helper-validator-identifier": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", - "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==", - "dev": true - }, "@babel/types": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.2.tgz", - "integrity": "sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.1", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } } } }, "@babel/plugin-transform-property-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.1.tgz", - "integrity": "sha512-Kr6+mgag8auNrgEpbfIWzdXYOvqDHZOF0+Bx2xh4H2EDNwcbRb9lY6nkZg8oSjsX+DH9Ebxm9hOqtKW+gRDeNA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz", + "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-regenerator": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.1.tgz", - "integrity": "sha512-B3+Y2prScgJ2Bh/2l9LJxKbb8C8kRfsG4AdPT+n7ixBHIxJaIG8bi8tgjxUMege1+WqSJ+7gu1YeoMVO3gPWzw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz", + "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==", "dev": true, "requires": { "regenerator-transform": "^0.14.2" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.1.tgz", - "integrity": "sha512-qN1OMoE2nuqSPmpTqEM7OvJ1FkMEV+BjVeZZm9V9mq/x1JLKQ4pcv8riZJMNN3u2AUGl0ouOMjRr2siecvHqUQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", + "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.1.tgz", - "integrity": "sha512-AR0E/lZMfLstScFwztApGeyTHJ5u3JUKMjneqRItWeEqDdHWZwAOKycvQNCasCK/3r5YXsuNG25funcJDu7Y2g==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz", + "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-spread": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.1.tgz", - "integrity": "sha512-8wTPym6edIrClW8FI2IoaePB91ETOtg36dOkj3bYcNe7aDMN2FXEoUa+WrmPc4xa1u2PQK46fUX2aCb+zo9rfw==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz", + "integrity": "sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.1.tgz", - "integrity": "sha512-j17ojftKjrL7ufX8ajKvwRilwqTok4q+BjkknmQw9VNHnItTyMP5anPFzxFJdCQs7clLcWpCV3ma+6qZWLnGMA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", + "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1", - "@babel/helper-regex": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-regex": "^7.10.4" } }, "@babel/plugin-transform-template-literals": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.1.tgz", - "integrity": "sha512-t7B/3MQf5M1T9hPCRG28DNGZUuxAuDqLYS03rJrIk2prj/UV7Z6FOneijhQhnv/Xa039vidXeVbvjK2SK5f7Gg==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz", + "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.1.tgz", - "integrity": "sha512-qX8KZcmbvA23zDi+lk9s6hC1FM7jgLHYIjuLgULgc8QtYnmB3tAVIYkNoKRQ75qWBeyzcoMoK8ZQmogGtC/w0g==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz", + "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.1.tgz", - "integrity": "sha512-Y/2a2W299k0VIUdbqYm9X2qS6fE0CUBhhiPpimK6byy7OJ/kORLlIX+J6UrjgNu5awvs62k+6RSslxhcvVw2Tw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz", + "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.1", - "@babel/helper-plugin-utils": "^7.10.1" + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/preset-env": { @@ -2731,9 +3686,9 @@ } }, "@babel/preset-modules": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz", - "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", + "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -2744,9 +3699,9 @@ } }, "@babel/runtime": { - "version": "7.10.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.2.tgz", - "integrity": "sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg==", + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", "dev": true, "requires": { "regenerator-runtime": "^0.13.4" @@ -3020,17 +3975,42 @@ "integrity": "sha512-/FvgcpjO4IvwNFnRVoHGikAvckr6fxKf4NgYoTQ9giI8xavolLvuQUHxzH20legi5dgZz34ii2m2g1Q7OxEV8w==" }, "@ngtools/webpack": { - "version": "9.1.7", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-9.1.7.tgz", - "integrity": "sha512-A7VB2I42Kn+7jl0tDKzGNLAoZLWSqkKo9Hg1bmKpvAAIz+DSbq3uV+JWgGgTprM3tn0lfkVgmqk4H17HKwAOcg==", + "version": "9.1.12", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-9.1.12.tgz", + "integrity": "sha512-lypMXIq5oxBMsoDu/VOa1yUmmXthhxkCJa8LG0ZohfnbwhmZvz3SAW7omBGuVrb5cVIfLCkaRCSnQ1MNc6ULXw==", "dev": true, "requires": { - "@angular-devkit/core": "9.1.7", + "@angular-devkit/core": "9.1.12", "enhanced-resolve": "4.1.1", "rxjs": "6.5.4", "webpack-sources": "1.4.3" }, "dependencies": { + "@angular-devkit/core": { + "version": "9.1.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.1.12.tgz", + "integrity": "sha512-D/GnBeSlmdgGn7EhuE32HuPuRAjvUuxi7Q6WywBI8PSsXKAGnrypghBwMATNnOA24//CgbW2533Y9VWHaeXdeA==", + "dev": true, + "requires": { + "ajv": "6.12.3", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.5.4", + "source-map": "0.7.3" + } + }, + "ajv": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "rxjs": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", @@ -3039,6 +4019,12 @@ "requires": { "tslib": "^1.9.0" } + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true } } }, @@ -3068,6 +4054,23 @@ "fastq": "^1.6.0" } }, + "@npmcli/move-file": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", + "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } + } + }, "@rollup/plugin-commonjs": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz", @@ -3453,9 +4456,9 @@ "dev": true }, "@types/glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-VgNIkxK+j7Nz5P7jvUZlRvhuPSmsEfS03b0alKcq5V/STUKAa3Plemsn5mrQUO7am6OErJ4rhGEGJbACclrtRA==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", "dev": true, "requires": { "@types/minimatch": "*", @@ -3525,9 +4528,9 @@ "dev": true }, "@types/q": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", - "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", + "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", "dev": true }, "@types/request": { @@ -4011,6 +5014,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -4162,14 +5166,15 @@ } }, "asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", "dev": true, "requires": { "bn.js": "^4.0.0", "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" }, "dependencies": { "bn.js": { @@ -4196,16 +5201,6 @@ "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", "dev": true }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==" - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" - }, "util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", @@ -4330,39 +5325,13 @@ "dependencies": { "find-cache-dir": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "dependencies": { - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } - } - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" } }, "json5": { @@ -4385,40 +5354,6 @@ "json5": "^1.0.1" } }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", @@ -4578,9 +5513,9 @@ "dev": true }, "bn.js": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.2.tgz", - "integrity": "sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", + "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==", "dev": true }, "body-parser": { @@ -4822,41 +5757,22 @@ } }, "browserify-sign": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.0.tgz", - "integrity": "sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", "dev": true, "requires": { "bn.js": "^5.1.1", "browserify-rsa": "^4.0.1", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", - "elliptic": "^6.5.2", + "elliptic": "^6.5.3", "inherits": "^2.0.4", "parse-asn1": "^5.1.5", "readable-stream": "^3.6.0", "safe-buffer": "^5.2.0" }, "dependencies": { - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" - }, - "normalize-url": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", - "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -5065,38 +5981,6 @@ "unique-filename": "^1.1.1" }, "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "dependencies": { - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - } - } - }, "minipass": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", @@ -5280,6 +6164,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -5290,6 +6175,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -5458,6 +6344,57 @@ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", "dev": true }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -5575,6 +6512,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "requires": { "color-name": "1.1.3" } @@ -5582,7 +6520,8 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true }, "color-string": { "version": "1.5.3", @@ -5974,28 +6913,16 @@ "is-directory": "^0.3.1", "js-yaml": "^3.13.1", "parse-json": "^4.0.0" - }, - "dependencies": { - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - } } }, "create-ecdh": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", - "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", "dev": true, "requires": { "bn.js": "^4.1.0", - "elliptic": "^6.0.0" + "elliptic": "^6.5.3" }, "dependencies": { "bn.js": { @@ -6212,9 +7139,9 @@ } }, "css-what": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz", - "integrity": "sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.3.0.tgz", + "integrity": "sha512-pv9JPyatiPaQ6pf4OvD/dbfm0o5LviWmwxNWzblYf/1u9QZd0ihV+PMwy5jdQWQ3349kZmKEx9WXuSka2dM4cg==", "dev": true }, "cssauron": { @@ -7026,9 +7953,9 @@ "dev": true }, "elliptic": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", - "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", "dev": true, "requires": { "bn.js": "^4.4.0", @@ -7202,9 +8129,9 @@ "dev": true }, "entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.2.tgz", - "integrity": "sha512-dmD3AvJQBUjKpcNkoqr+x+IF0SdRtPz9Vk0uTy4yWqga9ibB6s4v++QFWNohjiUGoMlF552ZvNyXDxz5iW0qmw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", + "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", "dev": true }, "err-code": { @@ -7387,7 +8314,8 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true }, "escodegen": { "version": "1.8.1", @@ -7694,9 +8622,9 @@ "dev": true }, "events": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", - "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", + "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", "dev": true }, "eventsource": { @@ -8057,42 +8985,6 @@ "requires": { "loader-utils": "^2.0.0", "schema-utils": "^2.6.5" - }, - "dependencies": { - "ajv": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" - } - } } }, "fileset": { @@ -8845,7 +9737,8 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true }, "has-glob": { "version": "1.0.0", @@ -9138,6 +10031,111 @@ "is-glob": "^4.0.0", "lodash": "^4.17.11", "micromatch": "^3.1.10" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } } }, "http-signature": { @@ -10401,9 +11399,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -10414,7 +11412,8 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "js-yaml": { "version": "3.13.1", @@ -11355,9 +12354,9 @@ } }, "loglevel": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz", - "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.0.tgz", + "integrity": "sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ==", "dev": true }, "loose-envify": { @@ -11673,108 +12672,13 @@ "dev": true }, "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } + "braces": "^3.0.1", + "picomatch": "^2.0.5" } }, "miller-rabin": { @@ -11969,9 +12873,9 @@ } }, "minipass-pipeline": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.2.tgz", - "integrity": "sha512-3JS5A2DKhD2g0Gg8x3yamO0pj7YeKGwVlDS90pF++kxptwx/F+B//roxf9SqYil5tQo65bijy+dAuAFZmYOouA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "dev": true, "requires": { "minipass": "^3.0.0" @@ -11989,9 +12893,9 @@ } }, "minizlib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", - "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, "requires": { "minipass": "^3.0.0", @@ -13216,22 +14120,22 @@ }, "dependencies": { "es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", "object-inspect": "^1.7.0", "object-keys": "^1.1.1", "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" } }, "es-to-primitive": { @@ -13258,41 +14162,19 @@ "dev": true }, "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, "requires": { "has-symbols": "^1.0.1" } }, "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", "dev": true - }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - } } } }, @@ -13436,22 +14318,22 @@ }, "dependencies": { "es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", "object-inspect": "^1.7.0", "object-keys": "^1.1.1", "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" } }, "es-to-primitive": { @@ -13478,41 +14360,19 @@ "dev": true }, "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, "requires": { "has-symbols": "^1.0.1" } }, "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", "dev": true - }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - } } } }, @@ -14006,19 +14866,28 @@ } }, "parse-asn1": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", - "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", "dev": true, "requires": { - "asn1.js": "^4.0.0", + "asn1.js": "^5.2.0", "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.0", "pbkdf2": "^3.0.3", "safe-buffer": "^5.1.1" } }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, "parse-passwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", @@ -14247,14 +15116,14 @@ "dev": true }, "portfinder": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz", - "integrity": "sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", "dev": true, "requires": { "async": "^2.6.2", "debug": "^3.1.1", - "mkdirp": "^0.5.1" + "mkdirp": "^0.5.5" }, "dependencies": { "debug": { @@ -14266,6 +15135,15 @@ "ms": "^2.1.1" } }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -14300,9 +15178,9 @@ } }, "postcss-calc": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.2.tgz", - "integrity": "sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.4.tgz", + "integrity": "sha512-0I79VRAd1UTkaHzY9w83P39YGO/M3bG7/tNLrHGEunBolfoGM0hSjrGvjoeaj0JE/zIw5GsI2KZ0UwDJqv5hjw==", "dev": true, "requires": { "postcss": "^7.0.27", @@ -14602,15 +15480,34 @@ } }, "postcss-modules-local-by-default": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz", - "integrity": "sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", + "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", "dev": true, "requires": { "icss-utils": "^4.1.1", - "postcss": "^7.0.16", + "postcss": "^7.0.32", "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.0" + "postcss-value-parser": "^4.1.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", + "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "postcss-modules-scope": { @@ -14731,44 +15628,6 @@ "postcss-value-parser": "^3.0.0" }, "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, "postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", @@ -14968,13 +15827,7 @@ "prettier": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", - "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", - "dev": true - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", "dev": true }, "process": { @@ -15344,9 +16197,9 @@ "dev": true }, "querystringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", - "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, "randombytes": { @@ -15562,13 +16415,12 @@ "dev": true }, "regenerator-transform": { - "version": "0.14.4", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.4.tgz", - "integrity": "sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==", + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", "dev": true, "requires": { - "@babel/runtime": "^7.8.4", - "private": "^0.1.8" + "@babel/runtime": "^7.8.4" } }, "regex-not": { @@ -15592,22 +16444,22 @@ }, "dependencies": { "es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", "object-inspect": "^1.7.0", "object-keys": "^1.1.1", "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" } }, "es-to-primitive": { @@ -15634,41 +16486,19 @@ "dev": true }, "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, "requires": { "has-symbols": "^1.0.1" } }, "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", "dev": true - }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - } } } }, @@ -16431,20 +17261,20 @@ "dev": true }, "schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", "dev": true, "requires": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" }, "dependencies": { "ajv": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", + "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -16452,6 +17282,12 @@ "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true } } }, @@ -17883,21 +18719,6 @@ "indexes-of": "^1.0.1", "uniq": "^1.0.1" } - }, - "sax": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", - "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=" - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" } } }, @@ -18115,15 +18936,15 @@ "dev": true }, "tar": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.2.tgz", - "integrity": "sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", + "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", "dev": true, "requires": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^3.0.0", - "minizlib": "^2.1.0", + "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" }, @@ -18265,48 +19086,79 @@ } }, "terser-webpack-plugin": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.5.tgz", - "integrity": "sha512-WlWksUoq+E4+JlJ+h+U+QUzXpcsMSSNXkDy9lBVkSqDn1w23Gg29L/ary9GeJVYCGiNJJX7LnVc4bwL1N3/g1w==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-3.0.3.tgz", + "integrity": "sha512-bZFnotuIKq5Rqzrs+qIwFzGdKdffV9epG5vDSEbYzvKAhPeR5RbbrQysfPgbIIMhNAQtZD2hGwBfSKUXjXZZZw==", "dev": true, "requires": { - "cacache": "^13.0.1", - "find-cache-dir": "^3.2.0", - "jest-worker": "^25.1.0", - "p-limit": "^2.2.2", - "schema-utils": "^2.6.4", - "serialize-javascript": "^2.1.2", + "cacache": "^15.0.4", + "find-cache-dir": "^3.3.1", + "jest-worker": "^26.0.0", + "p-limit": "^2.3.0", + "schema-utils": "^2.6.6", + "serialize-javascript": "^3.1.0", "source-map": "^0.6.1", - "terser": "^4.4.3", + "terser": "^4.6.13", "webpack-sources": "^1.4.3" }, "dependencies": { "cacache": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", - "integrity": "sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w==", + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", + "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==", "dev": true, "requires": { - "chownr": "^1.1.2", - "figgy-pudding": "^3.5.1", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "glob": "^7.1.4", - "graceful-fs": "^4.2.2", "infer-owner": "^1.0.4", - "lru-cache": "^5.1.1", - "minipass": "^3.0.0", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.2", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "p-map": "^3.0.0", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", "promise-inflight": "^1.0.1", - "rimraf": "^2.7.1", - "ssri": "^7.0.0", + "rimraf": "^3.0.2", + "ssri": "^8.0.0", + "tar": "^6.0.2", "unique-filename": "^1.1.1" } }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-worker": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.3.0.tgz", + "integrity": "sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, "minipass": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", @@ -18316,6 +19168,12 @@ "yallist": "^4.0.0" } }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -18325,26 +19183,63 @@ "p-try": "^2.0.0" } }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "serialize-javascript": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", + "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "ssri": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", - "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "figgy-pudding": "^3.5.1", - "minipass": "^3.1.1" + "has-flag": "^4.0.0" + } + }, + "terser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" } } } @@ -19081,22 +19976,22 @@ }, "dependencies": { "es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", "object-inspect": "^1.7.0", "object-keys": "^1.1.1", "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" } }, "es-to-primitive": { @@ -19123,41 +20018,19 @@ "dev": true }, "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, "requires": { "has-symbols": "^1.0.1" } }, "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", "dev": true - }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - } } } }, @@ -19228,49 +20101,21 @@ "dev": true }, "watchpack": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz", - "integrity": "sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz", + "integrity": "sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==", "dev": true, "requires": { - "chokidar": "^3.4.0", + "chokidar": "^3.4.1", "graceful-fs": "^4.1.2", "neo-async": "^2.5.0", "watchpack-chokidar2": "^2.0.0" }, "dependencies": { - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "optional": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "binary-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", - "dev": true, - "optional": true - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "optional": true, - "requires": { - "fill-range": "^7.0.1" - } - }, "chokidar": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", - "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", "dev": true, "optional": true, "requires": { @@ -19284,23 +20129,6 @@ "readdirp": "~3.4.0" } }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "optional": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true - }, "glob-parent": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", @@ -19310,63 +20138,73 @@ "requires": { "is-glob": "^4.0.1" } - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "optional": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "optional": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "optional": true - }, - "readdirp": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", - "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + } + } + }, + "watchpack-chokidar2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz", + "integrity": "sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==", + "dev": true, + "optional": true, + "requires": { + "chokidar": "^2.1.8" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "optional": true, "requires": { - "picomatch": "^2.2.1" + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "optional": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } } }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "optional": true, "requires": { - "is-number": "^7.0.0" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } - } - } - }, - "watchpack-chokidar2": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz", - "integrity": "sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==", - "dev": true, - "optional": true, - "requires": { - "chokidar": "^2.1.8" - }, - "dependencies": { + }, "chokidar": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", @@ -19386,86 +20224,6 @@ "path-is-absolute": "^1.0.0", "readdirp": "^2.2.1", "upath": "^1.1.1" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "optional": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "optional": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "optional": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - } - }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, - "optional": true - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "optional": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - }, - "upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "optional": true - } - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "optional": true, - "requires": { - "is-extendable": "^0.1.0" } }, "fill-range": { @@ -19479,8 +20237,27 @@ "is-number": "^3.0.0", "repeat-string": "^1.6.1", "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true + }, "is-binary-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", @@ -19499,24 +20276,53 @@ "optional": true, "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "optional": true, "requires": { - "is-buffer": "^1.1.5" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", "dev": true, - "optional": true + "optional": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } }, "to-regex-range": { "version": "2.1.1", @@ -19612,6 +20418,35 @@ "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", "dev": true }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, "cacache": { "version": "12.0.4", "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", @@ -19635,6 +20470,29 @@ "y18n": "^4.0.0" } }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, "find-cache-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", @@ -19646,18 +20504,24 @@ "pkg-dir": "^3.0.0" } }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "is-wsl": { @@ -19696,6 +20560,27 @@ "readable-stream": "^2.0.1" } }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", @@ -19707,6 +20592,15 @@ "ajv-keywords": "^3.1.0" } }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -19723,31 +20617,30 @@ } }, "terser-webpack-plugin": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz", - "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", "dev": true, "requires": { "cacache": "^12.0.2", "find-cache-dir": "^2.1.0", "is-wsl": "^1.1.0", "schema-utils": "^1.0.0", - "serialize-javascript": "^2.1.2", + "serialize-javascript": "^4.0.0", "source-map": "^0.6.1", "terser": "^4.1.2", "webpack-sources": "^1.4.0", "worker-farm": "^1.7.0" - }, - "dependencies": { - "worker-farm": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", - "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", - "dev": true, - "requires": { - "errno": "~0.1.7" - } - } + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" } } } @@ -19824,12 +20717,6 @@ "yargs": "^13.3.2" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", @@ -19867,6 +20754,17 @@ "snapdragon-node": "^2.0.1", "split-string": "^3.0.2", "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, "chokidar": { @@ -19889,28 +20787,6 @@ "upath": "^1.1.1" } }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -19920,21 +20796,6 @@ "ms": "^2.1.1" } }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -19945,15 +20806,17 @@ "is-number": "^3.0.0", "repeat-string": "^1.6.1", "to-regex-range": "^2.1.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, "fsevents": { @@ -19978,12 +20841,6 @@ "binary-extensions": "^1.0.0" } }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -19991,25 +20848,38 @@ "dev": true, "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, "ms": { @@ -20018,30 +20888,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, "readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", @@ -20070,28 +20916,6 @@ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "dependencies": { - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, "to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", @@ -20101,40 +20925,6 @@ "is-number": "^3.0.0", "repeat-string": "^1.6.1" } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, @@ -20213,6 +21003,12 @@ "isexe": "^2.0.0" } }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, "widest-line": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", @@ -20268,6 +21064,15 @@ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "dev": true }, + "worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, "worker-plugin": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/worker-plugin/-/worker-plugin-4.0.3.tgz", @@ -20455,6 +21260,117 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, "yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", diff --git a/package.json b/package.json index 79fdb92362..15e022fa11 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "devDependencies": { "@angular-builders/custom-webpack": "^9.1.0", "@angular-devkit/architect": "^0.901.7", - "@angular-devkit/build-angular": "~0.901.5", + "@angular-devkit/build-angular": "^0.901.12", "@angular-devkit/build-ng-packagr": "~0.901.5", "@angular-devkit/core": "^9.1.7", "@angular-devkit/schematics": "^9.1.5", diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.ts index 890a24d958..ae51cd2620 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.ts @@ -7,7 +7,7 @@ import { ChartsService } from '../shared/services/charts.service'; selector: 'app-chart-item', templateUrl: './chart-item.component.html', styleUrls: ['./chart-item.component.scss'], - /* tslint:disable-next-line:use-input-property-decorator */ + /* tslint:disable-next-line:no-inputs-metadata-property */ inputs: ['chart', 'showVersion', 'showDescription'] }) export class ChartItemComponent implements OnInit { diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-filters/list-filters.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-filters/list-filters.component.ts index 06d9d7658a..cb4e7a538a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-filters/list-filters.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-filters/list-filters.component.ts @@ -4,10 +4,10 @@ import { Component } from '@angular/core'; selector: 'app-list-filters', templateUrl: './list-filters.component.html', styleUrls: ['./list-filters.component.scss'], - /* tslint:disable-next-line:use-input-property-decorator */ + /* tslint:disable-next-line:no-inputs-metadata-property */ inputs: ['filters'] }) export class ListFiltersComponent { - public filters: { title: string, items: Array<{}> }[] = []; + public filters: { title: string, items: Array<{}>, }[] = []; } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.ts index 8052238ffb..20cd266321 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.ts @@ -4,7 +4,7 @@ import { Component, Input } from '@angular/core'; selector: 'app-list-item', templateUrl: './list-item.component.html', styleUrls: ['./list-item.component.scss'], - /* tslint:disable-next-line:use-input-property-decorator */ + /* tslint:disable-next-line:no-inputs-metadata-property */ inputs: ['detailUrl'] }) export class ListItemComponent { diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/loader/loader.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/loader/loader.component.ts index 21a30ec988..9702102959 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/loader/loader.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/loader/loader.component.ts @@ -4,7 +4,7 @@ import { Component } from '@angular/core'; selector: 'app-loader', templateUrl: './loader.component.html', styleUrls: ['./loader.component.scss'], - /* tslint:disable-next-line:use-input-property-decorator */ + /* tslint:disable-next-line:no-inputs-metadata-property */ inputs: ['loading'] }) export class LoaderComponent { diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/panel/panel.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/panel/panel.component.ts index debf1260bf..957bcd01c1 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/panel/panel.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/panel/panel.component.ts @@ -4,7 +4,7 @@ import { Component } from '@angular/core'; selector: 'app-panel', templateUrl: './panel.component.html', styleUrls: ['./panel.component.scss'], - /* tslint:disable-next-line:use-input-property-decorator */ + /* tslint:disable-next-line:no-inputs-metadata-property */ inputs: ['title', 'background', 'container', 'border'] }) export class PanelComponent { diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts index b938cd119b..65a3d1b9b8 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts @@ -159,35 +159,7 @@ export class HelmEffects { }) ); - private makeRequest( - action: EntityRequestAction, - url: string, - mapResult: (response: any) => NormalizedResponse, - endpointIds: string[] - ): Observable<Action> { - this.store.dispatch(new StartRequestAction(action)); - const requestArgs = { - headers: null, - params: null - }; - return this.httpClient.get(url, requestArgs).pipe( - mergeMap((response: any) => [new WrapperRequestActionSuccess(mapResult(response), action)]), - catchError(error => { - const { status, message } = HelmEffects.createHelmError(error); - return [ - new WrapperRequestActionFailed(message, action, 'fetch', { - endpointIds, - url: error.url || url, - eventCode: status, - message, - error - }) - ]; - }) - ); - } - - static createHelmErrorMessage(err: any): string { + private static createHelmErrorMessage(err: any): string { if (err) { if (err.error && err.error.message) { // Kube error @@ -203,7 +175,7 @@ export class HelmEffects { return 'Helm API request error'; } - static createHelmError(err: any): { status: string, message: string } { + public static createHelmError(err: any): { status: string, message: string; } { let unwrapped = err; if (err.error) { unwrapped = err.error; @@ -222,6 +194,34 @@ export class HelmEffects { }; } + private makeRequest( + action: EntityRequestAction, + url: string, + mapResult: (response: any) => NormalizedResponse, + endpointIds: string[] + ): Observable<Action> { + this.store.dispatch(new StartRequestAction(action)); + const requestArgs = { + headers: null, + params: null + }; + return this.httpClient.get(url, requestArgs).pipe( + mergeMap((response: any) => [new WrapperRequestActionSuccess(mapResult(response), action)]), + catchError(error => { + const { status, message } = HelmEffects.createHelmError(error); + return [ + new WrapperRequestActionFailed(message, action, 'fetch', { + endpointIds, + url: error.url || url, + eventCode: status, + message, + error + }) + ]; + }) + ); + } + private checkSyncStatus() { // Dispatch request const url = `/pp/${this.proxyAPIVersion}/chartrepos/status`; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.ts index de5d80f1c7..7a0f02b017 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.ts @@ -16,7 +16,7 @@ import { AppState } from '../../../../../../store/src/app-state'; import { KubeConfigHelper } from '../kube-config.helper'; import { KubeConfigFileCluster } from '../kube-config.types'; import { KubeConfigTableCertComponent } from './kube-config-table-cert/kube-config-table-cert.component'; -import { KubeConfigTableName } from './kube-config-table-name/kube-config-table-name.component'; +import { KubeConfigTableNameComponent } from './kube-config-table-name/kube-config-table-name.component'; import { KubeConfigTableSelectComponent } from './kube-config-table-select/kube-config-table-select.component'; import { KubeConfigTableSubTypeSelectComponent, @@ -50,7 +50,7 @@ export class KubeConfigSelectionComponent { selectAllChecked: false, selectAllFilteredRows: () => { // Should always go to true from indeterminate - this.dataSource.selectAllChecked = this.dataSource.selectAllIndeterminate ? true : !this.dataSource.selectAllChecked + this.dataSource.selectAllChecked = this.dataSource.selectAllIndeterminate ? true : !this.dataSource.selectAllChecked; this.dataSource.selectAllIndeterminate = false; // either all off or all on, cannot be indeterminate this.helper.clusters$.pipe( @@ -65,7 +65,7 @@ export class KubeConfigSelectionComponent { first(), ).subscribe(clusters => { this.checkCanGoNext(clusters); - }) + }); }, editRow: null, editRowName: null, @@ -95,7 +95,7 @@ export class KubeConfigSelectionComponent { }, { columnId: 'name', headerCell: () => 'Name', - cellComponent: KubeConfigTableName, + cellComponent: KubeConfigTableNameComponent, cellFlex: '3', class: 'app-table__cell--table-no-v-padding' }, @@ -135,7 +135,7 @@ export class KubeConfigSelectionComponent { public helper: KubeConfigHelper, private snackbarService: SnackBarService ) { - this.helper.clustersChanged = () => this.clustersChanged() + this.helper.clustersChanged = () => this.clustersChanged(); } // Save data for the next step to know the list of clusters to import @@ -145,15 +145,15 @@ export class KubeConfigSelectionComponent { success: true, data: clusters })) - ) + ); clustersParse(cluster: string) { this.snackbarService.hide(); this.helper.parse(cluster).pipe(first()).subscribe(errorString => { if (errorString) { - this.snackbarService.show(`Failed to load Kube Config: ${errorString}`, 'Close') + this.snackbarService.show(`Failed to load Kube Config: ${errorString}`, 'Close'); } - }) + }); } onEnter = () => { @@ -161,8 +161,8 @@ export class KubeConfigSelectionComponent { return; } // Handle back from review step (ensure newly registered endpoints are taken into account) - this.helper.updateAll().pipe(first()).subscribe(() => { }) - } + this.helper.updateAll().pipe(first()).subscribe(() => { }); + }; // Row changed event - update the next button and selection state clustersChanged() { @@ -187,7 +187,7 @@ export class KubeConfigSelectionComponent { } else { this.dataSource.selectAllIndeterminate = true; } - }) + }); } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.spec.ts index 4e4f7f59c7..bd6516944d 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.spec.ts @@ -5,28 +5,28 @@ import { } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; import { KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; import { KubeConfigFileCluster } from '../../kube-config.types'; -import { KubeConfigTableName } from './kube-config-table-name.component'; +import { KubeConfigTableNameComponent } from './kube-config-table-name.component'; describe('KubeConfigTableName', () => { - let component: KubeConfigTableName; - let fixture: ComponentFixture<KubeConfigTableName>; + let component: KubeConfigTableNameComponent; + let fixture: ComponentFixture<KubeConfigTableNameComponent>; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ ...KubernetesBaseTestModules ], - declarations: [KubeConfigTableName] + declarations: [KubeConfigTableNameComponent] }) .compileComponents(); })); beforeEach(() => { - fixture = TestBed.createComponent(KubeConfigTableName); + fixture = TestBed.createComponent(KubeConfigTableNameComponent); component = fixture.componentInstance; component.dataSource = { - getRowUniqueId: (row) => "" - } as IListDataSource<KubeConfigFileCluster> + getRowUniqueId: (row) => '' + } as IListDataSource<KubeConfigFileCluster>; fixture.detectChanges(); }); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.ts index 2b5420071c..8e3fe8c827 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.ts @@ -8,4 +8,4 @@ import { KubeConfigFileCluster } from '../../kube-config.types'; templateUrl: './kube-config-table-name.component.html', styleUrls: ['./kube-config-table-name.component.scss'] }) -export class KubeConfigTableName extends TableCellCustom<KubeConfigFileCluster> { } +export class KubeConfigTableNameComponent extends TableCellCustom<KubeConfigFileCluster> { } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config.types.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config.types.ts index 41cbb80198..0a34ee0611 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config.types.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config.types.ts @@ -35,7 +35,7 @@ export interface KubeConfigFileCluster { _subType?: string; // additional info is required in order to connect, hints at register only, though is specific due to warning message _additionalUserInfo: boolean; - // unique identifier + // unique identifier _id: string; } @@ -51,7 +51,7 @@ export interface KubeConfigFileUserDetail { 'client-certificate-data'?: string; 'client-key-data'?: string; token?: string; - exec?: any + exec?: any; username?: string; password?: string; } @@ -100,5 +100,5 @@ export interface EndpointConfig { export interface KubeConfigImportAuthConfig { subType: string; authType: string; - values: { [key: string]: string }; + values: { [key: string]: string, }; } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-generator.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-generator.ts index 814f8a3e1f..0e526b8aeb 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-generator.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-generator.ts @@ -71,7 +71,7 @@ const enum KubeEndpointAuthTypes { GKE = 'gke-auth', } -const kubeAuthTypeMap: { [type: string]: EndpointAuthTypeConfig } = { +const kubeAuthTypeMap: { [type: string]: EndpointAuthTypeConfig, } = { [KubeEndpointAuthTypes.CERT_AUTH]: { value: KubeEndpointAuthTypes.CERT_AUTH, name: 'Kubernetes Cert Auth', @@ -132,7 +132,11 @@ export function generateKubernetesEntities(): StratosBaseCatalogEntity[] { iconFont: 'stratos-icons', logoUrl: '/core/assets/custom/kubernetes.svg', urlValidation: undefined, - authTypes: [kubeAuthTypeMap[KubeEndpointAuthTypes.CERT_AUTH], kubeAuthTypeMap[KubeEndpointAuthTypes.CONFIG], BaseEndpointAuth.UsernamePassword], + authTypes: [ + kubeAuthTypeMap[KubeEndpointAuthTypes.CERT_AUTH], + kubeAuthTypeMap[KubeEndpointAuthTypes.CONFIG], + BaseEndpointAuth.UsernamePassword + ], renderPriority: 4, subTypes: [ { @@ -209,7 +213,8 @@ function generateStatefulSetsEntity(endpointDefinition: StratosEndpointExtension schema: kubernetesEntityFactory(kubernetesStatefulSetsEntityType), endpoint: endpointDefinition }; - kubeEntityCatalog.statefulSet = new StratosCatalogEntity<IFavoriteMetadata, KubernetesStatefulSet, KubeStatefulSetsActionBuilders>(definition, { + kubeEntityCatalog.statefulSet = new StratosCatalogEntity<IFavoriteMetadata, KubernetesStatefulSet, KubeStatefulSetsActionBuilders>( + definition, { actionBuilders: kubeStatefulSetsActionBuilders }); return kubeEntityCatalog.statefulSet; @@ -233,7 +238,8 @@ function generateDeploymentsEntity(endpointDefinition: StratosEndpointExtensionD schema: kubernetesEntityFactory(kubernetesDeploymentsEntityType), endpoint: endpointDefinition }; - kubeEntityCatalog.deployment = new StratosCatalogEntity<IFavoriteMetadata, KubernetesDeployment, KubeDeploymentActionBuilders>(definition, { + kubeEntityCatalog.deployment = new StratosCatalogEntity<IFavoriteMetadata, KubernetesDeployment, KubeDeploymentActionBuilders>( + definition, { actionBuilders: kubeDeploymentActionBuilders }); return kubeEntityCatalog.deployment; @@ -296,7 +302,7 @@ function generateAnalysisReportsEntity(endpointDefinition: StratosEndpointExtens kubeEntityCatalog.analysisReport = new StratosCatalogEntity<undefined, any, AnalysisReportsActionBuilders>(definition, { actionBuilders: analysisReportsActionBuilders }); - return kubeEntityCatalog.analysisReport + return kubeEntityCatalog.analysisReport; } function generateMetricEntity(endpointDefinition: StratosEndpointExtensionDefinition) { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.ts index 4672958d66..ded7309f19 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.ts @@ -20,8 +20,8 @@ interface KubernetesResourceViewerResource { jsonView: KubeAPIResource; age: string; creationTimestamp: string; - labels: { name: string, value: string }[]; - annotations: { name: string, value: string }[]; + labels: { name: string, value: string, }[]; + annotations: { name: string, value: string, }[]; kind: string; apiVersion: string; } @@ -66,6 +66,7 @@ export class KubernetesResourceViewerComponent implements PreviewableComponent { resource.jsonView = newItem; + /* tslint:disable-next-line:no-string-literal */ const fallback = item['_metadata'] || {}; const ts = item.metadata ? item.metadata.creationTimestamp : fallback.creationTimestamp; @@ -92,7 +93,9 @@ export class KubernetesResourceViewerComponent implements PreviewableComponent { }); } + /* tslint:disable-next-line:no-string-literal */ resource.kind = item['kind'] || fallback.kind || props.resourceKind; + /* tslint:disable-next-line:no-string-literal */ resource.apiVersion = item['apiVersion'] || fallback.apiVersion || this.getVersionFromSelfLink(item.metadata['selfLink']); // Apply analysis if there is one - if this is a k8s resource (i.e. not a container) diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.setup.module.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.setup.module.ts index 88d75af289..f064cb04e2 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.setup.module.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.setup.module.ts @@ -26,7 +26,7 @@ import { KubeConfigTableCertComponent, } from './kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component'; import { - KubeConfigTableName, + KubeConfigTableNameComponent, } from './kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component'; import { KubeConfigTableSelectComponent, @@ -64,7 +64,7 @@ import { KubernetesEndpointService } from './services/kubernetes-endpoint.servic KubeConfigTableUserSelectComponent, KubeConfigTableImportStatusComponent, KubeConfigTableSubTypeSelectComponent, - KubeConfigTableName, + KubeConfigTableNameComponent, KubeConfigTableCertComponent ], providers: [ @@ -81,7 +81,7 @@ import { KubernetesEndpointService } from './services/kubernetes-endpoint.servic KubeConfigTableUserSelectComponent, KubeConfigTableImportStatusComponent, KubeConfigTableSubTypeSelectComponent, - KubeConfigTableName, + KubeConfigTableNameComponent, KubeConfigTableCertComponent ] }) diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-source.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-source.ts index 6ccd5e3d98..d2f9330a3f 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-source.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-source.ts @@ -10,7 +10,7 @@ import { AppState } from '../../../../../store/src/app-state'; import { isFetchingPage } from '../../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper'; import { kubeEntityCatalog } from '../kubernetes-entity-catalog'; import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; -import { GetAnalysisReports } from '../store/anaylsis.actions'; +import { GetAnalysisReports } from '../store/analysis.actions'; import { AnalysisReport } from '../store/kube.types'; export class AnalysisReportsDataSource extends ListDataSource<AnalysisReport> { @@ -52,12 +52,12 @@ export class AnalysisReportsDataSource extends ListDataSource<AnalysisReport> { kubeEntityCatalog.analysisReport.store.getPaginationMonitor(this.analysisAction.kubeGuid).pagination$.pipe( first(), map(isFetchingPage) - ).subscribe(isFetchingPage => { - if (!isFetchingPage) { + ).subscribe(isFetchingPageRes => { + if (!isFetchingPageRes) { ngZone.run(() => { store.dispatch(this.analysisAction); }); } - }) + }); } } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.ts index f01ff1652c..7f349cd24a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.ts @@ -1,3 +1,4 @@ +/* tslint:disable:max-line-length */ import { TitleCasePipe } from '@angular/common'; import { Component, Input } from '@angular/core'; import * as moment from 'moment'; @@ -14,6 +15,8 @@ import { CardCell } from '../../../../../../../core/src/shared/components/list/l import { kubeEntityCatalog } from '../../../kubernetes-entity-catalog'; import { Container, ContainerState, ContainerStatus, InitContainer, KubernetesPod } from '../../../store/kube.types'; +/* tslint:enable:max-line-length */ + export interface ContainerForTable { isInit: boolean; container: Container | InitContainer; @@ -45,6 +48,13 @@ export class KubernetesPodContainersComponent extends CardCell<KubernetesPod> { } }; + public readyBoolConfig: TableCellBooleanIndicatorComponentConfig<ContainerForTable> = { + isEnabled: (row: ContainerForTable) => row.containerStatus.ready, + type: BooleanIndicatorType.yesNo, + subtle: false, + showText: false + }; + @Input() set row(row: KubernetesPod) { if (!row || !!this.containers$) { @@ -92,13 +102,6 @@ export class KubernetesPodContainersComponent extends CardCell<KubernetesPod> { return containerStatusWithContainers.sort((a, b) => a.container.name.localeCompare(b.container.name)); } - public readyBoolConfig: TableCellBooleanIndicatorComponentConfig<ContainerForTable> = { - isEnabled: (row: ContainerForTable) => row.containerStatus.ready, - type: BooleanIndicatorType.yesNo, - subtle: false, - showText: false - }; - private containerStatusToString(state: string, status: ContainerState): string { const exitCode = status.exitCode ? `:${status.exitCode}` : ''; const signal = status.signal ? `:${status.signal}` : ''; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes.analysis.service.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes.analysis.service.ts index 14749dc1c8..a5589d99b6 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes.analysis.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes.analysis.service.ts @@ -9,7 +9,7 @@ import { ResetPaginationOfType } from '../../../../../store/src/actions/paginati import { AppState } from '../../../../../store/src/app-state'; import { ListActionState, RequestInfoState } from '../../../../../store/src/reducers/api-request-reducer/types'; import { kubeEntityCatalog } from '../kubernetes-entity-catalog'; -import { GetAnalysisReports } from '../store/anaylsis.actions'; +import { GetAnalysisReports } from '../store/analysis.actions'; import { AnalysisReport } from '../store/kube.types'; import { getHelmReleaseDetailsFromGuid } from '../workloads/store/workloads-entity-factory'; import { KubernetesEndpointService } from './kubernetes-endpoint.service'; @@ -50,7 +50,7 @@ export class KubernetesAnalysisService { this.hideAnalysis$ = this.enabled$.pipe( map(enabled => !enabled), startWith(true), - ) + ); const allEngines = { popeye: @@ -100,10 +100,10 @@ export class KubernetesAnalysisService { }) ); - this.action = kubeEntityCatalog.analysisReport.actions.getMultiple(this.kubeGuid) + this.action = kubeEntityCatalog.analysisReport.actions.getMultiple(this.kubeGuid); } - public delete(endpointID: string, item: { id: string }) { + public delete(endpointID: string, item: { id: string, }) { return kubeEntityCatalog.analysisReport.api.delete(endpointID, item.id); } @@ -117,7 +117,7 @@ export class KubernetesAnalysisService { filter(([oldE, newE]) => oldE.creating && !newE.creating), map(([, newE]) => newE), first() - ) + ); obs$.subscribe(() => { const type = id.charAt(0).toUpperCase() + id.substring(1); let msg; @@ -136,7 +136,7 @@ export class KubernetesAnalysisService { public getByID(endpoint: string, id: string, refresh = false): Observable<AnalysisReport> { if (refresh) { - kubeEntityCatalog.analysisReport.api.getById<RequestInfoState>(endpoint, id) + kubeEntityCatalog.analysisReport.api.getById<RequestInfoState>(endpoint, id); } const entityService = kubeEntityCatalog.analysisReport.store.getById.getEntityService(endpoint, id); @@ -154,7 +154,7 @@ export class KubernetesAnalysisService { public getByPath(endpointID: string, path: string, refresh = false): Observable<AnalysisReport[]> { if (refresh) { - kubeEntityCatalog.analysisReport.api.getByPath<ListActionState>(endpointID, path) + kubeEntityCatalog.analysisReport.api.getByPath<ListActionState>(endpointID, path); } return kubeEntityCatalog.analysisReport.store.getByPath.getPaginationService(endpointID, path).entities$.pipe( filter(entities => !!entities) diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/route.helper.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/route.helper.ts index ccbb8f7bc1..847fa6dd84 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/route.helper.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/route.helper.ts @@ -5,7 +5,7 @@ export function getParentURL(route: ActivatedRoute, removeLastParts = 1): string const p = v.url.join('/'); return p.length > 0 ? `${a}/${p}` : a; }; - let res = route.snapshot.pathFromRoot.reduce(reducer, '').split('/'); + const res = route.snapshot.pathFromRoot.reduce(reducer, '').split('/'); res.splice(-removeLastParts, removeLastParts); return res.join('/'); } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/action-builders/kube.action-builders.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/action-builders/kube.action-builders.ts index 362c418497..22d30c7c9b 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/action-builders/kube.action-builders.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/action-builders/kube.action-builders.ts @@ -8,7 +8,7 @@ import { GetAnalysisReports, GetAnalysisReportsByPath, RunAnalysisReport, -} from '../anaylsis.actions'; +} from '../analysis.actions'; import { CreateKubernetesNamespace, GeKubernetesDeployments, @@ -31,35 +31,35 @@ export interface KubeStatefulSetsActionBuilders extends OrchestratedActionBuilde getMultiple: ( kubeGuid: string, paginationKey?: string, - ) => GetKubernetesStatefulSets + ) => GetKubernetesStatefulSets; } export const kubeStatefulSetsActionBuilders: KubeStatefulSetsActionBuilders = { getMultiple: (kubeGuid: string, paginationKey?: string) => new GetKubernetesStatefulSets(kubeGuid) -} +}; export interface KubePodActionBuilders extends OrchestratedActionBuilders { get: ( podName: string, kubeGuid: string, - extraArgs: { namespace: string } + extraArgs: { namespace: string; } ) => GetKubernetesPod, getMultiple: ( kubeGuid: string, paginationKey?: string, - ) => GetKubernetesPods + ) => GetKubernetesPods; getOnNode: ( kubeGuid: string, nodeName: string - ) => GetKubernetesPodsOnNode + ) => GetKubernetesPodsOnNode; getInNamespace: ( kubeGuid: string, namespace: string - ) => GetKubernetesPodsInNamespace + ) => GetKubernetesPodsInNamespace; getInWorkload: ( kubeGuid: string, releaseTitle: string - ) => GetHelmReleasePods + ) => GetHelmReleasePods; } export const kubePodActionBuilders: KubePodActionBuilders = { @@ -68,28 +68,28 @@ export const kubePodActionBuilders: KubePodActionBuilders = { getOnNode: (kubeGuid: string, nodeName: string) => new GetKubernetesPodsOnNode(kubeGuid, nodeName), getInNamespace: (kubeGuid: string, namespace: string) => new GetKubernetesPodsInNamespace(kubeGuid, namespace), getInWorkload: (kubeGuid: string, releaseTitle: string) => new GetHelmReleasePods(kubeGuid, releaseTitle) -} +}; export interface KubeDeploymentActionBuilders extends OrchestratedActionBuilders { getMultiple: ( kubeGuid: string, paginationKey?: string, - ) => GeKubernetesDeployments + ) => GeKubernetesDeployments; } export const kubeDeploymentActionBuilders: KubeDeploymentActionBuilders = { getMultiple: (kubeGuid: string, paginationKey?: string) => new GeKubernetesDeployments(kubeGuid) -} +}; export interface KubeNodeActionBuilders extends OrchestratedActionBuilders { get: ( nodeName: string, kubeGuid: string - ) => GetKubernetesNode + ) => GetKubernetesNode; getMultiple: ( kubeGuid: string, paginationKey?: string, - ) => GetKubernetesNodes + ) => GetKubernetesNodes; healthCheck: ( kubeGuid: string, ) => KubeHealthCheck; @@ -99,7 +99,7 @@ export const kubeNodeActionBuilders: KubeNodeActionBuilders = { get: (nodeName: string, endpointGuid: string) => new GetKubernetesNode(nodeName, endpointGuid), getMultiple: (kubeGuid: string, paginationKey?: string) => new GetKubernetesNodes(kubeGuid), healthCheck: (kubeGuid: string) => new KubeHealthCheck(kubeGuid) -} +}; export interface KubeNamespaceActionBuilders extends OrchestratedActionBuilders { get: ( @@ -113,35 +113,35 @@ export interface KubeNamespaceActionBuilders extends OrchestratedActionBuilders getMultiple: ( kubeGuid: string, paginationKey?: string, - ) => GetKubernetesNamespaces + ) => GetKubernetesNamespaces; } export const kubeNamespaceActionBuilders: KubeNamespaceActionBuilders = { get: (namespace: string, kubeGuid: string) => new GetKubernetesNamespace(namespace, kubeGuid), create: (namespace: string, kubeGuid: string) => new CreateKubernetesNamespace(namespace, kubeGuid), getMultiple: (kubeGuid: string, paginationKey?: string) => new GetKubernetesNamespaces(kubeGuid) -} +}; export interface KubeServiceActionBuilders extends OrchestratedActionBuilders { getMultiple: ( kubeGuid: string, paginationKey?: string - ) => GetKubernetesServices + ) => GetKubernetesServices; getInNamespace: ( namespace: string, kubeGuid: string - ) => GetKubernetesServicesInNamespace + ) => GetKubernetesServicesInNamespace; getInWorkload: ( releaseTitle: string, kubeGuid: string - ) => GetHelmReleaseServices + ) => GetHelmReleaseServices; } export const kubeServiceActionBuilders: KubeServiceActionBuilders = { getMultiple: (kubeGuid: string, paginationKey?: string) => new GetKubernetesServices(kubeGuid), getInNamespace: (namespace: string, kubeGuid: string) => new GetKubernetesServicesInNamespace(kubeGuid, namespace), getInWorkload: (releaseTitle: string, kubeGuid: string) => new GetHelmReleaseServices(kubeGuid, releaseTitle) -} +}; export interface KubeDashboardActionBuilders extends OrchestratedActionBuilders { get: ( @@ -151,7 +151,7 @@ export interface KubeDashboardActionBuilders extends OrchestratedActionBuilders export const kubeDashboardActionBuilders: KubeDashboardActionBuilders = { get: (kubeGuid: string) => new GetKubernetesDashboard(kubeGuid) -} +}; export interface AnalysisReportsActionBuilders extends OrchestratedActionBuilders { getMultiple: ( @@ -183,4 +183,4 @@ export const analysisReportsActionBuilders: AnalysisReportsActionBuilders = { getByPath: (kubeGuid: string, path: string) => new GetAnalysisReportsByPath(kubeGuid, path), delete: (kubeGuid: string, id: string) => new DeleteAnalysisReport(kubeGuid, id), run: (kubeGuid: string, id: string, namespace?: string, app?: string) => new RunAnalysisReport(kubeGuid, id, namespace, app) -} +}; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/anaylsis.actions.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/analysis.actions.ts similarity index 97% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/store/anaylsis.actions.ts rename to src/frontend/packages/suse-extensions/src/custom/kubernetes/store/analysis.actions.ts index e9da8d7a27..5748da68f3 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/anaylsis.actions.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/analysis.actions.ts @@ -31,12 +31,12 @@ abstract class AnalysisSingleEntityAction extends AnalysisAction implements Kube } -/** +/** * Get the analysis reports for the given endpoint ID */ export class GetAnalysisReports extends AnalysisPaginationAction { constructor(public kubeGuid: string) { - super(kubeGuid, GET_ANALYSIS_REPORTS_TYPES, kubeGuid) + super(kubeGuid, GET_ANALYSIS_REPORTS_TYPES, kubeGuid); } initialParams = { 'order-direction': 'asc', diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/analysis.effects.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/analysis.effects.ts index 4f8a7d24c2..24ab4ea499 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/analysis.effects.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/analysis.effects.ts @@ -27,7 +27,7 @@ import { GetAnalysisReportsByPath, RUN_ANALYSIS_REPORT_TYPES, RunAnalysisReport, -} from './anaylsis.actions'; +} from './analysis.actions'; import { AnalysisReport } from './kube.types'; @Injectable() @@ -143,7 +143,7 @@ export class AnalysisEffects { const guid = schema.getId(report); res.entities[entityKey][guid] = report; res.result.push(guid); - }) + }); return [new WrapperRequestActionSuccess(res, action)]; }), catchError(error => [ diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-socket-service.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-socket-service.ts index cb2eb017b2..4147fe3e0e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-socket-service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-socket-service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, OnDestroy } from '@angular/core'; import { Store } from '@ngrx/store'; import { Subject, Subscription } from 'rxjs'; import makeWebSocketObservable, { GetWebSocketResponses } from 'rxjs-websockets'; @@ -24,14 +24,15 @@ enum SocketEventTypes { } interface SocketMessage { - type: SocketEventTypes + type: SocketEventTypes; } @Injectable() -export class HelmReleaseSocketService { +export class HelmReleaseSocketService implements OnDestroy { private sub: Subscription; private sendToSocket = new Subject<any>(); + public isPaused = false; constructor( private helmReleaseHelper: HelmReleaseHelperService, @@ -133,16 +134,16 @@ export class HelmReleaseSocketService { public stop() { if (this.sub) { - this.sub.unsubscribe() + this.sub.unsubscribe(); this.sub = null; } } public enable(enable: boolean) { if (enable) { - this.start() + this.start(); } else { - this.stop() + this.stop(); } } @@ -151,17 +152,15 @@ export class HelmReleaseSocketService { } public pause(pause: boolean) { - if (pause != this.isPaused) { + if (pause !== this.isPaused) { const message: SocketMessage = { type: pause ? SocketEventTypes.PAUSE_TRUE : SocketEventTypes.PAUSE_FALSE - } + }; this.sendToSocket.next(JSON.stringify(message)); this.isPaused = pause; } } - public isPaused = false; - ngOnDestroy() { this.sub.unsubscribe(); this.snackbarService.hide(); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/icon-helper.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/icon-helper.ts index 1bfb949f64..7029707587 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/icon-helper.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/icon-helper.ts @@ -1,12 +1,3 @@ -export function getIcon(kind: string) { - const rkind = kind || 'Pod'; - if (iconMappings[rkind]) { - return iconMappings[rkind]; - } else { - return iconMappings.default; - } -} - const iconMappings = { Namespace: { name: 'namespace', @@ -83,4 +74,13 @@ const iconMappings = { name: 'collocation', font: 'stratos-icons' } -}; \ No newline at end of file +}; + +export function getIcon(kind: string) { + const rkind = kind || 'Pod'; + if (iconMappings[rkind]) { + return iconMappings[rkind]; + } else { + return iconMappings.default; + } +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.ts index beb9436e4c..5986c0df78 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.ts @@ -25,7 +25,7 @@ export class HelmReleaseAnalysisTabComponent { public helmReleaseHelper: HelmReleaseHelperService ) { this.path = `${this.helmReleaseHelper.namespace}/${this.helmReleaseHelper.releaseTitle}`; - } + } public analysisChanged(report) { if (report.id !== this.currentReport) { @@ -34,9 +34,8 @@ export class HelmReleaseAnalysisTabComponent { } } - public onReportCount(count: number) { this.noReportsAvailable = count === 0; - } + } } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts index 13b0507c23..fae00ae737 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts @@ -71,7 +71,7 @@ export class HelmReleaseHelperService { public fetchReleaseResources(): Observable<HelmReleaseResources> { // Get helm release - const action = workloadsEntityCatalog.resource.actions.get(this.releaseTitle, this.endpointGuid) + const action = workloadsEntityCatalog.resource.actions.get(this.releaseTitle, this.endpointGuid); return workloadsEntityCatalog.resource.store.getEntityMonitor( action.guid ).entity$.pipe( @@ -90,7 +90,7 @@ export class HelmReleaseHelperService { } private mapPods(pods: KubernetesPod[]): HelmReleaseChartData { - const podPhases: { [phase: string]: number } = {}; + const podPhases: { [phase: string]: number, } = {}; const containers = { ready: { name: 'Ready', @@ -132,6 +132,7 @@ export class HelmReleaseHelperService { }; } + // tslint:disable-next-line:ban-types private isContainerReady(state: ContainerStateCollection = {}): Boolean { if (state.running) { return true; @@ -139,7 +140,7 @@ export class HelmReleaseHelperService { return false; } else if (!!state.terminated) { // Assume a failed state is not ready (covers completed init states), discard success state - return state.terminated.exitCode === 0 ? null : false + return state.terminated.exitCode === 0 ? null : false; } return false; } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.ts index 99235522c1..c6774f7980 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.ts @@ -32,13 +32,13 @@ const layouts = [ ]; interface CustomHelmReleaseGraphNode extends Omit<HelmReleaseGraphNode, 'data'> { - data: CustomHelmReleaseGraphNodeData + data: CustomHelmReleaseGraphNodeData; } interface CustomHelmReleaseGraphNode { id: string; label: string; - data: CustomHelmReleaseGraphNodeData + data: CustomHelmReleaseGraphNodeData; } interface CustomHelmReleaseGraphNodeData extends HelmReleaseGraphNodeData { @@ -48,7 +48,7 @@ interface CustomHelmReleaseGraphNodeData extends HelmReleaseGraphNodeData { text: string, icon: any, alerts: [], - alertSummary: {} + alertSummary: {}; } @Component({ @@ -115,14 +115,14 @@ export class HelmReleaseResourceGraphComponent implements OnInit, OnDestroy { dash: missing ? 6 : 0, fill: colors.bg, text: colors.fg, - icon: icon, + icon, alerts: null, alertSummary: {} }, }; // Does this node have any alerts? - this.applyAlertToNode(newNode, report) + this.applyAlertToNode(newNode, report); newNodes.push(newNode); }); @@ -200,7 +200,7 @@ export class HelmReleaseResourceGraphComponent implements OnInit, OnDestroy { }, this.componentFactoryResolver ); - }) + }); } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-factory.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-factory.ts index 9605546f9d..c1f3dd65bc 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-factory.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-factory.ts @@ -15,17 +15,19 @@ export const getHelmReleaseDetailsFromGuid = (guid: string) => { endpointId: parts[0], namespace: parts[1], releaseTitle: parts[2] - } -} -export const getHelmReleaseId = (endpointId: string, namespace: string, name: string) => `${endpointId}${separator}${namespace}${separator}${name}`; + }; +}; +export const getHelmReleaseId = (endpointId: string, namespace: string, name: string) => + `${endpointId}${separator}${namespace}${separator}${name}`; export const getHelmReleaseIdByObj = (entity: HelmRelease) => getHelmReleaseId(entity.endpointId, entity.namespace, entity.name); export const getHelmReleaseGraphId = (endpointId: string, releaseTitle: string) => `${endpointId}${separator}${releaseTitle}`; export const getHelmReleaseGraphIdByObj = (entity: HelmReleaseGraph) => getHelmReleaseGraphId(entity.endpointId, entity.releaseTitle); export const getHelmReleaseResourceId = (endpointId: string, releaseTitle: string) => `${endpointId}${separator}${releaseTitle}`; -export const getHelmReleaseResourceIdByObj = (entity: HelmReleaseResources) => getHelmReleaseResourceId(entity.endpointId, entity.releaseTitle); +export const getHelmReleaseResourceIdByObj = (entity: HelmReleaseResources) => + getHelmReleaseResourceId(entity.endpointId, entity.releaseTitle); const entityCache: { - [key: string]: EntitySchema + [key: string]: EntitySchema, } = {}; entityCache[helmReleaseEntityKey] = new KubernetesEntitySchema( @@ -48,6 +50,6 @@ entityCache[helmReleaseResourceEntityType] = new KubernetesEntitySchema( Object.entries(entityCache).forEach(([key, workloadSchema]) => addKubernetesEntitySchema(key, workloadSchema)); -export const createHelmReleaseEntities = (): { [cacheName: string]: EntitySchema } => { +export const createHelmReleaseEntities = (): { [cacheName: string]: EntitySchema; } => { return entityCache; }; diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-welcome/suse-welcome.component.ts b/src/frontend/packages/suse-extensions/src/custom/suse-welcome/suse-welcome.component.ts index dc04e71d03..fb15002a6d 100644 --- a/src/frontend/packages/suse-extensions/src/custom/suse-welcome/suse-welcome.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/suse-welcome/suse-welcome.component.ts @@ -5,4 +5,4 @@ import { Component } from '@angular/core'; templateUrl: './suse-welcome.component.html', styleUrls: ['./suse-welcome.component.scss'], }) -export class SuseWelcomeComponent {} +export class SuseWelcomeComponent { } diff --git a/src/frontend/packages/suse-extensions/tslint.json b/src/frontend/packages/suse-extensions/tslint.json new file mode 100644 index 0000000000..9da788b6cb --- /dev/null +++ b/src/frontend/packages/suse-extensions/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": "../../../../tslint.json" +} diff --git a/tslint.json b/tslint.json index 3d9cb120dd..2b58369bbd 100644 --- a/tslint.json +++ b/tslint.json @@ -31,7 +31,6 @@ "no-non-null-assertion": true, "no-redundant-jsdoc": true, "no-switch-case-fall-through": true, - "no-use-before-declare": true, "no-var-requires": false, "object-literal-key-quotes": [true, "as-needed"], "object-literal-sort-keys": false, From 3d4829dbb1e9507a7b62b19a666281e9e030cf9c Mon Sep 17 00:00:00 2001 From: Richard Cox <richard-cox@users.noreply.github.com> Date: Thu, 10 Sep 2020 10:19:36 +0100 Subject: [PATCH 609/648] Filter endpoints by type (#434) * Add endpoint type filter to endpoint page list - Need to decide - always show filter or only when above certain number of endpoint types? - Need to decide - show base enpdoints or endpoint with subtypes * Changes to suse extensions * Fix issue where endpoint list sort/filter was lost on unregister/disconnect/connect * Changes following review - only filter by type, ignore sub type - only show filter if there are more than two types of endpoint registered * Tidy up * Changes following review, fix empty sort drop down on cf endpoints list --- .../cf-endpoints-list-config.service.ts | 5 +- .../endpoint/base-endpoints-data-source.ts | 57 +++++++++---- .../endpoint/endpoints-data-source.ts | 6 +- .../endpoint/endpoints-list-config.service.ts | 80 ++++++++++++++++++- .../components/list/list.component.html | 3 +- .../components/list/list.component.types.ts | 7 +- .../store/src/actions/pagination.actions.ts | 7 +- ...agination-reducer-clear-pagination-type.ts | 13 --- .../pagination-reducer-create-pagination.ts | 24 +++++- .../pagination-reducer-reset-pagination.ts | 17 ++++ .../pagination-reducer/pagination.reducer.ts | 10 ++- ...onocular-repository-list-config.service.ts | 2 +- .../kubernetes-endpoints-data-source.ts | 2 +- ...ubernetes-endpoints-list-config.service.ts | 4 +- 14 files changed, 185 insertions(+), 52 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-endpoints/cf-endpoints-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-endpoints/cf-endpoints-list-config.service.ts index a72c7221e7..f38a7a0705 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-endpoints/cf-endpoints-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-endpoints/cf-endpoints-list-config.service.ts @@ -36,7 +36,7 @@ export class CFEndpointsListConfigService implements IListConfig<EndpointModel> paginationMonitorFactory: PaginationMonitorFactory, entityMonitorFactory: EntityMonitorFactory, internalEventMonitorFactory: InternalEventMonitorFactory, - endpointsListConfigService: EndpointsListConfigService + endpointsListConfigService: EndpointsListConfigService, ) { this.columns = endpointsListConfigService.columns.filter(column => { return column.columnId !== 'type'; @@ -46,7 +46,8 @@ export class CFEndpointsListConfigService implements IListConfig<EndpointModel> this, paginationMonitorFactory, entityMonitorFactory, - internalEventMonitorFactory); + internalEventMonitorFactory, + ); } public getColumns = () => this.columns; public getGlobalActions = () => []; diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/base-endpoints-data-source.ts b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/base-endpoints-data-source.ts index afacea4130..22f224cbe5 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/base-endpoints-data-source.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/base-endpoints-data-source.ts @@ -11,7 +11,8 @@ import { InternalEventMonitorFactory } from '../../../../../../../store/src/moni import { PaginationMonitorFactory } from '../../../../../../../store/src/monitors/pagination-monitor.factory'; import { endpointEntitiesSelector } from '../../../../../../../store/src/selectors/endpoint.selectors'; import { EndpointModel } from '../../../../../../../store/src/types/endpoint.types'; -import { ListDataSource } from '../../data-sources-controllers/list-data-source'; +import { PaginationEntityState } from '../../../../../../../store/src/types/pagination.types'; +import { DataFunction, DataFunctionDefinition, ListDataSource } from '../../data-sources-controllers/list-data-source'; import { IListDataSourceConfig } from '../../data-sources-controllers/list-data-source-config'; import { RowsState } from '../../data-sources-controllers/list-data-source-types'; import { TableRowStateManager } from '../../list-table/table-row/table-row-state-manager'; @@ -27,11 +28,15 @@ export function syncPaginationSection( store.dispatch(new CreatePagination( action, paginationKey, - action.paginationKey + action.paginationKey, + action.initialParams )); } export class BaseEndpointsDataSource extends ListDataSource<EndpointModel> { + + public static typeFilterKey = 'endpointType'; + store: Store<AppState>; /** * Used to distinguish between data sources providing all endpoints or those that only provide endpoints matching this value. @@ -49,7 +54,8 @@ export class BaseEndpointsDataSource extends ListDataSource<EndpointModel> { paginationMonitorFactory: PaginationMonitorFactory, entityMonitorFactory: EntityMonitorFactory, internalEventMonitorFactory: InternalEventMonitorFactory, - onlyConnected = true + onlyConnected = true, + filterByType = false ) { const rowStateHelper = new ListRowSateHelper(); const { rowStateManager, sub } = rowStateHelper.getRowStateManager( @@ -73,21 +79,28 @@ export class BaseEndpointsDataSource extends ListDataSource<EndpointModel> { () => this.store.dispatch(action) ); + const transformEntities: (DataFunctionDefinition | DataFunction<EndpointModel>)[] = [{ + type: 'filter', + field: 'name' + }]; + if (dsEndpointType || onlyConnected) { + transformEntities.push((entities: EndpointModel[]) => { + return dsEndpointType || onlyConnected ? entities.filter(endpoint => { + return (!onlyConnected || endpoint.connectionStatus === 'connected') && + (!dsEndpointType || endpoint.cnsi_type === dsEndpointType); + }) : entities; + }); + } + if (filterByType) { + transformEntities.push((entities: EndpointModel[], paginationState: PaginationEntityState) => + BaseEndpointsDataSource.endpointTypeFilter(entities, paginationState) + ); + } + super({ ...config, paginationKey: action.paginationKey, - transformEntities: [ - (entities: EndpointModel[]) => { - return dsEndpointType || onlyConnected ? entities.filter(endpoint => { - return (!onlyConnected || endpoint.connectionStatus === 'connected') && - (!dsEndpointType || endpoint.cnsi_type === dsEndpointType); - }) : entities; - }, - { - type: 'filter', - field: 'name' - }, - ], + transformEntities, }); this.dsEndpointType = dsEndpointType; } @@ -154,4 +167,18 @@ export class BaseEndpointsDataSource extends ListDataSource<EndpointModel> { })), ).subscribe(); } + + static endpointTypeFilter: DataFunction<EndpointModel> = (entities: EndpointModel[], paginationState: PaginationEntityState) => { + if ( + !paginationState.clientPagination || + !paginationState.clientPagination.filter || + !paginationState.clientPagination.filter.items[BaseEndpointsDataSource.typeFilterKey] + ) { + return entities; + } + const searchTerm = paginationState.clientPagination.filter.items[BaseEndpointsDataSource.typeFilterKey]; + return searchTerm ? + entities.filter(endpoint => endpoint.cnsi_type === searchTerm) : + entities; + }; } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-data-source.ts b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-data-source.ts index cdb066f099..8ae915ccf3 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-data-source.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-data-source.ts @@ -18,7 +18,8 @@ export class EndpointsDataSource extends BaseEndpointsDataSource { listConfig: IListConfig<EndpointModel>, paginationMonitorFactory: PaginationMonitorFactory, entityMonitorFactory: EntityMonitorFactory, - internalEventMonitorFactory: InternalEventMonitorFactory + internalEventMonitorFactory: InternalEventMonitorFactory, + filterByType = false ) { super( store, @@ -28,7 +29,8 @@ export class EndpointsDataSource extends BaseEndpointsDataSource { paginationMonitorFactory, entityMonitorFactory, internalEventMonitorFactory, - false + false, + filterByType ); } } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts index 0f091dbd85..217c3b43b9 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service.ts @@ -1,18 +1,29 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import { filter } from 'rxjs/operators'; +import { BehaviorSubject, combineLatest, of } from 'rxjs'; +import { debounceTime, filter, map } from 'rxjs/operators'; import { ListView } from '../../../../../../../store/src/actions/list.actions'; +import { SetClientFilter } from '../../../../../../../store/src/actions/pagination.actions'; import { AppState } from '../../../../../../../store/src/app-state'; import { entityCatalog } from '../../../../../../../store/src/entity-catalog/entity-catalog'; import { FavoritesConfigMapper } from '../../../../../../../store/src/favorite-config-mapper'; import { EntityMonitorFactory } from '../../../../../../../store/src/monitors/entity-monitor.factory.service'; import { InternalEventMonitorFactory } from '../../../../../../../store/src/monitors/internal-event-monitor.factory'; import { PaginationMonitorFactory } from '../../../../../../../store/src/monitors/pagination-monitor.factory'; +import { stratosEntityCatalog } from '../../../../../../../store/src/stratos-entity-catalog'; import { EndpointModel } from '../../../../../../../store/src/types/endpoint.types'; +import { PaginationEntityState } from '../../../../../../../store/src/types/pagination.types'; import { createTableColumnFavorite } from '../../list-table/table-cell-favorite/table-cell-favorite.component'; import { ITableColumn } from '../../list-table/table.types'; -import { IListAction, IListConfig, ListViewTypes } from '../../list.component.types'; +import { + IListAction, + IListConfig, + IListMultiFilterConfig, + IListMultiFilterConfigItem, + ListViewTypes, +} from '../../list.component.types'; +import { BaseEndpointsDataSource } from './base-endpoints-data-source'; import { EndpointCardComponent } from './endpoint-card/endpoint-card.component'; import { EndpointListHelper } from './endpoint-list.helpers'; import { EndpointsDataSource } from './endpoints-data-source'; @@ -110,12 +121,14 @@ export class EndpointsListConfigService implements IListConfig<EndpointModel> { (row: EndpointModel) => favoritesConfigMapper.getFavoriteEndpointFromEntity(row) ); this.columns.push(favoriteCell); + this.dataSource = new EndpointsDataSource( this.store, this, paginationMonitorFactory, entityMonitorFactory, - internalEventMonitorFactory + internalEventMonitorFactory, + true ); } @@ -124,9 +137,68 @@ export class EndpointsListConfigService implements IListConfig<EndpointModel> { public getSingleActions = () => this.singleActions; public getColumns = () => this.columns; public getDataSource = () => this.dataSource; - public getMultiFiltersConfigs = () => []; + + public getMultiFiltersConfigs = (): IListMultiFilterConfig[] => [this.createEndpointTypeFilter()]; private getEndpointTypeString(endpoint: EndpointModel): string { return entityCatalog.getEndpoint(endpoint.cnsi_type, endpoint.sub_type).definition.label; } + + private createEndpointTypeFilter(): IListMultiFilterConfig { + return { + key: BaseEndpointsDataSource.typeFilterKey, + label: 'Endpoint Type', + list$: combineLatest([ + stratosEntityCatalog.endpoint.store.getPaginationMonitor().currentPage$, + stratosEntityCatalog.endpoint.store.getPaginationMonitor().pagination$ + ]).pipe( + debounceTime(100),// This can get pretty spammy, to help protect resetEndpointTypeFilter allow a pause + filter(([endpoints, pagination]) => !!endpoints), + map(([endpoints, pagination]) => { + // Provide a list of endpoint types only if there are more than two registered endpoint types + const types: { [type: string]: boolean; } = {}; + for (const endpoint of endpoints) { + types[endpoint.cnsi_type] = true; + } + if (Object.values(types).filter(type => type).length < 2) { + // If we're going to hid the endpoint filter ensure any existing filter value is reset + this.resetEndpointTypeFilter(pagination); + return []; + } + return entityCatalog.getAllBaseEndpointTypes() + .sort((a, b) => a.definition.renderPriority - b.definition.renderPriority) + .filter(et => types[et.type]) + .map(et => { + const res: IListMultiFilterConfigItem = { + label: et.definition.label, + item: et, + value: et.type + }; + return res; + }); + }) + ), + loading$: of(false), + select: new BehaviorSubject(undefined) + }; + } + + private resetEndpointTypeFilter(pagination: PaginationEntityState) { + if ( + pagination.clientPagination && + pagination.clientPagination.filter && + pagination.clientPagination.filter.items[BaseEndpointsDataSource.typeFilterKey] + ) { + const clientPaginationFilter = { + ...pagination.clientPagination.filter, + items: { + ...pagination.clientPagination.filter.items, + [BaseEndpointsDataSource.typeFilterKey]: null + } + }; + this.store.dispatch( + new SetClientFilter(this.dataSource.masterAction, this.dataSource.paginationKey, clientPaginationFilter) + ); + } + } } diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.html b/src/frontend/packages/core/src/shared/components/list/list.component.html index 28ee1e36ef..f8b7839b14 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.html +++ b/src/frontend/packages/core/src/shared/components/list/list.component.html @@ -45,7 +45,8 @@ <div class="list-component__header__left--multi-filters" [hidden]="(!(hasRows$ | async) && !filter) || (isAddingOrSelecting$ | async)"> <ng-container *ngFor="let multiFilterManager of multiFilterManagers; first as isFirst"> - <mat-form-field *ngIf="!isFirst || !(multiFilterManager.hasOneItem$ | async)" [floatLabel]="'never'"> + <mat-form-field *ngIf="!isFirst || !(multiFilterManager.hasOneOrLessItems$ | async)" + [floatLabel]="'never'"> <mat-select id="{{multiFilterManager.filterKey}}" matInput [(value)]="multiFilterManager.value" [disabled]="!(multiFilterManager.filterIsReady$ | async)" (selectionChange)="multiFilterManager.selectItem($event.value)"> diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.types.ts b/src/frontend/packages/core/src/shared/components/list/list.component.types.ts index ae2038162f..de841ba208 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.types.ts +++ b/src/frontend/packages/core/src/shared/components/list/list.component.types.ts @@ -1,4 +1,4 @@ -import { Type } from '@angular/core'; +import { Injectable, Type } from '@angular/core'; import * as moment from 'moment'; import { BehaviorSubject, combineLatest, Observable, of as observableOf } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; @@ -13,7 +13,6 @@ import { ListDataSource } from './data-sources-controllers/list-data-source'; import { IListDataSource } from './data-sources-controllers/list-data-source-types'; import { CardTypes } from './list-cards/card/card.component'; import { ITableColumn, ITableText } from './list-table/table.types'; -import { Injectable } from "@angular/core"; import { CardCell } from './list.types'; export enum ListViewTypes { @@ -204,7 +203,7 @@ export class MultiFilterManager<T> { public filterIsReady$: Observable<boolean>; public filterItems$: Observable<IListMultiFilterConfigItem[]>; public hasItems$: Observable<boolean>; - public hasOneItem$: Observable<boolean>; + public hasOneOrLessItems$: Observable<boolean>; public value: string; public filterKey: string; @@ -217,7 +216,7 @@ export class MultiFilterManager<T> { this.filterKey = this.multiFilterConfig.key; this.allLabel = multiFilterConfig.allLabel || 'All'; this.filterItems$ = this.getItemObservable(multiFilterConfig); - this.hasOneItem$ = this.filterItems$.pipe(map(items => items.length === 1)); + this.hasOneOrLessItems$ = this.filterItems$.pipe(map(items => items.length <= 1)); this.hasItems$ = this.filterItems$.pipe(map(items => !!items.length)); this.filterIsReady$ = this.getReadyObservable(multiFilterConfig, dataSource, this.hasItems$); } diff --git a/src/frontend/packages/store/src/actions/pagination.actions.ts b/src/frontend/packages/store/src/actions/pagination.actions.ts index 0a6f540eec..9c133b7bf7 100644 --- a/src/frontend/packages/store/src/actions/pagination.actions.ts +++ b/src/frontend/packages/store/src/actions/pagination.actions.ts @@ -69,7 +69,12 @@ export class CreatePagination extends BasePaginationAction implements Action { /** * @param seed The pagination key for the section we should use as a seed when creating the new pagination section. */ - constructor(pEntityConfig: Partial<EntityCatalogEntityConfig>, public paginationKey: string, public seed?: string) { + constructor( + pEntityConfig: Partial<EntityCatalogEntityConfig>, + public paginationKey: string, + public seed?: string, + public initialParams?: PaginationParam + ) { super(pEntityConfig); } type = CREATE_PAGINATION; diff --git a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-clear-pagination-type.ts b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-clear-pagination-type.ts index aaf5dafbaf..7191d36695 100644 --- a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-clear-pagination-type.ts +++ b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-clear-pagination-type.ts @@ -1,5 +1,3 @@ -import { EndpointActionComplete } from '../../actions/endpoint.actions'; -import { entityCatalog } from '../../entity-catalog/entity-catalog'; import { PaginationState } from '../../types/pagination.types'; import { getDefaultPaginationEntityState } from './pagination-reducer-reset-pagination'; @@ -22,14 +20,3 @@ export function paginationClearAllTypes(state: PaginationState, entityKeys: stri return prevState; }, state); } - -export function clearEndpointEntities(state: PaginationState, action: EndpointActionComplete) { - const entityKeys = entityCatalog.getAllEntitiesForEndpointType(action.endpointType).map(entity => entity.entityKey); - if (entityKeys.length > 0) { - return paginationClearAllTypes( - state, - entityKeys - ); - } - return state; -} diff --git a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-create-pagination.ts b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-create-pagination.ts index a34f1d65cb..ad60adc0d1 100644 --- a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-create-pagination.ts +++ b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-create-pagination.ts @@ -1,7 +1,12 @@ import { CreatePagination } from '../../actions/pagination.actions'; import { entityCatalog } from '../../entity-catalog/entity-catalog'; import { EntityCatalogEntityConfig } from '../../entity-catalog/entity-catalog.types'; -import { PaginationEntityState, PaginationState } from '../../types/pagination.types'; +import { + PaginationEntityState, + PaginationEntityTypeState, + PaginationParam, + PaginationState, +} from '../../types/pagination.types'; import { spreadClientPagination } from './pagination-reducer.helper'; function getPaginationKey(entityConfig: EntityCatalogEntityConfig) { @@ -42,7 +47,7 @@ function mergeWithSeed(state: PaginationState, action: CreatePagination, default const currentPagination = state[entityKey][action.paginationKey] || defaultState; const seeded = action.seed && state[entityKey] && state[entityKey][action.seed]; const seedPagination = seeded ? state[entityKey][action.seed] : defaultState; - const entityState = { + const entityState: PaginationEntityTypeState = { ...newState[entityKey], [action.paginationKey]: { ...seedPagination, @@ -50,7 +55,10 @@ function mergeWithSeed(state: PaginationState, action: CreatePagination, default pageCount: currentPagination.pageCount, currentPage: currentPagination.currentPage, clientPagination: mergePaginationSections(currentPagination, seedPagination, defaultState), - seed: seeded ? action.seed : null + seed: seeded ? action.seed : null, + // Ensure any filters from seed are not carried into new list + // For example, sort by type on endpoints page, go to cf endpoint page and type is not shown + params: mergeParamsSections(currentPagination, action.initialParams) } }; return { @@ -59,6 +67,16 @@ function mergeWithSeed(state: PaginationState, action: CreatePagination, default }; } +function mergeParamsSections( + currentPagination: PaginationEntityState, + initialSeedParams: PaginationParam = {}, +): PaginationParam { + return { + ...currentPagination.params, + ...initialSeedParams, + }; +} + function mergePaginationSections( currentPagination: PaginationEntityState, seedPagination: PaginationEntityState, diff --git a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-reset-pagination.ts b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-reset-pagination.ts index 18c1566775..fb4bcce3b0 100644 --- a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-reset-pagination.ts +++ b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-reset-pagination.ts @@ -1,3 +1,4 @@ +import { EndpointActionComplete } from '../../actions/endpoint.actions'; import { ResetPagination } from '../../actions/pagination.actions'; import { entityCatalog } from '../../entity-catalog/entity-catalog'; import { PaginationEntityState, PaginationEntityTypeState, PaginationState } from '../../types/pagination.types'; @@ -102,3 +103,19 @@ function paginationResetPaginationState(oldEntityState: PaginationEntityState) { } return entityState; } + +export function resetEndpointEntities(state: PaginationState, action: EndpointActionComplete) { + const entityKeys = entityCatalog.getAllEntitiesForEndpointType(action.endpointType).map(entity => entity.entityKey); + if (entityKeys.length > 0) { + return entityKeys.reduce((prevState, entityKey) => { + if (prevState[entityKey]) { + return { + ...prevState, + [entityKey]: paginationResetAllPaginationSections(prevState, entityKey) + } + } + return prevState; + }, state); + } + return state; +} \ No newline at end of file diff --git a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination.reducer.ts b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination.reducer.ts index d24616c794..eef23bc50d 100644 --- a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination.reducer.ts +++ b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination.reducer.ts @@ -36,11 +36,15 @@ import { UpdatePaginationMaxedState } from './../../actions/pagination.actions'; import { paginationAddParams } from './pagination-reducer-add-params'; import { paginationClearPages } from './pagination-reducer-clear-pages'; import { paginationClearOfEntity } from './pagination-reducer-clear-pagination-of-entity'; -import { clearEndpointEntities, paginationClearAllTypes } from './pagination-reducer-clear-pagination-type'; +import { paginationClearAllTypes } from './pagination-reducer-clear-pagination-type'; import { createNewPaginationSection } from './pagination-reducer-create-pagination'; import { paginationIgnoreMaxed, paginationMaxReached } from './pagination-reducer-max-reached'; import { paginationRemoveParams } from './pagination-reducer-remove-params'; -import { getDefaultPaginationEntityState, paginationResetPagination } from './pagination-reducer-reset-pagination'; +import { + getDefaultPaginationEntityState, + paginationResetPagination, + resetEndpointEntities, +} from './pagination-reducer-reset-pagination'; import { paginationSetClientFilter } from './pagination-reducer-set-client-filter'; import { paginationSetClientFilterKey } from './pagination-reducer-set-client-filter-key'; import { paginationSetClientPage } from './pagination-reducer-set-client-page'; @@ -137,7 +141,7 @@ function paginate(action, state = {}, updatePagination) { } if (isEndpointAction(action)) { - return clearEndpointEntities(state, action); + return resetEndpointEntities(state, action); } if (action.type === UPDATE_MAXED_STATE) { diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-repository-list-config.service.ts b/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-repository-list-config.service.ts index 879403cb41..654ec79973 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-repository-list-config.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-repository-list-config.service.ts @@ -172,7 +172,7 @@ export class MonocularRepositoryListConfig implements IListConfig<EndpointModel> paginationMonitorFactory, entityMonitorFactory, internalEventMonitorFactory, - ngZone + ngZone, ); } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-data-source.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-data-source.ts index b578a4ad05..3eaaf1f26e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-data-source.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-data-source.ts @@ -33,7 +33,7 @@ export class KubernetesEndpointsDataSource extends BaseEndpointsDataSource { 'k8s', paginationMonitorFactory, entityMonitorFactory, - internalEventMonitorFactory + internalEventMonitorFactory, ); } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-list-config.service.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-list-config.service.ts index 9115a09569..2c384fefa9 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-list-config.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-list-config.service.ts @@ -39,7 +39,7 @@ export class KubernetesEndpointsListConfigService implements IListConfig<Endpoin paginationMonitorFactory: PaginationMonitorFactory, entityMonitorFactory: EntityMonitorFactory, internalEventMonitorFactory: InternalEventMonitorFactory, - endpointsListConfigService: EndpointsListConfigService + endpointsListConfigService: EndpointsListConfigService, ) { this.columns = endpointsListConfigService.columns.filter(column => { return column.columnId !== 'type'; @@ -49,7 +49,7 @@ export class KubernetesEndpointsListConfigService implements IListConfig<Endpoin this, paginationMonitorFactory, entityMonitorFactory, - internalEventMonitorFactory + internalEventMonitorFactory, ); } public getColumns = () => this.columns; From c44204a49b2baa731dd675625d97b6b4a8d05777 Mon Sep 17 00:00:00 2001 From: Neil MacDougall <nwmac@users.noreply.github.com> Date: Thu, 10 Sep 2020 10:19:53 +0100 Subject: [PATCH 610/648] Add support for Helm Upgrade and history (#458) * Improve presentation and fix issue with development versions * Add support for Helm Upgrade and Helm history * Remove console logging * Add comment * Minor tweaks following self-review * Remove debug logging * Fix whitespace * Minor tidy ups * Fix compile issue * Fix front-end unit tests * Always show upgrade button * Minor entity store type updates * Address PR feedback * Revert * Remove description when checking for similar charts * Only show button when the helm chart is available Co-authored-by: Richard Cox <richard.cox@suse.com> --- .../packages/core/sass/mat-desktop.scss | 4 + .../components/list/list.component.html | 2 +- .../components/list/list.component.types.ts | 15 +- .../components/stepper/step/step.component.ts | 2 +- .../stepper/steppers/steppers.component.ts | 2 +- .../suse-extensions/sass/_all-theme.scss | 2 + .../src/custom/helm/helm-entity-catalog.ts | 7 +- .../src/custom/helm/helm-entity-factory.ts | 7 + .../src/custom/helm/helm-entity-generator.ts | 22 ++- .../custom/helm/store/helm.action-builders.ts | 12 +- .../src/custom/helm/store/helm.actions.ts | 26 ++++ .../src/custom/helm/store/helm.effects.ts | 28 ++++ .../src/custom/helm/store/helm.types.ts | 44 +++--- .../helm-release-tab-base.component.ts | 1 + .../tabs/helm-release-helper.service.ts | 130 ++++++++++++++++- .../helm-release-history-tab.component.html | 1 + .../helm-release-history-tab.component.scss | 0 ...helm-release-history-tab.component.spec.ts | 31 ++++ .../helm-release-history-tab.component.ts | 92 ++++++++++++ .../helm-release-summary-tab.component.html | 63 ++++---- .../helm-release-summary-tab.component.scss | 7 + ...helm-release-summary-tab.component.spec.ts | 5 +- ...m-release-summary-tab.component.theme.scss | 15 ++ .../helm-release-summary-tab.component.ts | 11 +- .../store/workload-action-builders.ts | 50 +++++-- .../store/workloads-entity-factory.ts | 9 ++ .../store/workloads-entity-generator.ts | 27 +++- .../workloads/store/workloads.actions.ts | 58 +++++++- .../workloads/store/workloads.effects.ts | 87 ++++++++++- .../release-version-data-source.ts | 61 ++++++++ .../release-version-list-config.ts | 131 +++++++++++++++++ .../upgrade-release.component.html | 37 +++++ .../upgrade-release.component.scss | 59 ++++++++ .../upgrade-release.component.spec.ts | 37 +++++ .../upgrade-release.component.ts | 114 +++++++++++++++ .../kubernetes/workloads/workload.types.ts | 17 +++ .../workloads/workloads-entity-catalog.ts | 14 +- .../kubernetes/workloads/workloads.module.ts | 10 +- .../kubernetes/workloads/workloads.routing.ts | 12 +- .../workloads/workloads.testing.module.ts | 47 ++++++ .../plugins/kubernetes/install_release.go | 135 ++++++++++++++---- src/jetstream/plugins/kubernetes/main.go | 2 + 42 files changed, 1318 insertions(+), 118 deletions(-) create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.html create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.spec.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.theme.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-data-source.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-list-config.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.html create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.spec.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.testing.module.ts diff --git a/src/frontend/packages/core/sass/mat-desktop.scss b/src/frontend/packages/core/sass/mat-desktop.scss index cf474347e0..7e11af970e 100644 --- a/src/frontend/packages/core/sass/mat-desktop.scss +++ b/src/frontend/packages/core/sass/mat-desktop.scss @@ -63,6 +63,10 @@ $desktop-toggle-button-item-height: $desktop-menu-item-height - 2px; font-size: $desktop-font-size; } + .mat-slide-toggle-label { + font-size: $desktop-font-size; + } + // Allow a slightly-wider snackbar on desktop .mat-snack-bar-container { max-width: 40vw; diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.html b/src/frontend/packages/core/src/shared/components/list/list.component.html index f8b7839b14..db67aaa1e0 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.html +++ b/src/frontend/packages/core/src/shared/components/list/list.component.html @@ -50,7 +50,7 @@ <mat-select id="{{multiFilterManager.filterKey}}" matInput [(value)]="multiFilterManager.value" [disabled]="!(multiFilterManager.filterIsReady$ | async)" (selectionChange)="multiFilterManager.selectItem($event.value)"> - <mat-option>{{ multiFilterManager.allLabel }}</mat-option> + <mat-option *ngIf="!multiFilterManager.hideAllOption">{{ multiFilterManager.allLabel }}</mat-option> <mat-option *ngFor="let selectItem of multiFilterManager.filterItems$ | async" [value]="selectItem.value"> {{selectItem.label}} diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.types.ts b/src/frontend/packages/core/src/shared/components/list/list.component.types.ts index de841ba208..6416a73dc3 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.types.ts +++ b/src/frontend/packages/core/src/shared/components/list/list.component.types.ts @@ -1,7 +1,7 @@ import { Injectable, Type } from '@angular/core'; import * as moment from 'moment'; import { BehaviorSubject, combineLatest, Observable, of as observableOf } from 'rxjs'; -import { map, startWith } from 'rxjs/operators'; +import { first, map, startWith } from 'rxjs/operators'; import { ListView } from '../../../../../store/src/actions/list.actions'; import { ActionState } from '../../../../../store/src/reducers/api-request-reducer/types'; @@ -130,9 +130,11 @@ export interface IListMultiFilterConfig { key: string; label: string; allLabel?: string; + hideAllOption?: boolean; list$: Observable<IListMultiFilterConfigItem[]>; loading$: Observable<boolean>; select: BehaviorSubject<any>; + autoSelectFirst?: boolean; } export interface IListFilter { @@ -208,6 +210,7 @@ export class MultiFilterManager<T> { public filterKey: string; public allLabel: string; + public hideAllOption = false; constructor( public multiFilterConfig: IListMultiFilterConfig, @@ -215,10 +218,20 @@ export class MultiFilterManager<T> { ) { this.filterKey = this.multiFilterConfig.key; this.allLabel = multiFilterConfig.allLabel || 'All'; + this.hideAllOption = multiFilterConfig.hideAllOption || false; this.filterItems$ = this.getItemObservable(multiFilterConfig); this.hasOneOrLessItems$ = this.filterItems$.pipe(map(items => items.length <= 1)); this.hasItems$ = this.filterItems$.pipe(map(items => !!items.length)); this.filterIsReady$ = this.getReadyObservable(multiFilterConfig, dataSource, this.hasItems$); + + // Also select the first option if configured + if (multiFilterConfig.autoSelectFirst) { + this.filterItems$.pipe(first()).subscribe(options => { + if (options && options.length > 0) { + this.selectItem(options[0].value); + } + }) + } } private getReadyObservable( diff --git a/src/frontend/packages/core/src/shared/components/stepper/step/step.component.ts b/src/frontend/packages/core/src/shared/components/stepper/step/step.component.ts index 73f164301a..9ab65bcd51 100644 --- a/src/frontend/packages/core/src/shared/components/stepper/step/step.component.ts +++ b/src/frontend/packages/core/src/shared/components/stepper/step/step.component.ts @@ -21,7 +21,7 @@ export interface StepOnNextResult { data?: any; } -export type StepOnNextFunction = () => Observable<StepOnNextResult>; +export type StepOnNextFunction = (index: number, step: StepComponent) => Observable<StepOnNextResult>; @Component({ selector: 'app-step', diff --git a/src/frontend/packages/core/src/shared/components/stepper/steppers/steppers.component.ts b/src/frontend/packages/core/src/shared/components/stepper/steppers/steppers.component.ts index 1e0776289c..a79b0fcc7a 100644 --- a/src/frontend/packages/core/src/shared/components/stepper/steppers/steppers.component.ts +++ b/src/frontend/packages/core/src/shared/components/stepper/steppers/steppers.component.ts @@ -117,7 +117,7 @@ export class SteppersComponent implements OnInit, AfterContentInit, OnDestroy { if (this.currentIndex < this.steps.length) { const step = this.steps[this.currentIndex]; step.busy = true; - const obs$ = step.onNext(); + const obs$ = step.onNext(this.currentIndex, step); if (!(obs$ instanceof Observable)) { return; } diff --git a/src/frontend/packages/suse-extensions/sass/_all-theme.scss b/src/frontend/packages/suse-extensions/sass/_all-theme.scss index 5afbb47729..116316ed35 100644 --- a/src/frontend/packages/suse-extensions/sass/_all-theme.scss +++ b/src/frontend/packages/suse-extensions/sass/_all-theme.scss @@ -4,6 +4,7 @@ @import '../src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.theme'; @import '../src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.theme'; @import '../src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.theme'; +@import '../src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.theme'; @import '../src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.theme'; @mixin apply-theme-suse-extensions($stratos-theme) { @@ -15,6 +16,7 @@ @include kube-analysis-report-theme($theme, $app-theme); @include kube-analysis-card-theme($theme, $app-theme); @include monocular-chart-card($theme, $app-theme); + @include helm-release-summary-tab-theme($theme, $app-theme); @include kube-node-link-theme($theme, $app-theme); } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-catalog.ts b/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-catalog.ts index 288b527982..83dda2cd49 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-catalog.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-catalog.ts @@ -1,4 +1,4 @@ -import { StratosCatalogEndpointEntity, StratosCatalogEntity } from '../../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; import { IFavoriteMetadata } from '../../../../store/src/types/user-favorites.types'; import { MonocularChart, HelmVersion } from './store/helm.types'; import { HelmChartActionBuilders, HelmVersionActionBuilders } from './store/helm.action-builders'; +import { StratosCatalogEndpointEntity, StratosCatalogEntity } from '../../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; import { IFavoriteMetadata } from '../../../../store/src/types/user-favorites.types'; import { MonocularChart, HelmVersion, MonocularVersion } from './store/helm.types'; import { HelmChartActionBuilders, HelmVersionActionBuilders, HelmChartVersionsActionBuilders } from './store/helm.action-builders'; /** * A strongly typed collection of Helm Catalog Entities. @@ -6,8 +6,9 @@ import { StratosCatalogEndpointEntity, StratosCatalogEntity } from '../../../../ */ export class HelmEntityCatalog { endpoint: StratosCatalogEndpointEntity; - chart: StratosCatalogEntity<IFavoriteMetadata, MonocularChart, HelmChartActionBuilders> - version: StratosCatalogEntity<IFavoriteMetadata, HelmVersion, HelmVersionActionBuilders> + chart: StratosCatalogEntity<IFavoriteMetadata, MonocularChart, HelmChartActionBuilders>; + version: StratosCatalogEntity<IFavoriteMetadata, HelmVersion, HelmVersionActionBuilders>; + chartVersions: StratosCatalogEntity<IFavoriteMetadata, MonocularVersion[], HelmChartVersionsActionBuilders>; } /** diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-factory.ts b/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-factory.ts index 5bb57a9a65..302dd8a060 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-factory.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-factory.ts @@ -5,6 +5,7 @@ import { HelmVersion, MonocularChart } from './store/helm.types'; export const helmVersionsEntityType = 'helmVersions'; export const monocularChartsEntityType = 'monocularCharts'; +export const monocularChartVersionsEntityType = 'monocularChartVersions'; export const getMonocularChartId = (entity: MonocularChart) => entity.id; export const getHelmVersionId = (entity: HelmVersion) => entity.endpointId; @@ -45,6 +46,12 @@ entityCache[helmVersionsEntityType] = new HelmEntitySchema( { idAttribute: getHelmVersionId } ); +entityCache[monocularChartVersionsEntityType] = new HelmEntitySchema( + monocularChartVersionsEntityType, + {}, + { idAttribute: getHelmVersionId } +); + export function helmEntityFactory(key: string): EntitySchema { const entity = entityCache[key]; if (!entity) { diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-generator.ts b/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-generator.ts index 380b225a40..c9ef3d8afa 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-generator.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-generator.ts @@ -11,14 +11,17 @@ import { helmEntityFactory, helmVersionsEntityType, monocularChartsEntityType, + monocularChartVersionsEntityType, } from './helm-entity-factory'; import { HelmChartActionBuilders, helmChartActionBuilders, + HelmChartVersionsActionBuilders, + helmChartVersionsActionBuilders, HelmVersionActionBuilders, helmVersionActionBuilders, } from './store/helm.action-builders'; -import { HelmVersion, MonocularChart } from './store/helm.types'; +import { HelmVersion, MonocularChart, MonocularVersion } from './store/helm.types'; export function generateHelmEntities(): StratosBaseCatalogEntity[] { @@ -35,10 +38,12 @@ export function generateHelmEntities(): StratosBaseCatalogEntity[] { authTypes: [], renderPriority: 10, }; + return [ generateEndpointEntity(endpointDefinition), generateChartEntity(endpointDefinition), generateVersionEntity(endpointDefinition), + generateChartVersionsEntity(endpointDefinition), ]; } @@ -80,4 +85,19 @@ function generateVersionEntity(endpointDefinition: StratosEndpointExtensionDefin return helmEntityCatalog.version; } +function generateChartVersionsEntity(endpointDefinition: StratosEndpointExtensionDefinition) { + const definition = { + type: monocularChartVersionsEntityType, + schema: helmEntityFactory(monocularChartVersionsEntityType), + endpoint: endpointDefinition + }; + helmEntityCatalog.chartVersions = new StratosCatalogEntity<IFavoriteMetadata, MonocularVersion[], HelmChartVersionsActionBuilders>( + definition, + { + actionBuilders: helmChartVersionsActionBuilders + } + ); + return helmEntityCatalog.chartVersions; +} + diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.action-builders.ts b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.action-builders.ts index 53c20f4de9..df443d3ea6 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.action-builders.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.action-builders.ts @@ -1,5 +1,5 @@ import { OrchestratedActionBuilders } from '../../../../../store/src/entity-catalog/action-orchestrator/action-orchestrator'; -import { GetHelmVersions, GetMonocularCharts, HelmInstall } from './helm.actions'; +import { GetHelmChartVersions, GetHelmVersions, GetMonocularCharts, HelmInstall } from './helm.actions'; import { HelmInstallValues } from './helm.types'; export interface HelmChartActionBuilders extends OrchestratedActionBuilders { @@ -20,4 +20,12 @@ export interface HelmVersionActionBuilders extends OrchestratedActionBuilders { export const helmVersionActionBuilders: HelmVersionActionBuilders = { getMultiple: () => new GetHelmVersions() -} \ No newline at end of file +} + +export interface HelmChartVersionsActionBuilders extends OrchestratedActionBuilders { + getMultiple: (repoName: string, chartName: string) => GetHelmChartVersions +} + +export const helmChartVersionsActionBuilders: HelmChartVersionsActionBuilders = { + getMultiple: (repoName: string, chartName: string) => new GetHelmChartVersions(repoName, chartName) +} diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.actions.ts b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.actions.ts index c47e8e47f3..df552269dd 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.actions.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.actions.ts @@ -5,6 +5,7 @@ import { helmEntityFactory, helmVersionsEntityType, monocularChartsEntityType, + monocularChartVersionsEntityType, } from '../helm-entity-factory'; import { HelmInstallValues } from './helm.types'; @@ -12,6 +13,10 @@ export const GET_MONOCULAR_CHARTS = '[Monocular] Get Charts'; export const GET_MONOCULAR_CHARTS_SUCCESS = '[Monocular] Get Charts Success'; export const GET_MONOCULAR_CHARTS_FAILURE = '[Monocular] Get Charts Failure'; +export const GET_MONOCULAR_CHART_VERSIONS = '[Monocular] Get Chart Versions'; +export const GET_MONOCULAR_CHART_VERSIONS_SUCCESS = '[Monocular] Get Chart Versions Success'; +export const GET_MONOCULAR_CHART_VERSIONS_FAILURE = '[Monocular] Get Chart Versions Failure'; + export const GET_HELM_VERSIONS = '[Helm] Get Versions'; export const GET_HELM_VERSIONS_SUCCESS = '[Helm] Get Versions Success'; export const GET_HELM_VERSIONS_FAILURE = '[Helm] Get Versions Failure'; @@ -73,3 +78,24 @@ export class GetHelmVersions implements MonocularPaginationAction { }; flattenPagination = true; } + +export class GetHelmChartVersions implements MonocularPaginationAction { + constructor(public repoName: string, public chartName: string) { + this.paginationKey = `'monocular-chart-versions-${repoName}-${chartName}`; + } + type = GET_MONOCULAR_CHART_VERSIONS; + endpointType = HELM_ENDPOINT_TYPE; + entityType = monocularChartVersionsEntityType; + entity = [helmEntityFactory(monocularChartVersionsEntityType)]; + actions = [ + GET_MONOCULAR_CHART_VERSIONS, + GET_MONOCULAR_CHART_VERSIONS_SUCCESS, + GET_MONOCULAR_CHART_VERSIONS_FAILURE + ]; + paginationKey: string; + initialParams = { + 'order-direction': 'asc', + 'order-direction-field': 'version', + }; + flattenPagination = true; +} diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts index 65a3d1b9b8..31508d886f 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts @@ -23,7 +23,9 @@ import { helmEntityCatalog } from '../helm-entity-catalog'; import { getHelmVersionId, getMonocularChartId, HELM_ENDPOINT_TYPE } from '../helm-entity-factory'; import { GET_HELM_VERSIONS, + GET_MONOCULAR_CHART_VERSIONS, GET_MONOCULAR_CHARTS, + GetHelmChartVersions, GetHelmVersions, GetMonocularCharts, HELM_INSTALL, @@ -128,6 +130,32 @@ export class HelmEffects { }) ); + @Effect() + fetchChartVersions$ = this.actions$.pipe( + ofType<GetHelmChartVersions>(GET_MONOCULAR_CHART_VERSIONS), + flatMap(action => { + const entityKey = entityCatalog.getEntityKey(action); + return this.makeRequest(action, `/pp/${this.proxyAPIVersion}/chartsvc/v1/charts/${action.repoName}/${action.chartName}/versions`, + (response) => { + const base = { + entities: { [entityKey]: {} }, + result: [] + } as NormalizedResponse; + + const items = response.data as Array<any>; + const processedData = items.reduce((res, data) => { + const id = getMonocularChartId(data); + res.entities[entityKey][id] = data; + // Promote the name to the top-level object for simplicity + data.name = data.attributes.name; + res.result.push(id); + return res; + }, base); + return processedData; + }, []); + }) + ); + @Effect() helmInstall$ = this.actions$.pipe( ofType<HelmInstall>(HELM_INSTALL), diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.types.ts b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.types.ts index 54b21b657c..4ddf46021c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.types.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.types.ts @@ -1,3 +1,6 @@ +import { Chart } from './../monocular/shared/models/chart'; +import { ChartVersion } from './../monocular/shared/models/chart-version'; + export interface MonocularRepository { name: string; url: string; @@ -7,26 +10,18 @@ export interface MonocularRepository { status: string; } -export interface MonocularChart { - id: string; +// Reuse types from the Monocular codebase +export interface MonocularChart extends Chart { name: string; - attributes: { - description: string; - home: string; - icon: string; - keywords: string[]; - repo: { - name: string; - url: string; - }; - }; - relationships: { - latestChartVersion: { - data: { - version: string - } - } - }; +} + +export type MonocularVersion = ChartVersion; + +// Basic Chart Metadata +export interface ChartMetadata { + name: string; + description: string; + sources: string[]; } export interface HelmVersion { @@ -50,8 +45,6 @@ export enum HelmStatus { Pending_Rollback = 8 } - - export interface HelmInstallValues { endpoint: string; releaseName: string; @@ -59,3 +52,12 @@ export interface HelmInstallValues { values: string; chart: string; } + +export interface HelmUpgradeValues { + chart: { + name: string; + repo: string; + version: string; + }; + restartPods?: boolean; +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts index 8bad3751c8..d121deed5d 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts @@ -58,6 +58,7 @@ export class HelmReleaseTabBaseComponent implements OnDestroy { { link: 'summary', label: 'Summary', icon: 'helm', iconFont: 'stratos-icons' }, { link: 'notes', label: 'Notes', icon: 'subject' }, { link: 'values', label: 'Values', icon: 'list' }, + { link: 'history', label: 'History', icon: 'schedule' }, { link: 'analysis', label: 'Analysis', icon: 'assignment', hidden$: this.analysisService.hideAnalysis$ }, { link: '-', label: 'Resources' }, { link: 'graph', label: 'Overview', icon: 'share', hidden$: sessionService.isTechPreview().pipe(map(tp => !tp)) }, diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts index fae00ae737..749d08e560 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts @@ -1,7 +1,9 @@ import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; +import { combineLatest, Observable } from 'rxjs'; import { filter, map } from 'rxjs/operators'; +import { helmEntityCatalog } from '../../../../helm/helm-entity-catalog'; +import { ChartMetadata } from '../../../../helm/store/helm.types'; import { kubeEntityCatalog } from '../../../kubernetes-entity-catalog'; import { ContainerStateCollection, KubernetesPod } from '../../../store/kube.types'; import { getHelmReleaseDetailsFromGuid } from '../../store/workloads-entity-factory'; @@ -14,6 +16,49 @@ import { } from '../../workload.types'; import { workloadsEntityCatalog } from '../../workloads-entity-catalog'; +// Simple class to represent MAJOR.MINOR.REVISION version +class Version { + + public major: number; + public minor: number; + public revision: number; + + public valid: boolean; + + constructor(v: string) { + this.valid = false; + if (typeof v === 'string') { + const parts = v.split('.'); + if (parts.length === 3) { + this.major = parseInt(parts[0], 10); + this.minor = parseInt(parts[1], 10); + this.revision = parseInt(parts[2], 10); + this.valid = true; + } + } + } + + // Is this version newer than the supplied other version? + public isNewer(other: Version): boolean { + if (!this.valid || !other.valid) { + return false; + } + + if (this.major > other.major) { + return true; + } + + if (this.major === other.major) { + if (this.minor > other.minor) { + return true; + } + if (this.minor === other.minor) { + return this.revision > other.revision; + } + } + return false; + } +} @Injectable() export class HelmReleaseHelperService { @@ -71,10 +116,8 @@ export class HelmReleaseHelperService { public fetchReleaseResources(): Observable<HelmReleaseResources> { // Get helm release - const action = workloadsEntityCatalog.resource.actions.get(this.releaseTitle, this.endpointGuid); - return workloadsEntityCatalog.resource.store.getEntityMonitor( - action.guid - ).entity$.pipe( + const guid = workloadsEntityCatalog.resource.actions.get(this.releaseTitle, this.endpointGuid).guid; + return workloadsEntityCatalog.resource.store.getEntityMonitor(guid).entity$.pipe( filter(resources => !!resources) ); } @@ -89,6 +132,24 @@ export class HelmReleaseHelperService { ); } + // Check to see if a workload has updates available + public getCharts() { + return helmEntityCatalog.chart.store.getPaginationService().entities$.pipe( + filter(charts => !!charts) + ); + } + + public fetchReleaseHistory(): Observable<any> { + // Get the history for a Helm release + return workloadsEntityCatalog.history.store.getEntityService( + this.releaseTitle, + this.endpointGuid, + { namespace: this.namespace } + ).waitForEntity$.pipe( + map(historyEntity => historyEntity.entity.revisions) + ); + } + private mapPods(pods: KubernetesPod[]): HelmReleaseChartData { const podPhases: { [phase: string]: number, } = {}; const containers = { @@ -144,4 +205,63 @@ export class HelmReleaseHelperService { } return false; } + + public hasUpgrade(returnLatest = false): Observable<any> { + const updates = combineLatest(this.getCharts(), this.release$); + return updates.pipe( + map(([charts, release]) => { + for (const c of charts) { + if (this.isProbablySameChart(c.attributes, release.chart.metadata)) { + if (c.relationships && c.relationships.latestChartVersion && c.relationships.latestChartVersion.data) { + const latest = new Version(c.relationships.latestChartVersion.data.version); + const current = new Version(release.chart.metadata.version); + if (latest.isNewer(current)) { + return { + release, + upgrade: c.attributes, + version: c.relationships.latestChartVersion.data.version + }; + } + } + } + } + // No newer release, so return the release itself if that is what was requested and we can find the chart + // NOTE: If the helm repository is removed that we installed from, we won't be able to find the chart + if (returnLatest) { + const releaseChart = charts.find(c => c.relationships.latestChartVersion.data.version === release.chart.metadata.version); + if (releaseChart) { + return { + release, + upgrade: releaseChart.attributes, + version: releaseChart.relationships.latestChartVersion.data.version + } + } + } + return null; + }) + ); + } + + // We might have a chart with the same name in multiple repositories - we only have chart metadata + // We don't know which Helm repository it came from, so use the name and sources to match + private isProbablySameChart(a: ChartMetadata, b: ChartMetadata): boolean { + // Basic properties must be the same + if ((a.name !== b.name) || (a.sources.length !== b.sources.length)) { + return false; + } + + // Sources must match + let count = 0; + a.sources.forEach(source => { + count += b.sources.findIndex((s) => s === source) === -1 ? 0 : 1; + }); + + if (count !== a.sources.length) { + return false; + } + + return true; + } + } + diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.html new file mode 100644 index 0000000000..7e055c154c --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.html @@ -0,0 +1 @@ +<app-table [dataSource]="dataSource" [columns]="columns"></app-table> diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.spec.ts new file mode 100644 index 0000000000..f2b19dceeb --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.spec.ts @@ -0,0 +1,31 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HelmReleaseGuidMock } from '../../../../../helm/helm-testing.module'; +import { HelmReleaseHelperService } from '../helm-release-helper.service'; +import { HelmReleaseHistoryTabComponent } from './helm-release-history-tab.component'; + +describe('HelmReleaseHistoryTabComponent', () => { + let component: HelmReleaseHistoryTabComponent; + let fixture: ComponentFixture<HelmReleaseHistoryTabComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ HelmReleaseHistoryTabComponent ], + providers: [ + HelmReleaseHelperService, + HelmReleaseGuidMock + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HelmReleaseHistoryTabComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.ts new file mode 100644 index 0000000000..df49f034bc --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.ts @@ -0,0 +1,92 @@ +import { Component } from '@angular/core'; +import * as moment from 'moment'; +import { of } from 'rxjs'; +import { map, startWith } from 'rxjs/operators'; + +import { + ITableListDataSource, +} from '../../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; +import { ITableColumn } from '../../../../../../../../core/src/shared/components/list/list-table/table.types'; +import { HelmReleaseHelperService } from './../helm-release-helper.service'; + +@Component({ + selector: 'app-helm-release-history-tab', + templateUrl: './helm-release-history-tab.component.html', + styleUrls: ['./helm-release-history-tab.component.scss'] +}) +export class HelmReleaseHistoryTabComponent { + + public columns: ITableColumn<any>[] = []; + + public dataSource: ITableListDataSource<any>; + + constructor(public helmReleaseHelper: HelmReleaseHelperService) { + + // Use the ame column layout as the Helm CLI + this.columns = [ + { + columnId: 'revision', + headerCell: () => 'Revision', + cellFlex: '1', + cellDefinition: { + valuePath: 'revision' + } + }, + { + columnId: 'updated', + headerCell: () => 'Updated', + cellFlex: '3', + cellDefinition: { + getValue: row => moment(row.last_deployed).format('LLL') + } + }, + { + columnId: 'status', + headerCell: () => 'Status', + cellFlex: '2', + cellDefinition: { + valuePath: 'status' + } + }, + { + columnId: 'chart', + headerCell: () => 'Chart', + cellFlex: '2', + cellDefinition: { + getValue: row => `${row.chart.name}-${row.chart.version}` + } + }, + { + columnId: 'app_version', + headerCell: () => 'App Version', + cellFlex: '1', + cellDefinition: { + valuePath: 'chart.appVersion' + } + }, + { + columnId: 'description', + headerCell: () => 'Description', + cellFlex: '2', + cellDefinition: { + valuePath: 'description' + } + }, + ]; + + const data$ = this.helmReleaseHelper.fetchReleaseHistory(); + this.dataSource = { + connect: () => data$, + disconnect: () => { }, + trackBy: (index, item) => item.revision, + isTableLoading$: data$.pipe( + map(revisions => !revisions), + startWith(true), + ), + getRowState: (row) => { + return of({}); + } + }; + } + +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html index ad0b133c87..ade8a8bf0a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html @@ -4,6 +4,11 @@ <span>Delete</span> </button> + <button *ngIf="canUpgrade$ | async" mat-button name="upgrade" [routerLink]="['../upgrade']" matTooltip="Upgrade"> + <mat-icon>vertical_align_top</mat-icon> + <span>Upgrade</span> + </button> + <app-analysis-report-runner [kubeId]="helmReleaseHelper.endpointGuid" [namespace]="helmReleaseHelper.namespace" [app]="helmReleaseHelper.releaseTitle"> </app-analysis-report-runner> @@ -16,35 +21,41 @@ <app-loading-page [isLoading]="isBusy$" [text]="loadingMessage"> <ng-container *ngIf="helmReleaseHelper.release$ | async as release"> - <app-entity-summary-title [imagePath]="release.chart.metadata.icon" [title]="release.name" + <div> + <div *ngIf="hasUpgrade$ | async as upgrade" class="chart-upgrade"> + Upgrade available: {{ upgrade }} + </div> + <app-entity-summary-title [imagePath]="release.chart.metadata.icon" [title]="release.name" [subText]="release.chart.metadata.description" [subTitle]="release.chart.metadata.name" [fallBackIcon]="'workloads'" [fallBackIconFont]="'stratos-icons'"> - <div class="summary"> - <div class="chart-details"> - <app-metadata-item class="chart-details__item" label="Chart Version">{{ release.chart.metadata.version }} - </app-metadata-item> - <app-metadata-item class="chart-details__item" label="Application Version"> - {{ release.chart.metadata.appVersion || '-' }} - </app-metadata-item> - <app-metadata-item class="chart-details__item" label="Cluster"> - <a [routerLink]="createClusterLink()">{{ getClusterName() | async }}</a> - </app-metadata-item> - <app-metadata-item class="chart-details__item" label="Namespace"> - <a [routerLink]="createNamespaceLink(release.namespace)">{{ release.namespace }}</a> - </app-metadata-item> - <app-metadata-item class="chart-details__item" label="Release Version">{{ release.version }} - </app-metadata-item> - <app-metadata-item class="chart-details__item" label="Status">{{ release.status | titlecase }} - </app-metadata-item> - <app-metadata-item class="chart-details__item" label="First Deployed"> - {{ release.info.first_deployed | date:'medium' }} - </app-metadata-item> - <app-metadata-item class="chart-details__item" label="Last Deployed"> - {{ release.info.last_deployed | date:'medium' }} - </app-metadata-item> + <div class="summary"> + <div class="chart-details"> + <app-metadata-item class="chart-details__item" label="Chart Version">{{ release.chart.metadata.version }} + </app-metadata-item> + <app-metadata-item class="chart-details__item" label="Application Version"> + {{ release.chart.metadata.appVersion || '-' }} + </app-metadata-item> + <app-metadata-item class="chart-details__item" label="Cluster"> + <a [routerLink]="createClusterLink()">{{ getClusterName() | async }}</a> + </app-metadata-item> + <app-metadata-item class="chart-details__item" label="Namespace"> + <a [routerLink]="createNamespaceLink(release.namespace)">{{ release.namespace }}</a> + </app-metadata-item> + <app-metadata-item class="chart-details__item" label="Release Version">{{ release.version }} + </app-metadata-item> + <app-metadata-item class="chart-details__item" label="Status">{{ release.status | titlecase }} + </app-metadata-item> + <app-metadata-item class="chart-details__item" label="First Deployed"> + {{ release.info.first_deployed | date:'medium' }} + </app-metadata-item> + <app-metadata-item class="chart-details__item" label="Last Deployed"> + {{ release.info.last_deployed | date:'medium' }} + </app-metadata-item> + </div> </div> - </div> - </app-entity-summary-title> + </app-entity-summary-title> + </div> + <div *ngIf="hasResources$ | async; else loadingResources" class="resources"> <app-metadata-item class="chart-details__item" label="Pods and Containers"></app-metadata-item> <app-tile-grid> diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss index d57c1bb42f..b2f30b0f4a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss @@ -43,6 +43,13 @@ opacity: .6; } +.chart-upgrade { + border-radius: 8px; + float: right; + font-size: 12px; + margin-right: 10px; + padding: 2px 8px; +} .grid { $bottom-space: 20px; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts index 4582dd922d..9eb1592e81 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts @@ -2,9 +2,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TabNavService } from 'frontend/packages/core/tab-nav.service'; import { SidePanelService } from '../../../../../../../../core/src/shared/services/side-panel.service'; -import { HelmReleaseProviders, KubeBaseGuidMock, KubernetesBaseTestModules } from '../../../../kubernetes.testing.module'; +import { HelmReleaseProviders, KubeBaseGuidMock } from '../../../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../../../services/kubernetes-endpoint.service'; import { KubernetesAnalysisService } from '../../../../services/kubernetes.analysis.service'; +import { WorkloadsBaseTestingModule } from '../../../workloads.testing.module'; import { AnalysisReportSelectorComponent, } from './../../../../analysis-report-viewer/analysis-report-selector/analysis-report-selector.component'; @@ -17,7 +18,7 @@ describe('HelmReleaseSummaryTabComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ - ...KubernetesBaseTestModules + ...WorkloadsBaseTestingModule ], declarations: [HelmReleaseSummaryTabComponent, AnalysisReportSelectorComponent], providers: [ diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.theme.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.theme.scss new file mode 100644 index 0000000000..30c0894af2 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.theme.scss @@ -0,0 +1,15 @@ +@mixin helm-release-summary-tab-theme($theme, $app-theme) { + + $status-colors: map-get($app-theme, status); + $status-success: map-get($status-colors, success); + $status-warning: map-get($status-colors, warning); + $status-danger: map-get($status-colors, danger); + $status-tentative: map-get($status-colors, tentative); + $status-info: map-get($status-colors, info); + + .chart-upgrade { + border: 1px solid $status-success; + color: $status-success; + } + +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts index 3b3d8ce7c8..0a8fbac769 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts @@ -47,6 +47,11 @@ export class HelmReleaseSummaryTabComponent implements OnDestroy { public path: string; + public hasUpgrade$: Observable<string>; + + // Can we upgrade? Yes as long as the Helm Chart can be found + public canUpgrade$: Observable<boolean>; + public podChartColors = [ { name: 'Running', @@ -115,6 +120,10 @@ export class HelmReleaseSummaryTabComponent implements OnDestroy { ) ); + this.hasUpgrade$ = this.helmReleaseHelper.hasUpgrade().pipe(map(v => v ? v.version : null)); + + this.canUpgrade$ = this.helmReleaseHelper.hasUpgrade(true).pipe(map(v => !!v)); + this.resources$ = combineLatest( this.helmReleaseHelper.fetchReleaseGraph(), this.analysisReportUpdated$ @@ -217,7 +226,7 @@ export class HelmReleaseSummaryTabComponent implements OnDestroy { const action = workloadsEntityCatalog.release.actions.getMultiple(); this.store.dispatch(new ClearPaginationOfType(action)); this.completeDelete(); - this.store.dispatch(new RouterNav({ path: ['workloads'] })); + this.store.dispatch(new RouterNav({ path: ['./workloads'] })); } }); }); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workload-action-builders.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workload-action-builders.ts index eb7591f9c5..f85d09243e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workload-action-builders.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workload-action-builders.ts @@ -1,42 +1,74 @@ import { OrchestratedActionBuilders, } from '../../../../../../store/src/entity-catalog/action-orchestrator/action-orchestrator'; -import { GetHelmRelease, GetHelmReleaseGraph, GetHelmReleaseResource, GetHelmReleases } from './workloads.actions'; +import { HelmUpgradeValues } from '../../../helm/store/helm.types'; +import { + GetHelmRelease, + GetHelmReleaseGraph, + GetHelmReleaseHistory, + GetHelmReleaseResource, + GetHelmReleases, + UpgradeHelmRelease, +} from './workloads.actions'; export interface WorkloadReleaseBuilders extends OrchestratedActionBuilders { get: ( title: string, endpointGuid: string, - extraArgs: { namespace: string } + extraArgs: { namespace: string, } ) => GetHelmRelease; getMultiple: () => GetHelmReleases; + upgrade: ( + title: string, + endpointGuid: string, + namespace: string, + values: HelmUpgradeValues) => UpgradeHelmRelease; } export const workloadReleaseBuilders: WorkloadReleaseBuilders = { - get: (title: string, endpointGuid: string, { namespace }: { namespace: string }) => { + get: (title: string, endpointGuid: string, { namespace }: { namespace: string, }) => { return new GetHelmRelease(endpointGuid, namespace, title); }, - getMultiple: () => new GetHelmReleases() -} + getMultiple: () => new GetHelmReleases(), + upgrade: ( + title: string, + endpointGuid: string, + namespace: string, + values: HelmUpgradeValues) => new UpgradeHelmRelease(title, endpointGuid, namespace, values) +}; export interface WorkloadGraphBuilders extends OrchestratedActionBuilders { get: ( releaseTitle: string, endpointGuid: string - ) => GetHelmReleaseGraph + ) => GetHelmReleaseGraph; } export const workloadGraphBuilders: WorkloadGraphBuilders = { get: (releaseTitle: string, endpointGuid: string) => new GetHelmReleaseGraph(endpointGuid, releaseTitle) -} +}; export interface WorkloadResourceBuilders extends OrchestratedActionBuilders { get: ( releaseTitle: string, endpointGuid: string, - ) => GetHelmReleaseResource + ) => GetHelmReleaseResource; } export const workloadResourceBuilders: WorkloadResourceBuilders = { get: (releaseTitle: string, endpointGuid: string) => new GetHelmReleaseResource(endpointGuid, releaseTitle) -} \ No newline at end of file +}; + +export interface WorkloadResourceHistoryBuilders extends OrchestratedActionBuilders { + get: ( + releaseTitle: string, + endpointGuid: string, + extraArgs: { namespace: string, } + ) => GetHelmReleaseHistory; +} + +export const workloadResourceHistoryBuilders: WorkloadResourceHistoryBuilders = { + get: (releaseTitle: string, endpointGuid: string, { namespace }: { namespace: string, }) => + new GetHelmReleaseHistory(endpointGuid, namespace, releaseTitle) +}; + diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-factory.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-factory.ts index c1f3dd65bc..25ada89e41 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-factory.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-factory.ts @@ -7,6 +7,7 @@ export const helmReleasePodEntityType = 'helmReleasePod'; export const helmReleaseServiceEntityType = 'helmReleaseService'; export const helmReleaseGraphEntityType = 'helmReleaseGraph'; export const helmReleaseResourceEntityType = 'helmReleaseResource'; +export const helmReleaseHistoryEntityType = 'helmReleaseHistory'; const separator = ':'; export const getHelmReleaseDetailsFromGuid = (guid: string) => { @@ -48,8 +49,16 @@ entityCache[helmReleaseResourceEntityType] = new KubernetesEntitySchema( { idAttribute: getHelmReleaseResourceIdByObj } ); +entityCache[helmReleaseHistoryEntityType] = new KubernetesEntitySchema( + helmReleaseHistoryEntityType, + {}, + { idAttribute: getHelmReleaseResourceIdByObj } +); + Object.entries(entityCache).forEach(([key, workloadSchema]) => addKubernetesEntitySchema(key, workloadSchema)); + export const createHelmReleaseEntities = (): { [cacheName: string]: EntitySchema; } => { return entityCache; }; + diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-generator.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-generator.ts index 3a8f8a0181..d1695d2322 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-generator.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-generator.ts @@ -7,6 +7,7 @@ import { IFavoriteMetadata } from '../../../../../../store/src/types/user-favori import { kubernetesEntityFactory } from '../../kubernetes-entity-factory'; import { HelmRelease, HelmReleaseGraph, HelmReleaseResources } from '../workload.types'; import { workloadsEntityCatalog } from '../workloads-entity-catalog'; +import { HelmReleaseHistory } from './../workload.types'; import { WorkloadGraphBuilders, workloadGraphBuilders, @@ -14,15 +15,24 @@ import { workloadReleaseBuilders, WorkloadResourceBuilders, workloadResourceBuilders, + WorkloadResourceHistoryBuilders, + workloadResourceHistoryBuilders, } from './workload-action-builders'; -import { helmReleaseEntityKey, helmReleaseGraphEntityType, helmReleaseResourceEntityType } from './workloads-entity-factory'; +import { + helmReleaseEntityKey, + helmReleaseGraphEntityType, + helmReleaseHistoryEntityType, + helmReleaseResourceEntityType, +} from './workloads-entity-factory'; export function generateWorkloadsEntities(endpointDefinition: StratosEndpointExtensionDefinition): StratosBaseCatalogEntity[] { + return [ generateReleaseEntity(endpointDefinition), generateReleaseGraphEntity(endpointDefinition), generateReleaseResourceEntity(endpointDefinition), + generateReleaseHistoryEntity(endpointDefinition) ]; } @@ -71,3 +81,18 @@ function generateReleaseResourceEntity(endpointDefinition: StratosEndpointExtens return workloadsEntityCatalog.resource; } +function generateReleaseHistoryEntity(endpointDefinition: StratosEndpointExtensionDefinition) { + const definition = { + type: helmReleaseHistoryEntityType, + schema: kubernetesEntityFactory(helmReleaseHistoryEntityType), + endpoint: endpointDefinition + }; + workloadsEntityCatalog.history = new StratosCatalogEntity<IFavoriteMetadata, HelmReleaseHistory, WorkloadResourceHistoryBuilders>( + definition, + { + actionBuilders: workloadResourceHistoryBuilders + } + ); + return workloadsEntityCatalog.history; +} + diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.actions.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.actions.ts index b2448b591f..d80ba31beb 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.actions.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.actions.ts @@ -1,7 +1,7 @@ import { EntityRequestAction } from 'frontend/packages/store/src/types/request.types'; -import { PaginatedAction } from '../../../../../../store/src/types/pagination.types'; import { MonocularPaginationAction } from '../../../helm/store/helm.actions'; +import { HelmUpgradeValues } from '../../../helm/store/helm.types'; import { KUBERNETES_ENDPOINT_TYPE, kubernetesEntityFactory, @@ -15,6 +15,7 @@ import { getHelmReleaseResourceId, helmReleaseEntityKey, helmReleaseGraphEntityType, + helmReleaseHistoryEntityType, helmReleaseResourceEntityType, } from './workloads-entity-factory'; @@ -38,12 +39,16 @@ export const UPDATE_HELM_RELEASE = '[Helm] Update Release'; export const UPDATE_HELM_RELEASE_SUCCESS = '[Helm] Update Release Success'; export const UPDATE_HELM_RELEASE_FAILURE = '[Helm] Update Release Failure'; -interface HelmReleaseSingleEntity extends EntityRequestAction { - guid: string; -} +export const GET_HELM_RELEASE_HISTORY = '[Helm] Get Release History'; +export const GET_HELM_RELEASE_HISTORY_SUCCESS = '[Helm] Get Release History Success'; +export const GET_HELM_RELEASE_HISTORY_FAILURE = '[Helm] Get Release History Failure'; -interface HelmReleasePaginated extends PaginatedAction, EntityRequestAction { +export const UPGRADE_HELM_RELEASE = '[Helm] Upgrade Release'; +export const UPGRADE_HELM_RELEASE_SUCCESS = '[Helm] Upgrade Release Success'; +export const UPGRADE_HELM_RELEASE_FAILURE = '[Helm] Upgrade Release Failure'; +interface HelmReleaseSingleEntity extends EntityRequestAction { + guid: string; } export class GetHelmReleases implements MonocularPaginationAction { @@ -170,3 +175,46 @@ export class GetHelmReleaseServices implements KubePaginationAction { }; flattenPagination = true; } + +export class GetHelmReleaseHistory implements HelmReleaseSingleEntity { + constructor( + public endpointGuid: string, + public namespace: string, + public releaseTitle: string + ) { + this.guid = getHelmReleaseId(this.endpointGuid, this.namespace, this.releaseTitle); + } + type = GET_HELM_RELEASE_HISTORY; + endpointType = KUBERNETES_ENDPOINT_TYPE; + entity = kubernetesEntityFactory(helmReleaseHistoryEntityType); + entityType = helmReleaseHistoryEntityType; + actions = [ + GET_HELM_RELEASE_HISTORY, + GET_HELM_RELEASE_HISTORY_SUCCESS, + GET_HELM_RELEASE_HISTORY_FAILURE + ]; + + guid: string; +} + +export class UpgradeHelmRelease implements HelmReleaseSingleEntity { + guid: string; + type = UPGRADE_HELM_RELEASE; + endpointType = KUBERNETES_ENDPOINT_TYPE; + entity = kubernetesEntityFactory(helmReleaseEntityKey); + entityType = helmReleaseEntityKey; + constructor( + public releaseTitle: string, + public endpointGuid: string, + public namespace: string, + public values: HelmUpgradeValues + ) { + this.guid = getHelmReleaseId(this.endpointGuid, this.namespace, this.releaseTitle); + } + updatingKey = 'upgrading'; + actions = [ + UPGRADE_HELM_RELEASE, + UPGRADE_HELM_RELEASE_SUCCESS, + UPGRADE_HELM_RELEASE_FAILURE + ]; +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.effects.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.effects.ts index ea7add12fe..a572f14604 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.effects.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.effects.ts @@ -8,6 +8,7 @@ import { catchError, flatMap, mergeMap } from 'rxjs/operators'; import { AppState } from '../../../../../../store/src/app-state'; import { entityCatalog } from '../../../../../../store/src/entity-catalog/entity-catalog'; +import { ApiRequestTypes } from '../../../../../../store/src/reducers/api-request-reducer/request-helpers'; import { NormalizedResponse } from '../../../../../../store/src/types/api.types'; import { EntityRequestAction, @@ -16,9 +17,19 @@ import { WrapperRequestActionSuccess, } from '../../../../../../store/src/types/request.types'; import { HelmEffects } from '../../../helm/store/helm.effects'; -import { HelmRelease } from '../workload.types'; +import { HelmRelease, HelmReleaseHistory } from '../workload.types'; +import { workloadsEntityCatalog } from './../workloads-entity-catalog'; import { getHelmReleaseId } from './workloads-entity-factory'; -import { GET_HELM_RELEASE, GET_HELM_RELEASES, GetHelmRelease, GetHelmReleases } from './workloads.actions'; +import { + GET_HELM_RELEASE, + GET_HELM_RELEASE_HISTORY, + GET_HELM_RELEASES, + GetHelmRelease, + GetHelmReleaseHistory, + GetHelmReleases, + UPGRADE_HELM_RELEASE, + UpgradeHelmRelease, +} from './workloads.actions'; @Injectable() export class WorkloadsEffects { @@ -82,7 +93,77 @@ export class WorkloadsEffects { }) ); - private mapHelmRelease(data, endpointId, guid: string) { + @Effect() + fetchHelmReleaseHistory$ = this.actions$.pipe( + ofType<GetHelmReleaseHistory>(GET_HELM_RELEASE_HISTORY), + flatMap(action => { + const entityKey = entityCatalog.getEntityKey(action); + return this.makeRequest( + action, + `/pp/${this.proxyAPIVersion}/helm/releases/${action.endpointGuid}/${action.namespace}/${action.releaseTitle}/history`, + (response) => { + const processedData = { + entities: { [entityKey]: {} }, + result: [] + } as NormalizedResponse; + + const data: HelmReleaseHistory = { + endpointId: action.endpointGuid, + releaseTitle: action.releaseTitle, + revisions: [] + }; + + for (const version of response) { + data.revisions.push({ + ...version.info, + revision: version.version, + chart: version.chart.metadata, + values: version.chart.values + }); + } + // Store the data against the release guid + processedData.entities[entityKey][action.guid] = data; + processedData.result.push(action.guid); + return processedData; + }, [action.endpointGuid]); + }) + ); + + @Effect() + helmUpgrade$ = this.actions$.pipe( + ofType<UpgradeHelmRelease>(UPGRADE_HELM_RELEASE), + flatMap(action => { + const requestType: ApiRequestTypes = 'update'; + const url = `/pp/v1//helm/releases/${action.endpointGuid}/${action.namespace}/${action.releaseTitle}`; + this.store.dispatch(new StartRequestAction(action, requestType)); + // Refresh the workload after upgrade + const fetchAction = workloadsEntityCatalog.release.actions.get(action.releaseTitle, action.endpointGuid, + { namespace: action.namespace }); + return this.httpClient.post(url, action.values).pipe( + mergeMap(() => { + return [ + fetchAction, + new WrapperRequestActionSuccess(null, action) + ]; + }), + catchError(error => { + const { status, message } = HelmEffects.createHelmError(error); + const errorMessage = `Failed to upgrade helm release: ${message}`; + return [ + new WrapperRequestActionFailed(errorMessage, action, requestType, { + endpointIds: [action.endpointGuid], + url: error.url || url, + eventCode: status, + message: errorMessage, + error + }) + ]; + }) + ); + }) + ); + +private mapHelmRelease(data, endpointId, guid: string) { const helmRelease: HelmRelease = { ...data, endpointId diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-data-source.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-data-source.ts new file mode 100644 index 0000000000..f98ddea450 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-data-source.ts @@ -0,0 +1,61 @@ +import { Store } from '@ngrx/store'; +import { Observable, of } from 'rxjs'; + +import { + DataFunction, + ListDataSource, +} from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; +import { RowState } from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; +import { IListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; +import { PaginationEntityState } from '../../../../../../store/src/types/pagination.types'; +import { helmEntityCatalog } from '../../../helm/helm-entity-catalog'; +import { helmEntityFactory, monocularChartVersionsEntityType } from '../../../helm/helm-entity-factory'; +import { MonocularVersion } from './../../../helm/store/helm.types'; + + +const typeFilterKey = 'versionType'; + + +export class HelmReleaseVersionsDataSource extends ListDataSource<MonocularVersion> { + + private currentVersion: string; + + constructor( + store: Store<any>, + listConfig: IListConfig<MonocularVersion>, + repoName: string, + chartName: string, + version: string, + ) { + super({ + store, + action: helmEntityCatalog.chartVersions.actions.getMultiple(repoName, chartName), + schema: helmEntityFactory(monocularChartVersionsEntityType), + getRowUniqueId: (object: MonocularVersion) => object.id, + paginationKey: helmEntityCatalog.chartVersions.actions.getMultiple(repoName, chartName).paginationKey, + isLocal: true, + transformEntities: [ + (entities: MonocularVersion[], paginationState: PaginationEntityState) => this.endpointTypeFilter(entities, paginationState) + ], + listConfig, + }); + + this.currentVersion = version; + this.getRowState = (row: any): Observable<RowState> => of({ highlighted: row.attributes.version === this.currentVersion }); + } + + + public endpointTypeFilter: DataFunction<MonocularVersion> = (entities: MonocularVersion[], paginationState: PaginationEntityState) => { + if ( + !paginationState.clientPagination || + !paginationState.clientPagination.filter || + !paginationState.clientPagination.filter.items[typeFilterKey]) { + return entities; + } + + // Filter out development versions if configured + const showAll = paginationState.clientPagination.filter.items[typeFilterKey] === 'all'; + return showAll ? entities : entities.filter(e => e.attributes.version.indexOf('-') === -1); + } +} + diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-list-config.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-list-config.ts new file mode 100644 index 0000000000..6d712da1d5 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-list-config.ts @@ -0,0 +1,131 @@ +import { Store } from '@ngrx/store'; +import * as moment from 'moment'; +import { BehaviorSubject, of } from 'rxjs'; +import { first } from 'rxjs/operators'; + +import { ListDataSource } from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; +import { + TableCellRadioComponent, +} from '../../../../../../core/src/shared/components/list/list-table/table-cell-radio/table-cell-radio.component'; +import { ITableColumn } from '../../../../../../core/src/shared/components/list/list-table/table.types'; +import { + defaultPaginationPageSizeOptionsTable, + IGlobalListAction, + IListAction, + IListConfig, + IListMultiFilterConfig, + IMultiListAction, + ListViewTypes, +} from '../../../../../../core/src/shared/components/list/list.component.types'; +import { MonocularVersion } from '../../../helm/store/helm.types'; +import { HelmReleaseVersionsDataSource } from './release-version-data-source'; + +const typeFilterKey = 'versionType'; + +export class ReleaseUpgradeVersionsListConfig implements IListConfig<any> { + + public versionsDataSource: ListDataSource<MonocularVersion>; + + private multiFiltersConfigs: IListMultiFilterConfig[]; + + getGlobalActions: () => IGlobalListAction<any>[]; + getMultiActions: () => IMultiListAction<any>[]; + getSingleActions: () => IListAction<any>[]; + + columns: Array<ITableColumn<any>> = [ + { + columnId: 'radio', + headerCell: () => '', + cellComponent: TableCellRadioComponent, + class: 'table-column-select', + cellFlex: '0 0 60px' + }, + { + columnId: 'version', + headerCell: () => 'Version', + cellFlex: '2', + cellDefinition: { + valuePath: 'attributes.version' + } + }, + { + columnId: 'created', + headerCell: () => 'Created', + cellFlex: '3', + cellDefinition: { + getValue: row => moment(row.attributes.created).format('LLL') + } + }, + { + columnId: 'age', + headerCell: () => 'Age', + cellFlex: '2', + cellDefinition: { + getValue: row => moment(row.attributes.created).fromNow(true) + } + }, + ]; + pageSizeOptions = defaultPaginationPageSizeOptionsTable; + viewType = ListViewTypes.TABLE_ONLY; + + hideRefresh = true; + + getColumns = () => this.columns; + getMultiFiltersConfigs = (): IListMultiFilterConfig[] => this.multiFiltersConfigs; + + getDataSource = () => this.versionsDataSource; + + constructor( + store: Store<any>, + repoName: string, + chartName: string, + version: string, + ) { + this.getGlobalActions = () => []; + this.getMultiActions = () => []; + this.getSingleActions = () => []; + + this.versionsDataSource = new HelmReleaseVersionsDataSource(store, this, repoName, chartName, version); + + this.multiFiltersConfigs = [{ + hideAllOption: true, + autoSelectFirst: true, + key: typeFilterKey, + label: 'Endpoint Type', + list$: of([ + { + label: 'Release Versions', + item: {}, + value: 'release' + }, + { + label: 'All Versions', + item: {}, + value: 'all' + } + ]), + loading$: of(false), + select: new BehaviorSubject(undefined) + }]; + + // Auto-select first non-development version + setTimeout(() => { + this.versionsDataSource.page$.pipe(first()).subscribe(rs => { + if (rs && rs.length > 0) { + this.versionsDataSource.selectedRowToggle(this.getFirstNonDevelopmentVersion(rs), false); + } + }); + }, 0); + } + + // Get the first version that is a non-development version (no hypen in the version number) + private getFirstNonDevelopmentVersion(rows: MonocularVersion[]): MonocularVersion { + for (const mv of rows) { + if (mv.attributes.version.indexOf('-') === -1) { + return mv + } + } + return rows[0]; + } + +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.html new file mode 100644 index 0000000000..dae9721887 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.html @@ -0,0 +1,37 @@ +<app-page-header> + Upgrade Workload +</app-page-header> +<app-steppers [cancel]="cancelUrl"> + <app-step title="Version" [valid]="validate$ | async"> + <app-list class="workload-upgrade__list" *ngIf="listConfig" [listConfig]="listConfig"></app-list> + </app-step> + <app-step title="Overrides" [onNext]="doUpgrade" finishButtonText="Upgrade"> + <div class="workload-upgrade__form"> + <form [formGroup]="overrides" class="stepper-form overrides_form"> + <div class="workload-upgrade__heading"> + <h3 class="workload-upgrade__title">Enter YAML Value Overrides</h3> + <!-- + <button (click)="useValuesYaml()" [disabled]="!valuesYaml" class=""workload-upgrade__button" mat-button + color="primary">Copy from values.yaml</button> + --> + </div> + <mat-form-field [floatLabel]="'always'" class="overrides_form-field"> + <mat-label>Values</mat-label> + <textarea #overridesYamlTextArea class="overrides__yaml" matInput formControlName="values" name="Values" + spellcheck="false"></textarea> + </mat-form-field> + </form> + <!-- + <mat-slide-toggle (change)="toggleAdvancedOptions()">Show Advanced Options</mat-slide-toggle> + --> + </div> + </app-step> + + <!-- Add support for recreate pods and other advanced options in the future + <app-step [hidden]="!showAdvancedOptions" [onNext]="doUpgrade" title="Advanced" finishButtonText="Upgrade"> + Advanced Options + </app-step> + --> + + +</app-steppers> \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.scss new file mode 100644 index 0000000000..8d5007efa3 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.scss @@ -0,0 +1,59 @@ +:host { + flex: 1; +} +.workload-upgrade { + + &__list { + width: 100%; + } + + &__form { + display: flex; + flex: 1; + flex-direction: column; + } + &__heading { + align-items: center; + display: flex; + } + + &__title { + flex: 1; + font-size: 14px; + } + &__button { + height: 36px; + } +} + +form { + flex: 1; + + mat-checkbox { + display: flex; + height: 23px; + margin-top: 10px; + } +} + +.overrides { + &__yaml { + background-color: rgba(0, 0, 0, .1); + font-family: 'Source Code Pro', monospace; + height: 400px; + } + + &_form { + max-width: 100%; + } + + &_form-field { + flex: 1; + height: 100%; + width: 100%; + } +} + +form.overrides_form { + max-width: 100%; +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.spec.ts new file mode 100644 index 0000000000..f77dc54235 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.spec.ts @@ -0,0 +1,37 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HelmReleaseProviders, KubeBaseGuidMock } from '../../kubernetes.testing.module'; +import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; +import { WorkloadsBaseTestingModule } from '../workloads.testing.module'; +import { UpgradeReleaseComponent } from './upgrade-release.component'; + + +describe('UpgradeReleaseComponent', () => { + let component: UpgradeReleaseComponent; + let fixture: ComponentFixture<UpgradeReleaseComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ UpgradeReleaseComponent ], + imports: [ + ...WorkloadsBaseTestingModule + ], + providers: [ + KubernetesEndpointService, + KubeBaseGuidMock, + ...HelmReleaseProviders, + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(UpgradeReleaseComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts new file mode 100644 index 0000000000..8ef8a83118 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts @@ -0,0 +1,114 @@ +import { Component } from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { Store } from '@ngrx/store'; +import { Observable, of } from 'rxjs'; +import { filter, first, map, pairwise } from 'rxjs/operators'; + +import { StepComponent, StepOnNextFunction } from '../../../../../../core/src/shared/components/stepper/step/step.component'; +import { ActionState } from '../../../../../../store/src/reducers/api-request-reducer/types'; +import { HelmUpgradeValues } from '../../../helm/store/helm.types'; +import { HelmReleaseHelperService } from '../release/tabs/helm-release-helper.service'; +import { HelmReleaseGuid } from '../workload.types'; +import { workloadsEntityCatalog } from './../workloads-entity-catalog'; +import { ReleaseUpgradeVersionsListConfig } from './release-version-list-config'; + +@Component({ + selector: 'app-upgrade-release', + templateUrl: './upgrade-release.component.html', + styleUrls: ['./upgrade-release.component.scss'], + providers: [ + HelmReleaseHelperService, + { + provide: HelmReleaseGuid, + useFactory: (activatedRoute: ActivatedRoute) => ({ + guid: activatedRoute.snapshot.params.guid + }), + deps: [ + ActivatedRoute + ] + } + ] +}) +export class UpgradeReleaseComponent { + + public cancelUrl; + public listConfig; + public validate$: Observable<boolean>; + private version; + public overrides: FormGroup; + + // Future + public showAdvancedOptions = false; + + constructor(store: Store<any>, public helper: HelmReleaseHelperService) { + + this.cancelUrl = `/workloads/${this.helper.guid}`; + + // Form for overrides step (Helm Values) + this.overrides = new FormGroup({ + values: new FormControl('') + }); + + this.helper.hasUpgrade(true).pipe( + filter(c => !!c), + first() + ).subscribe(chart => { + const name = chart.upgrade.name; + const repoName = chart.upgrade.repo.name; + const version = chart.release.chart.metadata.version; + this.listConfig = new ReleaseUpgradeVersionsListConfig(store, repoName, name, version); + + // First step is valid when a version has been selected + this.validate$ = this.listConfig.versionsDataSource.selectedRows$.pipe( + map((rows: Map<string, any>) => { + if (rows && rows.size === 1) { + this.version = rows.values().next().value; + return true; + } + return false; + }) + ); + }); + } + + // Hide/show the advanced options step + toggleAdvancedOptions() { + this.showAdvancedOptions = !this.showAdvancedOptions; + } + + doUpgrade: StepOnNextFunction = (index: number, step: StepComponent) => { + // If we are showing the advanced options, don't upgrade if we aer not on the last step + if (this.showAdvancedOptions && index === 1 ) { + return of({ success: true }); + } + + const values: HelmUpgradeValues = { + ...this.overrides.value, + restartPods: false, + chart: { + name: this.version.relationships.chart.data.name, + repo: this.version.relationships.chart.data.repo.name, + version: this.version.attributes.version, + }, + }; + + // Make the request + return workloadsEntityCatalog.release.api.upgrade<ActionState>(this.helper.releaseTitle, + this.helper.endpointGuid, this.helper.namespace, values).pipe( + // Wait for result of request + filter(state => !!state), + pairwise(), + filter(([oldVal, newVal]) => (oldVal.busy && !newVal.busy)), + map(([, newVal]) => newVal), + map(result => ({ + success: !result.error, + redirect: !result.error, + redirectPayload: { + path: !result.error ? this.cancelUrl : '' + }, + message: !result.error ? '' : result.message + })) + ); + } +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workload.types.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workload.types.ts index bf5b8e708b..321e86b734 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workload.types.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workload.types.ts @@ -21,7 +21,11 @@ export interface HelmRelease { chart: { values: any; metadata: { + name: string; + version: string; icon?: string; + description: string; + sources: string[]; }; }; } @@ -67,6 +71,19 @@ export interface HelmReleaseResources extends HelmReleaseEntity { kind: string }; +export interface HelmReleaseRevision { + first_deployed: string; + last_deployed: string; + deleted: boolean; + description: string; + status: string; + revision: number; +} + +export interface HelmReleaseHistory extends HelmReleaseEntity { + revisions: HelmReleaseRevision[], +}; + export interface HelmReleaseKubeAPIResource extends KubeAPIResource { apiVersion: string; kind: string; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads-entity-catalog.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads-entity-catalog.ts index 6492abd0b9..30a8791541 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads-entity-catalog.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads-entity-catalog.ts @@ -1,7 +1,12 @@ import { StratosCatalogEntity } from '../../../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; import { IFavoriteMetadata } from '../../../../../store/src/types/user-favorites.types'; -import { WorkloadGraphBuilders, WorkloadReleaseBuilders, WorkloadResourceBuilders } from './store/workload-action-builders'; -import { HelmRelease, HelmReleaseGraph, HelmReleaseResources } from './workload.types'; +import { + WorkloadGraphBuilders, + WorkloadReleaseBuilders, + WorkloadResourceBuilders, + WorkloadResourceHistoryBuilders, +} from './store/workload-action-builders'; +import { HelmRelease, HelmReleaseGraph, HelmReleaseHistory, HelmReleaseResources } from './workload.types'; /** * A strongly typed collection of Workload Catalog Entities. @@ -9,8 +14,9 @@ import { HelmRelease, HelmReleaseGraph, HelmReleaseResources } from './workload. */ export class WorkloadsEntityCatalog { release: StratosCatalogEntity<IFavoriteMetadata, HelmRelease, WorkloadReleaseBuilders>; - graph: StratosCatalogEntity<IFavoriteMetadata, HelmReleaseGraph, WorkloadGraphBuilders> - resource: StratosCatalogEntity<IFavoriteMetadata, HelmReleaseResources, WorkloadResourceBuilders> + graph: StratosCatalogEntity<IFavoriteMetadata, HelmReleaseGraph, WorkloadGraphBuilders>; + resource: StratosCatalogEntity<IFavoriteMetadata, HelmReleaseResources, WorkloadResourceBuilders>; + history: StratosCatalogEntity<IFavoriteMetadata, HelmReleaseHistory, WorkloadResourceHistoryBuilders>; } /** diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts index 05804cb596..44ab5ce4bb 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts @@ -7,6 +7,9 @@ import { SharedModule } from '../../../../../core/src/shared/shared.module'; import { KubernetesModule } from '../kubernetes.module'; import { HelmReleaseCardComponent } from './list-types/helm-release-card/helm-release-card.component'; import { HelmReleaseTabBaseComponent } from './release/helm-release-tab-base/helm-release-tab-base.component'; +import { + HelmReleaseAnalysisTabComponent, +} from './release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component'; import { HelmReleaseNotesTabComponent } from './release/tabs/helm-release-notes-tab/helm-release-notes-tab.component'; import { HelmReleasePodsTabComponent } from './release/tabs/helm-release-pods/helm-release-pods-tab.component'; import { @@ -17,8 +20,9 @@ import { HelmReleaseSummaryTabComponent } from './release/tabs/helm-release-summ import { HelmReleaseValuesTabComponent } from './release/tabs/helm-release-values-tab/helm-release-values-tab.component'; import { HelmReleasesTabComponent } from './releases-tab/releases-tab.component'; import { WorkloadsStoreModule } from './store/workloads.store.module'; +import { UpgradeReleaseComponent } from './upgrade-release/upgrade-release.component'; import { WorkloadsRouting } from './workloads.routing'; -import { HelmReleaseAnalysisTabComponent } from './release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component'; +import { HelmReleaseHistoryTabComponent } from './release/tabs/helm-release-history-tab/helm-release-history-tab.component'; import { WorkloadLiveReloadComponent } from './release/workload-live-reload/workload-live-reload.component'; @NgModule({ @@ -29,7 +33,7 @@ import { WorkloadLiveReloadComponent } from './release/workload-live-reload/work WorkloadsStoreModule, WorkloadsRouting, NgxGraphModule, - KubernetesModule + KubernetesModule, ], declarations: [ HelmReleasesTabComponent, @@ -43,6 +47,8 @@ import { WorkloadLiveReloadComponent } from './release/workload-live-reload/work HelmReleaseCardComponent, HelmReleaseAnalysisTabComponent, WorkloadLiveReloadComponent, + UpgradeReleaseComponent, + HelmReleaseHistoryTabComponent, ], entryComponents: [ HelmReleaseCardComponent diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.routing.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.routing.ts index dc687ecd76..a7732ac7a5 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.routing.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.routing.ts @@ -3,6 +3,10 @@ import { RouterModule, Routes } from '@angular/router'; import { NgxChartsModule } from '@swimlane/ngx-charts'; import { HelmReleaseTabBaseComponent } from './release/helm-release-tab-base/helm-release-tab-base.component'; +import { + HelmReleaseAnalysisTabComponent, +} from './release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component'; +import { HelmReleaseHistoryTabComponent } from './release/tabs/helm-release-history-tab/helm-release-history-tab.component'; import { HelmReleaseNotesTabComponent } from './release/tabs/helm-release-notes-tab/helm-release-notes-tab.component'; import { HelmReleasePodsTabComponent } from './release/tabs/helm-release-pods/helm-release-pods-tab.component'; import { @@ -12,7 +16,7 @@ import { HelmReleaseServicesTabComponent } from './release/tabs/helm-release-ser import { HelmReleaseSummaryTabComponent } from './release/tabs/helm-release-summary-tab/helm-release-summary-tab.component'; import { HelmReleaseValuesTabComponent } from './release/tabs/helm-release-values-tab/helm-release-values-tab.component'; import { HelmReleasesTabComponent } from './releases-tab/releases-tab.component'; -import { HelmReleaseAnalysisTabComponent } from './release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component'; +import { UpgradeReleaseComponent } from './upgrade-release/upgrade-release.component'; const routes: Routes = [ { @@ -23,6 +27,11 @@ const routes: Routes = [ component: HelmReleasesTabComponent, pathMatch: 'full', }, + { + path: ':guid/upgrade', + component: UpgradeReleaseComponent, + pathMatch: 'full', + }, { // Helm Release Views path: ':guid', @@ -35,6 +44,7 @@ const routes: Routes = [ { path: 'summary', component: HelmReleaseSummaryTabComponent }, { path: 'notes', component: HelmReleaseNotesTabComponent }, { path: 'values', component: HelmReleaseValuesTabComponent }, + { path: 'history', component: HelmReleaseHistoryTabComponent }, { path: 'pods', component: HelmReleasePodsTabComponent }, { path: 'services', component: HelmReleaseServicesTabComponent }, { path: 'graph', component: HelmReleaseResourceGraphComponent }, diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.testing.module.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.testing.module.ts new file mode 100644 index 0000000000..5f6b193777 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.testing.module.ts @@ -0,0 +1,47 @@ +import { HttpClientModule } from '@angular/common/http'; +import { NgModule } from '@angular/core'; +import { CoreModule } from '@angular/flex-layout'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { SharedModule } from '../../../../../core/src/public-api'; +import { AppTestModule } from '../../../../../core/test-framework/core-test.helper'; +import { CATALOGUE_ENTITIES, EntityCatalogFeatureModule } from '../../../../../store/src/entity-catalog.module'; +import { entityCatalog, TestEntityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; +import { generateStratosEntities } from '../../../../../store/src/stratos-entity-generator'; +import { createBasicStoreModule } from '../../../../../store/testing/public-api'; +import { generateHelmEntities } from '../../helm/helm-entity-generator'; +import { HelmTestingModule } from '../../helm/helm-testing.module'; +import { generateKubernetesEntities } from '../kubernetes-entity-generator'; + +@NgModule({ + imports: [{ + ngModule: EntityCatalogFeatureModule, + providers: [ + { + provide: CATALOGUE_ENTITIES, useFactory: () => { + const testEntityCatalog = entityCatalog as TestEntityCatalog; + testEntityCatalog.clear(); + return [ + ...generateStratosEntities(), + ...generateKubernetesEntities(), + ...generateHelmEntities(), + ]; + } + } + ] + }] +}) +export class WorkloadsTestingModule { } + +export const WorkloadsBaseTestingModule = [ + AppTestModule, + RouterTestingModule, + CoreModule, + createBasicStoreModule(), + NoopAnimationsModule, + HttpClientModule, + SharedModule, + HelmTestingModule, + WorkloadsTestingModule +] diff --git a/src/jetstream/plugins/kubernetes/install_release.go b/src/jetstream/plugins/kubernetes/install_release.go index 0505f51f06..15cf67623e 100644 --- a/src/jetstream/plugins/kubernetes/install_release.go +++ b/src/jetstream/plugins/kubernetes/install_release.go @@ -15,6 +15,7 @@ import ( "k8s.io/client-go/kubernetes" "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" @@ -34,6 +35,15 @@ type installRequest struct { } `json:"chart"` } +type upgradeRequest struct { + Values string `json:"values"` + Chart struct { + Name string `json:"name"` + Repository string `json:"repo"` + Version string `json:"version"` + } `json:"chart"` +} + // Monocular is a plugin for Monocular type Monocular interface { GetChartStore() *chartsvc.ChartSvcDatastore @@ -51,36 +61,11 @@ func (c *KubernetesSpecification) InstallRelease(ec echo.Context) error { return interfaces.NewJetstreamErrorf("Could not get Create Release Parameters: %v+", err) } - chartID := fmt.Sprintf("%s/%s", params.Chart.Repository, params.Chart.Name) - - log.Debugf("Helm: Installing release %s", chartID) - - downloadURL, err := c.getChart(chartID, params.Chart.Version) - if err != nil { - return interfaces.NewJetstreamErrorf("Could not get the Download URL for the Helm Chart") - } - - log.Debugf("Chart Download URL: %s", downloadURL) - - // NWM: Should we look up Helm Repository endpoint and use the value from that - httpClient := c.portalProxy.GetHttpClient(false) - resp, err := httpClient.Get(downloadURL) - if err != nil { - return interfaces.NewJetstreamErrorf("Could not download Chart Archive: %s", err) - } - if resp.StatusCode != 200 { - return interfaces.NewJetstreamErrorf("Could not download Chart Archive: %s", resp.Status) - } - - defer resp.Body.Close() - - chart, err := loader.LoadArchive(resp.Body) + chart, err := c.loadChart(params.Chart.Repository, params.Chart.Name, params.Chart.Version) if err != nil { - return interfaces.NewJetstreamErrorf("Could not load chart from archive: %v+", err) + return interfaces.NewJetstreamErrorf("Could not load chart: %v+", err) } - log.Debugf("Loaded helm chart: %s", chart.Name()) - endpointGUID := params.Endpoint userGUID := ec.Get("user_id").(string) @@ -154,6 +139,32 @@ func (c *KubernetesSpecification) getChart(chartID, version string) (string, err return "", errors.New("Could not find Chart Version") } +// Load the Helm chart for the given repository, name and version +func (c *KubernetesSpecification) loadChart(repo, name, version string) (*chart.Chart, error) { + + chartID := fmt.Sprintf("%s/%s", repo, name) + downloadURL, err := c.getChart(chartID, version) + if err != nil { + return nil, fmt.Errorf("Could not get the Download URL for the Helm Chart") + } + + log.Debugf("Helm Chart Download URL: %s", downloadURL) + + // NWM: Should we look up Helm Repository endpoint and use the value from that + httpClient := c.portalProxy.GetHttpClient(false) + resp, err := httpClient.Get(downloadURL) + if err != nil { + return nil, fmt.Errorf("Could not download Chart Archive: %s", err) + } + if resp.StatusCode != 200 { + return nil, fmt.Errorf("Could not download Chart Archive: %s", resp.Status) + } + + defer resp.Body.Close() + + return loader.LoadArchive(resp.Body) +} + // DeleteRelease will delete a release func (c *KubernetesSpecification) DeleteRelease(ec echo.Context) error { endpointGUID := ec.Param("endpoint") @@ -170,7 +181,6 @@ func (c *KubernetesSpecification) DeleteRelease(ec echo.Context) error { defer hc.Cleanup() uninstall := action.NewUninstall(config) - deleteResponse, err := uninstall.Run(releaseName) if err != nil { return interfaces.NewJetstreamError("Could not delete Helm Release") @@ -178,3 +188,72 @@ func (c *KubernetesSpecification) DeleteRelease(ec echo.Context) error { return ec.JSON(200, deleteResponse) } + +// GetReleaseHistory will get the history for a release +func (c *KubernetesSpecification) GetReleaseHistory(ec echo.Context) error { + endpointGUID := ec.Param("endpoint") + releaseName := ec.Param("name") + namespace := ec.Param("namespace") + + userGUID := ec.Get("user_id").(string) + + config, hc, err := c.GetHelmConfiguration(endpointGUID, userGUID, namespace) + if err != nil { + return interfaces.NewJetstreamErrorf("Could not get Helm Configuration for endpoint: %+v", err) + } + + defer hc.Cleanup() + + history := action.NewHistory(config) + historyResponse, err := history.Run(releaseName) + if err != nil { + return interfaces.NewJetstreamError("Could not get history for the Helm Release") + } + + return ec.JSON(200, historyResponse) +} + +// UpgradeRelease will upgrade the specified release +func (c *KubernetesSpecification) UpgradeRelease(ec echo.Context) error { + endpointGUID := ec.Param("endpoint") + releaseName := ec.Param("name") + namespace := ec.Param("namespace") + + userGUID := ec.Get("user_id").(string) + + bodyReader := ec.Request().Body + buf := new(bytes.Buffer) + buf.ReadFrom(bodyReader) + + var params upgradeRequest + err := json.Unmarshal(buf.Bytes(), ¶ms) + if err != nil { + return interfaces.NewJetstreamErrorf("Could not get Upgrade Release Parameters: %+v", err) + } + + config, hc, err := c.GetHelmConfiguration(endpointGUID, userGUID, namespace) + if err != nil { + return interfaces.NewJetstreamErrorf("Could not get Helm Configuration for endpoint: %+v", err) + } + + defer hc.Cleanup() + + chart, err := c.loadChart(params.Chart.Repository, params.Chart.Name, params.Chart.Version) + if err != nil { + return interfaces.NewJetstreamErrorf("Could not load chart for upgrade: %+v", err) + } + + userSuppliedValues := map[string]interface{}{} + if err := yaml.Unmarshal([]byte(params.Values), &userSuppliedValues); err != nil { + // Could not parse the user's values + return interfaces.NewJetstreamErrorf("Could not parse values: %+v", err) + } + + upgrade := action.NewUpgrade(config) + upgradeResponse, err := upgrade.Run(releaseName, chart, userSuppliedValues) + if err != nil { + return interfaces.NewJetstreamErrorf("Could not upgrade Helm Release: %+v", err) + } + + return ec.JSON(200, upgradeResponse) +} diff --git a/src/jetstream/plugins/kubernetes/main.go b/src/jetstream/plugins/kubernetes/main.go index fb374deb69..628251591c 100644 --- a/src/jetstream/plugins/kubernetes/main.go +++ b/src/jetstream/plugins/kubernetes/main.go @@ -178,8 +178,10 @@ func (c *KubernetesSpecification) AddSessionGroupRoutes(echoGroup *echo.Group) { echoGroup.GET("/helm/releases", c.ListReleases) echoGroup.POST("/helm/install", c.InstallRelease) echoGroup.DELETE("/helm/releases/:endpoint/:namespace/:name", c.DeleteRelease) + echoGroup.GET("/helm/releases/:endpoint/:namespace/:name/history", c.GetReleaseHistory) echoGroup.GET("/helm/releases/:endpoint/:namespace/:name/status", c.GetReleaseStatus) echoGroup.GET("/helm/releases/:endpoint/:namespace/:name", c.GetRelease) + echoGroup.POST("/helm/releases/:endpoint/:namespace/:name", c.UpgradeRelease) // Kube Terminal if c.kubeTerminal != nil { From 799197834012f0e6a18d7a1abd1ff17e88c658c2 Mon Sep 17 00:00:00 2001 From: Richard Cox <richard-cox@users.noreply.github.com> Date: Fri, 11 Sep 2020 10:19:30 +0100 Subject: [PATCH 611/648] Helm: Allow users to view and use repositories from Helm Hub (#463) * Improve presentation and fix issue with development versions * WIP * Add support for Helm Upgrade and Helm history * Remove console logging * Add comment * Minor tweaks following self-review * WIP * fix install button * Fix actual helm install * Remove helm repos view - need to add endpoint type specific actions to endpoints list view (helm sync) * Show per endpoint type actions in endpoints table - use for helm sync * Tidy up registration step * Tidy up provider interceptor pattern * Tidy up services, fix charts list filter by repo/helm hub * Add endpoint unRegisterable, make helm types sub types, tidy up * Remove debug logging * Fix whitespace * Minor tidy ups * Fix compile issue * Fixed versions again, and bugs following subtype split * Fix unit tests * Add db migration script to update existing helm endpoints with repo subtype * Fix front-end unit tests * Always show upgrade button * Minor entity store type updates * Convert unRegisterable to registeredLimit * Changes following review * Fix display of helm type in favourite cards - favourite cards are NOT by subtype, so parent type must have label info * Multiple Fixes - Fix display of disconected text & box in endpoint favourite card - Don't go out to fetch helm schema if there's none defined - Fixes following merge * Align icons in workload summary page * Fix lint failures * Address PR feedback * Revert * Remove description when checking for similar charts * Only show button when the helm chart is available * Improve types, start work on helm hub upgrade * Fixes following merge * Remove TODOs Co-authored-by: Neil MacDougall <neil.macdougall@suse.com> Co-authored-by: Neil MacDougall <nwmac@users.noreply.github.com> --- src/frontend/packages/core/src/app.module.ts | 3 - .../create-endpoint-base-step.component.ts | 52 +++- .../card-number-metric.component.scss | 1 + .../favorites-meta-card.component.scss | 1 - .../meta-card-base/meta-card.component.html | 32 ++- .../meta-card-base/meta-card.component.ts | 11 +- .../endpoint-card/endpoint-card.component.ts | 15 +- .../endpoint/endpoint-list.helpers.ts | 45 +++- .../tile-selector/tile-selector.component.ts | 10 +- src/frontend/packages/core/xsrf.module.ts | 3 + .../src/entity-catalog/entity-catalog.ts | 2 +- .../entity-catalog/entity-catalog.types.ts | 18 +- .../helm/chart-view/monocular.component.ts | 9 +- .../create-release.component.ts | 19 +- .../src/custom/helm/helm-entity-factory.ts | 6 +- .../src/custom/helm/helm-entity-generator.ts | 62 ++++- .../helm-hub-registration.component.html | 10 + .../helm-hub-registration.component.scss} | 0 .../helm-hub-registration.component.spec.ts | 33 +++ .../helm-hub-registration.component.ts | 35 +++ .../src/custom/helm/helm.module.ts | 41 +-- .../src/custom/helm/helm.routing.ts | 11 +- .../src/custom/helm/helm.setup.module.ts | 4 + .../monocular-charts-data-source.ts | 25 +- .../monocular-charts-list-config.service.ts | 23 +- ...onocular-repository-list-config.service.ts | 185 -------------- .../monocular-repository-list-source.ts | 65 ----- .../monocular-tab-base.component.html | 2 +- .../monocular-tab-base.component.ts | 4 - .../src/custom/helm/monocular.interceptor.ts | 67 +++++ .../chart-details-usage.component.html | 12 +- .../chart-details-usage.component.ts | 25 +- .../chart-details-versions.component.ts | 6 +- .../chart-details/chart-details.component.ts | 3 +- .../chart-item/chart-item.component.spec.ts | 13 +- .../chart-item/chart-item.component.ts | 5 +- .../custom/helm/monocular/monocular.module.ts | 53 ++++ .../helm/monocular/shared/models/chart.ts | 3 +- .../shared/services/charts.service.ts | 34 ++- .../shared/services/repos.service.ts | 3 +- .../stratos-monocular-providers.helpers.ts | 28 +++ .../monocular/stratos-monocular.helper.ts | 19 ++ .../custom/helm/store/helm.action-builders.ts | 36 ++- .../src/custom/helm/store/helm.actions.ts | 16 +- .../src/custom/helm/store/helm.effects.ts | 180 +++++++++---- .../src/custom/helm/store/helm.types.ts | 22 +- .../repository-tab.component.html | 1 - .../repository-tab.component.spec.ts | 29 --- .../repository-tab.component.ts | 17 -- .../tabs/helm-release-helper.service.ts | 18 +- .../workloads/store/workloads.effects.ts | 4 +- .../release-version-data-source.ts | 24 +- .../release-version-list-config.ts | 41 +-- .../upgrade-release.component.ts | 52 ++-- .../kubernetes/workloads/workloads.routing.ts | 1 + .../datastore/20200902162200_HelmSubtype.go | 21 ++ src/jetstream/passthrough.go | 2 +- .../plugins/kubernetes/install_release.go | 49 ++-- src/jetstream/plugins/metrics/main.go | 2 - src/jetstream/plugins/monocular/main.go | 236 ++++++++++++++++-- 60 files changed, 1127 insertions(+), 622 deletions(-) create mode 100644 src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.html rename src/frontend/packages/suse-extensions/src/custom/helm/{tabs/repository-tab/repository-tab.component.scss => helm-hub-registration/helm-hub-registration.component.scss} (100%) create mode 100644 src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.spec.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.ts delete mode 100644 src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-repository-list-config.service.ts delete mode 100644 src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-repository-list-source.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/helm/monocular.interceptor.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/helm/monocular/monocular.module.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/helm/monocular/stratos-monocular-providers.helpers.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/helm/monocular/stratos-monocular.helper.ts delete mode 100644 src/frontend/packages/suse-extensions/src/custom/helm/tabs/repository-tab/repository-tab.component.html delete mode 100644 src/frontend/packages/suse-extensions/src/custom/helm/tabs/repository-tab/repository-tab.component.spec.ts delete mode 100644 src/frontend/packages/suse-extensions/src/custom/helm/tabs/repository-tab/repository-tab.component.ts create mode 100644 src/jetstream/datastore/20200902162200_HelmSubtype.go diff --git a/src/frontend/packages/core/src/app.module.ts b/src/frontend/packages/core/src/app.module.ts index 840ed60377..64f5ca665b 100644 --- a/src/frontend/packages/core/src/app.module.ts +++ b/src/frontend/packages/core/src/app.module.ts @@ -89,9 +89,6 @@ const storeDebugImports = environment.production ? [] : [ }) class AppStoreDebugModule { } -/** - * `HttpXsrfTokenExtractor` which retrieves the token from a cookie. - */ @NgModule({ declarations: [ diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.ts index e354112437..38d4b1e417 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.ts @@ -1,13 +1,17 @@ import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; -import { first, map } from 'rxjs/operators'; +import { combineLatest, filter, first, map } from 'rxjs/operators'; import { RouterNav } from '../../../../../../store/src/actions/router.actions'; import { GeneralEntityAppState } from '../../../../../../store/src/app-state'; import { entityCatalog } from '../../../../../../store/src/entity-catalog/entity-catalog'; +import { + StratosCatalogEndpointEntity, +} from '../../../../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; import { IStratosEndpointDefinition } from '../../../../../../store/src/entity-catalog/entity-catalog.types'; import { selectSessionData } from '../../../../../../store/src/reducers/auth.reducer'; +import { stratosEntityCatalog } from '../../../../../../store/src/stratos-entity-catalog'; import { BASE_REDIRECT_QUERY } from '../../../../shared/components/stepper/stepper.types'; import { TileConfigManager } from '../../../../shared/components/tile/tile-selector.helpers'; import { ITileConfig, ITileData } from '../../../../shared/components/tile/tile-selector.types'; @@ -17,6 +21,10 @@ interface ICreateEndpointTilesData extends ITileData { parentType: string; } +type EndpointsByType = { + [endpointType: string]: number, +}; + @Component({ selector: 'app-create-endpoint-base-step', templateUrl: './create-endpoint-base-step.component.html', @@ -67,7 +75,7 @@ export class CreateEndpointBaseStepComponent { } // Both A & B are equal. Unlikely. return 0; - } + }; get selectedTile() { return this.pSelectedTile; @@ -83,13 +91,15 @@ export class CreateEndpointBaseStepComponent { })); } } - constructor(public store: Store<GeneralEntityAppState>, ) { + constructor(public store: Store<GeneralEntityAppState>,) { // Need to filter the endpoint types on the tech preview flag this.tileSelectorConfig$ = store.select(selectSessionData()).pipe( + combineLatest(this.getEndpointTypesByCount()), first(), - map(sessionData => { + map(([sessionData, endpointTypesByCount]) => { const techPreviewIsEnabled = sessionData.config.enableTechPreview || false; return entityCatalog.getAllEndpointTypes(techPreviewIsEnabled) + .filter(endpoint => this.filterByEndpointCount(endpoint, endpointTypesByCount)) .sort((endpointA, endpointB) => this.sortEndpointTiles(endpointA.definition, endpointB.definition)) .map(catalogEndpoint => { const endpoint = catalogEndpoint.definition; @@ -112,4 +122,38 @@ export class CreateEndpointBaseStepComponent { ); } + private getEndpointDefinitionKey = (type: string, subType: string): string => type + '_sep_' + subType; + private getEndpointTypesByCount = (): Observable<EndpointsByType> => + stratosEntityCatalog.endpoint.store.getAll.getPaginationService().entities$.pipe( + filter(endpoints => !!endpoints), + map(endpoints => { + const endpointsByType: { [endpointType: string]: number; } = {}; + return endpoints.reduce((res, endpoint) => { + const type = this.getEndpointDefinitionKey(endpoint.cnsi_type, endpoint.sub_type); + if (!res[type]) { + res[type] = 0; + } + res[type]++; + return res; + }, endpointsByType); + }), + ); + private filterByEndpointCount = (endpoint: StratosCatalogEndpointEntity, endpointTypesByCount: EndpointsByType) => { + // No limit applied, always show endpoint + if (typeof endpoint.definition.registeredLimit !== 'number') { + return true; + } + // Zero limit, never show endpoint + if (endpoint.definition.registeredLimit === 0) { + return false; + } + + // Check that the limit is not exceeded by endpoints already registered + const type = endpoint.definition.parentType ? + this.getEndpointDefinitionKey(endpoint.definition.parentType, endpoint.definition.type) : + this.getEndpointDefinitionKey(endpoint.definition.type, ''); + const count = endpointTypesByCount[type] || 0; + return count < endpoint.definition.registeredLimit; + }; + } diff --git a/src/frontend/packages/core/src/shared/components/cards/card-number-metric/card-number-metric.component.scss b/src/frontend/packages/core/src/shared/components/cards/card-number-metric/card-number-metric.component.scss index 78aa3599eb..cc87470846 100644 --- a/src/frontend/packages/core/src/shared/components/cards/card-number-metric/card-number-metric.component.scss +++ b/src/frontend/packages/core/src/shared/components/cards/card-number-metric/card-number-metric.component.scss @@ -26,6 +26,7 @@ font-size: 40px; height: 40px; margin-right: 12px; + text-align: center; width: 40px; } &__label { diff --git a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.scss b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.scss index 84f88ba55f..bbb07aa386 100644 --- a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.scss +++ b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.scss @@ -69,7 +69,6 @@ font-size: 14px; font-weight: normal; margin-right: 8px; - margin-top: -2px; padding: 2px 4px; } } diff --git a/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component.html b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component.html index 075cdf838c..0223537772 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component.html +++ b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component.html @@ -1,5 +1,5 @@ -<mat-card class="meta-card {{status$ && statusBackground? (status$ | async): '' }}" [ngClass]="{'meta-card-pointer': clickAction}" - (click)="clickAction ? clickAction() : null"> +<mat-card class="meta-card {{status$ && statusBackground? (status$ | async): '' }}" + [ngClass]="{'meta-card-pointer': clickAction}" (click)="clickAction ? clickAction() : null"> <div *ngIf="isDeleting$ | async" class="meta-card__deleting-overlay"> <div class="meta-card__deleting-overlay-inner"> <div class="meta-card__deleting-text">Deleting</div> @@ -9,7 +9,8 @@ <app-card-status *ngIf="status$" [status$]="status$"> </app-card-status> <mat-card-header class="meta-card__header" *ngIf="title"> - <div [ngClass]="statusIconByTitle ? 'meta-card__header-container__title--with-icon' : 'meta-card__header-container__title'"> + <div + [ngClass]="statusIconByTitle ? 'meta-card__header-container__title--with-icon' : 'meta-card__header-container__title'"> <div [ngClass]="statusIconByTitle ? 'meta-card__header-container__title__content--with-icon' : 'meta-card__header-container__title__content'"> <ng-container *ngTemplateOutlet="title.content"></ng-container> @@ -18,20 +19,24 @@ <ng-container *ngTemplateOutlet="statusIconTmple"></ng-container> </div> </div> - <app-entity-favorite-star [confirmRemoval]="confirmFavoriteRemoval" class="meta-card__favorite" *ngIf="favorite" [favorite]="favorite"> + <app-entity-favorite-star [confirmRemoval]="confirmFavoriteRemoval" class="meta-card__favorite" *ngIf="favorite" + [favorite]="favorite"> </app-entity-favorite-star> <div class="meta-card__header-container__actions" *ngIf="actionMenu && (showMenu$ | async)" appClickStopPropagation> - <button mat-icon-button class="meta-card__header__button" color="basic" [matMenuTriggerFor]="menu" [disabled]="isDeleting$ | async"> + <button mat-icon-button class="meta-card__header__button" color="basic" [matMenuTriggerFor]="menu" + [disabled]="isDeleting$ | async"> <mat-icon>more_vert</mat-icon> </button> - <mat-menu class="meta-card__header__popup" #menu="matMenu" xPosition="before" > + <mat-menu class="meta-card__header__popup" #menu="matMenu" xPosition="before"> <ng-container *ngFor="let menuItem of actionMenu"> - <button class="meta-card__header__popup__btn" [disabled]="menuItem.disabled | async" mat-menu-item *ngIf="menuItem.can | async" - (click)="menuItem.action()"> - <mat-icon *ngIf="menuItem.icon">{{menuItem.icon}}</mat-icon> - <span>{{menuItem.label}}</span> - </button> - <div *ngIf="menuItem.separator" class="meta-card__header__popup-separator"></div> + <ng-container *ngIf="menuItem.can | async"> + <button *ngIf="!menuItem.separator" class="meta-card__header__popup__btn" + [disabled]="menuItem.disabled | async" mat-menu-item (click)="menuItem.action()"> + <mat-icon *ngIf="menuItem.icon">{{menuItem.icon}}</mat-icon> + <span>{{menuItem.label}}</span> + </button> + <div *ngIf="menuItem.separator" class="meta-card__header__popup-separator"></div> + </ng-container> </ng-container> </mat-menu> </div> @@ -48,6 +53,7 @@ </mat-card-content> </mat-card> <ng-template #statusIconTmple> - <app-application-state-icon *ngIf="statusIcon && status$" [status]="status$ | async" matTooltip="{{statusIconTooltip}}"> + <app-application-state-icon *ngIf="statusIcon && status$" [status]="status$ | async" + matTooltip="{{statusIconTooltip}}"> </app-application-state-icon> </ng-template> \ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component.ts b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component.ts index 59a95244d6..039954d7f2 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component.ts @@ -1,5 +1,5 @@ import { Component, ContentChild, ContentChildren, Input, OnDestroy, QueryList } from '@angular/core'; -import { combineLatest, Observable, of as observableOf, Subscription } from 'rxjs'; +import { combineLatest, Observable, of as observableOf, of, Subscription } from 'rxjs'; import { first, map, tap } from 'rxjs/operators'; import { FavoritesConfigMapper } from '../../../../../../../../store/src/favorite-config-mapper'; @@ -13,7 +13,7 @@ import { MetaCardItemComponent } from '../meta-card-item/meta-card-item.componen import { MetaCardTitleComponent } from '../meta-card-title/meta-card-title.component'; -export function createMetaCardMenuItemSeparator() { +export function createMetaCardMenuItemSeparator(): MenuItem { return { label: '-', separator: true, @@ -86,7 +86,7 @@ export class MetaCardComponent implements OnDestroy { this.pActionMenu = actionMenu.map(menuItem => { if (!menuItem.can) { menuItem.separator = menuItem.label === '-'; - menuItem.can = observableOf(!menuItem.separator); + menuItem.can = of(true); } if (!menuItem.disabled) { menuItem.disabled = observableOf(false); @@ -94,7 +94,10 @@ export class MetaCardComponent implements OnDestroy { return menuItem; }); - this.showMenu$ = combineLatest(actionMenu.map(menuItem => menuItem.can)).pipe( + const nonSeparators = actionMenu + .filter(menuItem => !menuItem.separator) + .map(menuItem => menuItem.can); + this.showMenu$ = combineLatest(nonSeparators).pipe( map(cans => cans.some(can => can)) ); } diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.ts b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.ts index dfdd1548a2..82bb85e5a4 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.ts @@ -93,13 +93,18 @@ export class EndpointCardComponent extends CardCell<EndpointModel> implements On @Input('dataSource') set dataSource(ds: BaseEndpointsDataSource) { this.pDs = ds; + // Don't show card menu if the ds only provides a single endpoint type (for instance the cf endpoint page) if (ds && !ds.dsEndpointType && !this.cardMenu) { - this.cardMenu = this.endpointListHelper.endpointActions().map(endpointAction => ({ - label: endpointAction.label, - action: () => endpointAction.action(this.pRow), - can: endpointAction.createVisible(this.rowObs) - })); + this.cardMenu = this.endpointListHelper.endpointActions(true).map(endpointAction => { + const separator = endpointAction.label === '-'; + return { + label: endpointAction.label, + action: () => endpointAction.action(this.pRow), + can: endpointAction.createVisible ? endpointAction.createVisible(this.rowObs) : null, + separator + }; + }); // Add a copy address to clipboard this.cardMenu.push(createMetaCardMenuItemSeparator()); diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-list.helpers.ts b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-list.helpers.ts index e1ebaabf50..41347976e7 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-list.helpers.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-list.helpers.ts @@ -1,7 +1,7 @@ import { ComponentFactoryResolver, ComponentRef, Injectable, ViewContainerRef } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; -import { combineLatest, Observable } from 'rxjs'; +import { combineLatest, Observable, of } from 'rxjs'; import { map, pairwise } from 'rxjs/operators'; import { RouterNav } from '../../../../../../../store/src/actions/router.actions'; @@ -19,6 +19,7 @@ import { import { SnackBarService } from '../../../../services/snackbar.service'; import { ConfirmationDialogConfig } from '../../../confirmation-dialog.config'; import { ConfirmationDialogService } from '../../../confirmation-dialog.service'; +import { createMetaCardMenuItemSeparator } from '../../list-cards/meta-card/meta-card-base/meta-card.component'; import { IListAction } from '../../list.component.types'; import { TableCellCustom } from '../../list.types'; @@ -37,6 +38,27 @@ function isEndpointListDetailsComponent(obj: any): EndpointListDetailsComponent return obj ? obj.isEndpointListDetailsComponent ? obj as EndpointListDetailsComponent : null : null; } +/** + * Combine the result of all createVisibles functions for the given actions + */ +function combineCreateVisibles( + customActions: IListAction<EndpointModel>[] +): (row$: Observable<EndpointModel>) => Observable<boolean> { + const createVisiblesFns = customActions + .map(action => action.createVisible) + .filter(createVisible => !!createVisible); + if (createVisiblesFns.length === 0) { + return () => of(false); + } else { + return (row$: Observable<EndpointModel>) => { + const createVisibles = createVisiblesFns.map(createVisible => createVisible(row$)); + return combineLatest(createVisibles).pipe( + map(allRes => allRes.some(res => res)) + ); + }; + } +} + @Injectable() export class EndpointListHelper { constructor( @@ -48,7 +70,23 @@ export class EndpointListHelper { private snackBarService: SnackBarService, ) { } - endpointActions(): IListAction<EndpointModel>[] { + endpointActions(includeSeparators = false): IListAction<EndpointModel>[] { + // Add any additional actions that are per endpoint type + const customActions = entityCatalog.getAllEndpointTypes() + .map(endpoint => endpoint.definition.endpointListActions) + .filter(endpointListActions => !!endpointListActions) + .map(endpointListActions => endpointListActions(this.store)) + .reduce((res, actions) => res.concat(actions), []); + + if (includeSeparators && customActions.length) { + // Only show the separator if we have custom actions to separate AND at least one is visible + const createVisibleFn = combineCreateVisibles(customActions); + customActions.splice(0, 0, { + ...createMetaCardMenuItemSeparator(), + createVisible: createVisibleFn + }); + } + return [ { action: (item) => { @@ -126,7 +164,8 @@ export class EndpointListHelper { label: 'Edit endpoint', description: 'Edit the endpoint', createVisible: () => this.currentUserPermissionsService.can(StratosCurrentUserPermissions.ENDPOINT_REGISTER) - } + }, + ...customActions ]; } diff --git a/src/frontend/packages/core/src/shared/components/tile-selector/tile-selector.component.ts b/src/frontend/packages/core/src/shared/components/tile-selector/tile-selector.component.ts index 49c3e95cf2..18c8c144de 100644 --- a/src/frontend/packages/core/src/shared/components/tile-selector/tile-selector.component.ts +++ b/src/frontend/packages/core/src/shared/components/tile-selector/tile-selector.component.ts @@ -1,4 +1,5 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; + import { ITileConfig } from '../tile/tile-selector.types'; @@ -12,6 +13,9 @@ export class TileSelectorComponent { public hiddenOptions: ITileConfig[] = []; public showingMore = false; @Input() set options(options: ITileConfig[]) { + if (!options) { + return; + } const groupedOptions = options.reduce((grouped, option) => { if (option.hidden) { grouped.hidden.push(option); @@ -20,9 +24,9 @@ export class TileSelectorComponent { } return grouped; }, { - show: [], - hidden: [] - }); + show: [], + hidden: [] + }); this.pOptions = groupedOptions.show; this.hiddenOptions = groupedOptions.hidden; } diff --git a/src/frontend/packages/core/xsrf.module.ts b/src/frontend/packages/core/xsrf.module.ts index 5d875f273f..b1456f72eb 100644 --- a/src/frontend/packages/core/xsrf.module.ts +++ b/src/frontend/packages/core/xsrf.module.ts @@ -13,6 +13,9 @@ import { tap } from 'rxjs/operators'; const STRATOS_XSRF_HEADER_NAME = 'X-XSRF-Token'; +/** + * `HttpXsrfTokenExtractor` which retrieves the token from a cookie. + */ @Injectable() export class HttpXsrfHeaderExtractor implements HttpXsrfTokenExtractor { diff --git a/src/frontend/packages/store/src/entity-catalog/entity-catalog.ts b/src/frontend/packages/store/src/entity-catalog/entity-catalog.ts index 5565256852..224f05b471 100644 --- a/src/frontend/packages/store/src/entity-catalog/entity-catalog.ts +++ b/src/frontend/packages/store/src/entity-catalog/entity-catalog.ts @@ -176,7 +176,7 @@ class EntityCatalog { return Array.from(this.endpoints.values()); } - public getAllEndpointTypes(techPreviewEnabled = false) { + public getAllEndpointTypes(techPreviewEnabled = false): StratosCatalogEndpointEntity[] { const baseEndpoints = Array.from(this.endpoints.values()) .filter(item => !item.definition.techPreview || item.definition.techPreview && techPreviewEnabled); return baseEndpoints.reduce((allEndpoints, baseEndpoint) => { diff --git a/src/frontend/packages/store/src/entity-catalog/entity-catalog.types.ts b/src/frontend/packages/store/src/entity-catalog/entity-catalog.types.ts index fa574a1754..b0dfe4151f 100644 --- a/src/frontend/packages/store/src/entity-catalog/entity-catalog.types.ts +++ b/src/frontend/packages/store/src/entity-catalog/entity-catalog.types.ts @@ -1,7 +1,8 @@ import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; -import { GeneralEntityAppState } from '../app-state'; +import { IListAction } from '../../../core/src/shared/components/list/list.component.types'; +import { AppState, GeneralEntityAppState } from '../app-state'; import { ApiErrorMessageHandler, EntitiesFetchHandler, @@ -119,6 +120,10 @@ export interface IStratosEndpointDefinition<T = EntityCatalogSchemas | EntitySch readonly tokenSharing?: boolean; readonly urlValidation?: boolean; readonly unConnectable?: boolean; + /** + * How many endpoints of this type can be registered, 0 - many + */ + readonly registeredLimit?: number; /** * Indicates if this endpoint type is in tech preview and should only be shown when tech preview mode is enabled */ @@ -146,11 +151,16 @@ export interface IStratosEndpointDefinition<T = EntityCatalogSchemas | EntitySch /** * Allows the endpoint to fetch user roles, for example when the user loads Stratos or connects an endpoint of this type */ - readonly userRolesFetch?: EntityUserRolesFetch + readonly userRolesFetch?: EntityUserRolesFetch; /** * Allows the user roles to be stored, updated and removed in the current user permissions section of the store */ - readonly userRolesReducer?: EntityUserRolesReducer + readonly userRolesReducer?: EntityUserRolesReducer; + /** + * A list of actions that will be displayed in the endpoints lists + * Note - These should be restricted by type + */ + readonly endpointListActions?: (store: Store<AppState>) => IListAction<EndpointModel>[]; } export interface StratosEndpointExtensionDefinition extends Omit<IStratosEndpointDefinition, 'schema'> { } @@ -206,7 +216,7 @@ export interface IStratosEntityBuilder<T extends IEntityMetadata, Y = any> { getLines?(): EntityRowBuilder<T>[]; getSubTypeLabels?(entityMetadata: T): { singular: string, - plural: string + plural: string, }; /** * Actions that don't effect an individual entity i.e. create new diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/chart-view/monocular.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/chart-view/monocular.component.ts index fb74ce9981..f8cb93cdff 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/chart-view/monocular.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/chart-view/monocular.component.ts @@ -1,10 +1,17 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; +import { createMonocularProviders } from '../monocular/stratos-monocular-providers.helpers'; +import { getMonocularEndpoint } from '../monocular/stratos-monocular.helper'; + + @Component({ selector: 'app-monocular', templateUrl: './monocular.component.html', styleUrls: ['./monocular.component.scss'], + providers: [ + ...createMonocularProviders() + ] }) export class MonocularChartViewComponent implements OnInit { @@ -27,7 +34,7 @@ export class MonocularChartViewComponent implements OnInit { if (!!parts.version) { breadcrumbs.push( - { value: this.title, routerLink: `/monocular/charts/${parts.repo}/${parts.chartName}` } + { value: this.title, routerLink: `/monocular/charts/${getMonocularEndpoint(this.route)}/${parts.repo}/${parts.chartName}` } ); this.title = parts.version; } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.ts index d830c232f6..c57b1175cf 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.ts @@ -16,12 +16,17 @@ import { kubeEntityCatalog } from '../../kubernetes/kubernetes-entity-catalog'; import { KUBERNETES_ENDPOINT_TYPE } from '../../kubernetes/kubernetes-entity-factory'; import { KubernetesNamespace } from '../../kubernetes/store/kube.types'; import { helmEntityCatalog } from '../helm-entity-catalog'; +import { createMonocularProviders } from '../monocular/stratos-monocular-providers.helpers'; +import { getMonocularEndpoint, stratosMonocularEndpointGuid } from '../monocular/stratos-monocular.helper'; import { HelmInstallValues } from '../store/helm.types'; @Component({ selector: 'app-create-release', templateUrl: './create-release.component.html', styleUrls: ['./create-release.component.scss'], + providers: [ + ...createMonocularProviders() + ] }) export class CreateReleaseComponent implements OnInit, OnDestroy { @@ -61,7 +66,7 @@ export class CreateReleaseComponent implements OnInit, OnDestroy { private confirmDialog: ConfirmationDialogService, ) { const chart = this.route.snapshot.params; - this.cancelUrl = `/monocular/charts/${chart.repo}/${chart.chartName}/${chart.version}`; + this.cancelUrl = `/monocular/charts/${getMonocularEndpoint(this.route)}/${chart.repo}/${chart.chartName}/${chart.version}`; this.setupDetailsStep(); @@ -207,13 +212,13 @@ export class CreateReleaseComponent implements OnInit, OnDestroy { // this.overridesYamlAutosize.resizeToFitContent(true); this.overridesYamlTextArea.nativeElement.focus(); }, 1); - } + }; submit: StepOnNextFunction = () => { return this.createNamespace().pipe( switchMap(createRes => createRes.success ? this.installChart() : of(createRes)) ); - } + }; createNamespace(): Observable<StepOnNextResult> { if (!this.details.controls.createNamespace.value || this.createdNamespace) { @@ -245,11 +250,17 @@ export class CreateReleaseComponent implements OnInit, OnDestroy { } installChart(): Observable<StepOnNextResult> { + const endpoint = getMonocularEndpoint(this.route, null, null); // Build the request body const values: HelmInstallValues = { ...this.details.value, ...this.overrides.value, - chart: this.route.snapshot.params + chart: { + chartName: this.route.snapshot.params.chartName, + repo: this.route.snapshot.params.repo, + version: this.route.snapshot.params.version, + }, + monocularEndpoint: endpoint === stratosMonocularEndpointGuid ? null : endpoint }; // Make the request diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-factory.ts b/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-factory.ts index 302dd8a060..6cdf2402a9 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-factory.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-factory.ts @@ -11,9 +11,11 @@ export const getMonocularChartId = (entity: MonocularChart) => entity.id; export const getHelmVersionId = (entity: HelmVersion) => entity.endpointId; export const HELM_ENDPOINT_TYPE = 'helm'; +export const HELM_REPO_ENDPOINT_TYPE = 'repo'; +export const HELM_HUB_ENDPOINT_TYPE = 'hub'; const entityCache: { - [key: string]: EntitySchema + [key: string]: EntitySchema, } = {}; export class HelmEntitySchema extends EntitySchema { @@ -49,7 +51,7 @@ entityCache[helmVersionsEntityType] = new HelmEntitySchema( entityCache[monocularChartVersionsEntityType] = new HelmEntitySchema( monocularChartVersionsEntityType, {}, - { idAttribute: getHelmVersionId } + { idAttribute: getMonocularChartId } ); export function helmEntityFactory(key: string): EntitySchema { diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-generator.ts b/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-generator.ts index c9ef3d8afa..8ac9945a41 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-generator.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-generator.ts @@ -1,18 +1,28 @@ +import { Store } from '@ngrx/store'; +import { of } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { IListAction } from '../../../../core/src/shared/components/list/list.component.types'; +import { AppState } from '../../../../store/src/app-state'; import { StratosBaseCatalogEntity, StratosCatalogEndpointEntity, StratosCatalogEntity, } from '../../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; import { StratosEndpointExtensionDefinition } from '../../../../store/src/entity-catalog/entity-catalog.types'; +import { EndpointModel } from '../../../../store/src/types/endpoint.types'; import { IFavoriteMetadata } from '../../../../store/src/types/user-favorites.types'; import { helmEntityCatalog } from './helm-entity-catalog'; import { HELM_ENDPOINT_TYPE, + HELM_HUB_ENDPOINT_TYPE, + HELM_REPO_ENDPOINT_TYPE, helmEntityFactory, helmVersionsEntityType, monocularChartsEntityType, monocularChartVersionsEntityType, } from './helm-entity-factory'; +import { HelmHubRegistrationComponent } from './helm-hub-registration/helm-hub-registration.component'; import { HelmChartActionBuilders, helmChartActionBuilders, @@ -25,18 +35,52 @@ import { HelmVersion, MonocularChart, MonocularVersion } from './store/helm.type export function generateHelmEntities(): StratosBaseCatalogEntity[] { + const helmRepoRenderPriority = 10; const endpointDefinition: StratosEndpointExtensionDefinition = { type: HELM_ENDPOINT_TYPE, - label: 'Helm Repository', - labelPlural: 'Helm Repositories', - icon: 'helm', - iconFont: 'stratos-icons', logoUrl: '/core/assets/custom/helm.svg', - urlValidation: undefined, - unConnectable: true, - techPreview: false, authTypes: [], - renderPriority: 10, + registeredLimit: 0, + icon: 'helm', + iconFont: 'stratos-icons', + label: 'Helm', + labelPlural: 'Helms', + subTypes: [ + { + type: HELM_REPO_ENDPOINT_TYPE, + label: 'Helm Repository', + labelPlural: 'Helm Repositories', + logoUrl: '/core/assets/custom/helm.svg', + urlValidation: undefined, + unConnectable: true, + techPreview: false, + authTypes: [], + endpointListActions: (store: Store<AppState>): IListAction<EndpointModel>[] => { + return [{ + action: (item: EndpointModel) => helmEntityCatalog.chart.api.synchronise(item), + label: 'Synchronize', + description: '', + createVisible: row => row.pipe( + map(item => item.cnsi_type === HELM_ENDPOINT_TYPE && item.sub_type === HELM_REPO_ENDPOINT_TYPE) + ), + createEnabled: () => of(true) + }]; + }, + renderPriority: helmRepoRenderPriority, + registeredLimit: null, + }, + { + type: HELM_HUB_ENDPOINT_TYPE, + label: 'Helm Hub', + labelPlural: 'Helm Hubs', + authTypes: [], + unConnectable: true, + logoUrl: '/core/assets/custom/helm.svg', + renderPriority: helmRepoRenderPriority + 1, + registrationComponent: HelmHubRegistrationComponent, + registeredLimit: 1, + }, + ], }; return [ @@ -50,7 +94,7 @@ export function generateHelmEntities(): StratosBaseCatalogEntity[] { function generateEndpointEntity(endpointDefinition: StratosEndpointExtensionDefinition) { helmEntityCatalog.endpoint = new StratosCatalogEndpointEntity( endpointDefinition, - metadata => `/monocular/repos/${metadata.guid}`, + metadata => `/monocular/charts`, ); return helmEntityCatalog.endpoint; } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.html b/src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.html new file mode 100644 index 0000000000..fe749d24f3 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.html @@ -0,0 +1,10 @@ +<app-steppers cancel="/endpoints"> + <app-step title="" [valid]="true" [onNext]="onNext" finishButtonText="Register"> + <div> + <p>Helm Hub is a public repository for Helm Charts. </p> + <p>To browse and install these charts you need to register Helm Hub as an endpoint by clicking `Register` below. + </p> + <p>To disable Helm Hub simply unregister the Helm Hub endpoint</p> + </div> + </app-step> +</app-steppers> \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/tabs/repository-tab/repository-tab.component.scss b/src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/tabs/repository-tab/repository-tab.component.scss rename to src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.spec.ts new file mode 100644 index 0000000000..df9c494821 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.spec.ts @@ -0,0 +1,33 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EndpointsService } from '../../../../../core/src/core/endpoints.service'; +import { UserService } from '../../../../../core/src/core/user.service'; +import { BaseTestModules } from '../../../../../core/test-framework/core-test.helper'; +import { HelmHubRegistrationComponent } from './helm-hub-registration.component'; + +describe('HelmHubRegistrationComponent', () => { + let component: HelmHubRegistrationComponent; + let fixture: ComponentFixture<HelmHubRegistrationComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [...BaseTestModules], + declarations: [HelmHubRegistrationComponent], + providers: [ + EndpointsService, + UserService + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HelmHubRegistrationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.ts new file mode 100644 index 0000000000..2b1a2ceeb3 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.ts @@ -0,0 +1,35 @@ +import { Component } from '@angular/core'; +import { filter, map, pairwise } from 'rxjs/operators'; + +import { StepOnNextFunction } from '../../../../../core/src/shared/components/stepper/step/step.component'; +import { ActionState } from '../../../../../store/src/reducers/api-request-reducer/types'; +import { stratosEntityCatalog } from '../../../../../store/src/stratos-entity-catalog'; +import { HELM_ENDPOINT_TYPE, HELM_HUB_ENDPOINT_TYPE } from '../helm-entity-factory'; + +@Component({ + selector: 'app-helm-hub-registration', + templateUrl: './helm-hub-registration.component.html', + styleUrls: ['./helm-hub-registration.component.scss'] +}) +export class HelmHubRegistrationComponent { + + onNext: StepOnNextFunction = () => { + return stratosEntityCatalog.endpoint.api.register<ActionState>( + HELM_ENDPOINT_TYPE, + HELM_HUB_ENDPOINT_TYPE, + 'Helm Hub', + 'https://hub.helm.sh/api', + false + ).pipe( + pairwise(), + filter(([oldV, newV]) => oldV.busy && !newV.busy), + map(([, newV]) => newV), + map(state => ({ + success: !state.error, + message: state.message, + redirect: !state.error + })) + ); + }; + +} diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm.module.ts b/src/frontend/packages/suse-extensions/src/custom/helm/helm.module.ts index d03d15582b..0bba8cacac 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm.module.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/helm.module.ts @@ -8,27 +8,8 @@ import { CreateReleaseModule } from './create-release/create-release.module'; import { HelmRoutingModule } from './helm.routing'; import { MonocularChartCardComponent } from './list-types/monocular-chart-card/monocular-chart-card.component'; import { MonocularTabBaseComponent } from './monocular-tab-base/monocular-tab-base.component'; -import { ChartDetailsInfoComponent } from './monocular/chart-details/chart-details-info/chart-details-info.component'; -import { ChartDetailsReadmeComponent } from './monocular/chart-details/chart-details-readme/chart-details-readme.component'; -import { ChartDetailsUsageComponent } from './monocular/chart-details/chart-details-usage/chart-details-usage.component'; -import { - ChartDetailsVersionsComponent, -} from './monocular/chart-details/chart-details-versions/chart-details-versions.component'; -import { ChartDetailsComponent } from './monocular/chart-details/chart-details.component'; -import { ChartIndexComponent } from './monocular/chart-index/chart-index.component'; -import { ChartItemComponent } from './monocular/chart-item/chart-item.component'; -import { ChartListComponent } from './monocular/chart-list/chart-list.component'; -import { ChartsComponent } from './monocular/charts/charts.component'; -import { ListFiltersComponent } from './monocular/list-filters/list-filters.component'; -import { ListItemComponent } from './monocular/list-item/list-item.component'; -import { LoaderComponent } from './monocular/loader/loader.component'; -import { PanelComponent } from './monocular/panel/panel.component'; -import { ChartsService } from './monocular/shared/services/charts.service'; -import { ConfigService } from './monocular/shared/services/config.service'; -import { MenuService } from './monocular/shared/services/menu.service'; -import { ReposService } from './monocular/shared/services/repos.service'; +import { MonocularModule } from './monocular/monocular.module'; import { CatalogTabComponent } from './tabs/catalog-tab/catalog-tab.component'; -import { RepositoryTabComponent } from './tabs/repository-tab/repository-tab.component'; @NgModule({ imports: [ @@ -37,33 +18,15 @@ import { RepositoryTabComponent } from './tabs/repository-tab/repository-tab.com SharedModule, HelmRoutingModule, CreateReleaseModule, + MonocularModule ], declarations: [ - PanelComponent, - ChartIndexComponent, - ChartListComponent, - ChartItemComponent, - ListItemComponent, - ListFiltersComponent, - LoaderComponent, - ChartsComponent, - ChartIndexComponent, - ChartDetailsComponent, - ChartDetailsUsageComponent, - ChartDetailsVersionsComponent, - ChartDetailsReadmeComponent, - ChartDetailsInfoComponent, MonocularTabBaseComponent, - RepositoryTabComponent, CatalogTabComponent, MonocularChartCardComponent, MonocularChartViewComponent, ], providers: [ - ChartsService, - ConfigService, - MenuService, - ReposService ], entryComponents: [ MonocularChartCardComponent, diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm.routing.ts b/src/frontend/packages/suse-extensions/src/custom/helm/helm.routing.ts index 0f67b21db6..37dc491734 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm.routing.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/helm.routing.ts @@ -5,7 +5,6 @@ import { MonocularChartViewComponent } from './chart-view/monocular.component'; import { CreateReleaseComponent } from './create-release/create-release.component'; import { MonocularTabBaseComponent } from './monocular-tab-base/monocular-tab-base.component'; import { CatalogTabComponent } from './tabs/catalog-tab/catalog-tab.component'; -import { RepositoryTabComponent } from './tabs/repository-tab/repository-tab.component'; const monocular: Routes = [ { @@ -15,14 +14,12 @@ const monocular: Routes = [ { path: '', redirectTo: 'charts', pathMatch: 'full' }, { path: 'charts', component: CatalogTabComponent }, { path: 'charts/:repo', component: CatalogTabComponent }, - { path: 'repos', component: RepositoryTabComponent }, - { path: 'repos/:guid', component: RepositoryTabComponent }, ] }, - { pathMatch: 'full', path: 'charts/:repo/:chartName/:version', component: MonocularChartViewComponent }, - { path: 'charts/:repo/:chartName', component: MonocularChartViewComponent }, - { pathMatch: 'full', path: 'install/:repo/:chartName/:version', component: CreateReleaseComponent }, - { pathMatch: 'full', path: 'install/:repo/:chartName', component: CreateReleaseComponent }, + { pathMatch: 'full', path: 'charts/:endpoint/:repo/:chartName/:version', component: MonocularChartViewComponent }, + { path: 'charts/:endpoint/:repo/:chartName', component: MonocularChartViewComponent }, + { pathMatch: 'full', path: 'install/:endpoint/:repo/:chartName/:version', component: CreateReleaseComponent }, + { path: 'install/:endpoint/:repo/:chartName', component: CreateReleaseComponent }, ]; @NgModule({ diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm.setup.module.ts b/src/frontend/packages/suse-extensions/src/custom/helm/helm.setup.module.ts index 3070b56567..3a57d47e51 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm.setup.module.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/helm.setup.module.ts @@ -11,6 +11,7 @@ import { EntityCatalogModule } from '../../../../store/src/entity-catalog.module import { EndpointHealthCheck } from '../../../../store/src/entity-catalog/entity-catalog.types'; import { HELM_ENDPOINT_TYPE } from './helm-entity-factory'; import { generateHelmEntities } from './helm-entity-generator'; +import { HelmHubRegistrationComponent } from './helm-hub-registration/helm-hub-registration.component'; import { HelmStoreModule } from './helm.store.module'; @NgModule({ @@ -20,6 +21,9 @@ import { HelmStoreModule } from './helm.store.module'; CommonModule, SharedModule, HelmStoreModule, + ], + declarations: [ + HelmHubRegistrationComponent ] }) export class HelmSetupModule { diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-charts-data-source.ts b/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-charts-data-source.ts index 45e3929eed..00d04b583f 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-charts-data-source.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-charts-data-source.ts @@ -2,7 +2,8 @@ import { Store } from '@ngrx/store'; import { ListDataSource } from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; import { IListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; -import { AppState } from '../../../../../store/src/app-state'; +import { AppState, IRequestEntityTypeState } from '../../../../../store/src/app-state'; +import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; import { PaginationEntityState } from '../../../../../store/src/types/pagination.types'; import { helmEntityCatalog } from '../helm-entity-catalog'; import { MonocularChart } from '../store/helm.types'; @@ -11,7 +12,8 @@ export class MonocularChartsDataSource extends ListDataSource<MonocularChart> { constructor( store: Store<AppState>, - listConfig: IListConfig<MonocularChart> + listConfig: IListConfig<MonocularChart>, + endpoints: IRequestEntityTypeState<EndpointModel> ) { const action = helmEntityCatalog.chart.actions.getMultiple(); super({ @@ -22,13 +24,18 @@ export class MonocularChartsDataSource extends ListDataSource<MonocularChart> { paginationKey: action.paginationKey, isLocal: true, listConfig, - transformEntities: [{ type: 'filter', field: 'name' }, - (entities: MonocularChart[], paginationState: PaginationEntityState) => { - const repository = paginationState.clientPagination.filter.items.repository; - return entities.filter(e => { - return !(repository && repository !== e.attributes.repo.name); - }); - } + transformEntities: [ + { type: 'filter', field: 'name' }, + (entities: MonocularChart[], paginationState: PaginationEntityState) => { + const repository = paginationState.clientPagination.filter.items.repository; + if (!repository) { + return entities; + } + return entities.filter(e => e.monocularEndpointId ? + repository === endpoints[e.monocularEndpointId].name : + repository === e.attributes.repo.name + ); + } ] }); } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-charts-list-config.service.ts b/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-charts-list-config.service.ts index 5585990f79..cd41a26b0c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-charts-list-config.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-charts-list-config.service.ts @@ -14,13 +14,14 @@ import { import { ListView } from '../../../../../store/src/actions/list.actions'; import { AppState } from '../../../../../store/src/app-state'; import { defaultHelmKubeListPageSize } from '../../kubernetes/list-types/kube-helm-list-types'; +import { HELM_ENDPOINT_TYPE } from '../helm-entity-factory'; import { MonocularChart } from '../store/helm.types'; import { MonocularChartCardComponent } from './monocular-chart-card/monocular-chart-card.component'; import { MonocularChartsDataSource } from './monocular-charts-data-source'; @Injectable() export class MonocularChartsListConfig implements IListConfig<MonocularChart> { - AppsDataSource: MonocularChartsDataSource; + dataSource: MonocularChartsDataSource; isLocal = true; multiFilterConfigs: IListMultiFilterConfig[]; @@ -74,26 +75,36 @@ export class MonocularChartsListConfig implements IListConfig<MonocularChart> { noEntries: 'There are no charts' }; + private initialised: Observable<boolean>; + constructor( store: Store<AppState>, private endpointsService: EndpointsService, private route: ActivatedRoute, ) { - this.AppsDataSource = new MonocularChartsDataSource(store, this); + + this.initialised = endpointsService.endpoints$.pipe( + filter(endpoints => !!endpoints), + map(endpoints => { + this.dataSource = new MonocularChartsDataSource(store, this, endpoints); + return true; + }), + ); } getGlobalActions = () => []; getMultiActions = () => []; getSingleActions = () => []; getColumns = () => this.columns; - getDataSource = () => this.AppsDataSource; + getDataSource = () => this.dataSource; getMultiFiltersConfigs = () => [this.createRepositoryFilterConfig()]; + getInitialised = () => this.initialised; private createRepositoryFilterConfig(): IListMultiFilterConfig { return { key: 'repository', - label: 'Repository', - allLabel: 'All Repositories', + label: 'Source', + allLabel: 'All Sources', list$: this.helmRepositories(), loading$: observableOf(false), select: new BehaviorSubject(this.route.snapshot.params.repo) @@ -105,7 +116,7 @@ export class MonocularChartsListConfig implements IListConfig<MonocularChart> { map(endpoints => { const repos = []; Object.values(endpoints).forEach(ep => { - if (ep.cnsi_type === 'helm') { + if (ep.cnsi_type === HELM_ENDPOINT_TYPE) { repos.push({ label: ep.name, item: ep.name, value: ep.name }); } }); diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-repository-list-config.service.ts b/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-repository-list-config.service.ts deleted file mode 100644 index 654ec79973..0000000000 --- a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-repository-list-config.service.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { HttpClient } from '@angular/common/http'; -import { Injectable, NgZone } from '@angular/core'; -import { MatSnackBar } from '@angular/material/snack-bar'; -import { ActivatedRoute } from '@angular/router'; -import { Store } from '@ngrx/store'; -import { UnregisterEndpoint } from 'frontend/packages/store/src/actions/endpoint.actions'; -import { entityCatalog } from 'frontend/packages/store/src/entity-catalog/entity-catalog'; -import { selectDeletionInfo } from 'frontend/packages/store/src/selectors/api.selectors'; -import { of as observableOf } from 'rxjs'; -import { pairwise } from 'rxjs/operators'; - -import { CurrentUserPermissionsService } from '../../../../../core/src/core/permissions/current-user-permissions.service'; -import { StratosCurrentUserPermissions } from '../../../../../core/src/core/permissions/stratos-user-permissions.checker'; -import { environment } from '../../../../../core/src/environments/environment'; -import { ConfirmationDialogConfig } from '../../../../../core/src/shared/components/confirmation-dialog.config'; -import { ConfirmationDialogService } from '../../../../../core/src/shared/components/confirmation-dialog.service'; -import { ITableColumn } from '../../../../../core/src/shared/components/list/list-table/table.types'; -import { - EndpointCardComponent, -} from '../../../../../core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component'; -import { - TableCellEndpointStatusComponent, -} from '../../../../../core/src/shared/components/list/list-types/endpoint/table-cell-endpoint-status/table-cell-endpoint-status.component'; -import { - IListAction, - IListConfig, - ListViewTypes, -} from '../../../../../core/src/shared/components/list/list.component.types'; -import { AppState } from '../../../../../store/src/app-state'; -import { getFullEndpointApiUrl } from '../../../../../store/src/endpoint-utils'; -import { endpointEntityType, STRATOS_ENDPOINT_TYPE } from '../../../../../store/src/helpers/stratos-entity-factory'; -import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; -import { InternalEventMonitorFactory } from '../../../../../store/src/monitors/internal-event-monitor.factory'; -import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; -import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; -import { defaultHelmKubeListPageSize } from '../../kubernetes/list-types/kube-helm-list-types'; -import { MonocularRepositoryDataSource } from './monocular-repository-list-source'; - -@Injectable() -export class MonocularRepositoryListConfig implements IListConfig<EndpointModel> { - isLocal = true; - dataSource: MonocularRepositoryDataSource; - viewType = ListViewTypes.TABLE_ONLY; - cardComponent = EndpointCardComponent; - text = { - title: '', - filter: 'Filter Repositories', - noEntries: 'There are no repositories' - }; - pageSizeOptions = defaultHelmKubeListPageSize; - enableTextFilter = true; - columns: ITableColumn<EndpointModel>[] = [ - { - columnId: 'name', - headerCell: () => 'Name', - cellDefinition: { - valuePath: 'name' - }, - sort: { - type: 'sort', - orderKey: 'name', - field: 'name' - }, - cellFlex: '2' - }, - { - columnId: 'address', - headerCell: () => 'Address', - cellDefinition: { - getValue: getFullEndpointApiUrl - }, - sort: { - type: 'sort', - orderKey: 'address', - field: 'api_endpoint.Host' - }, - cellFlex: '7' - }, - { - columnId: 'status', - headerCell: () => 'Status', - cellComponent: TableCellEndpointStatusComponent, - cellConfig: { - showLabel: true - }, - cellFlex: '2' - }, - ]; - - private endpointEntityKey = entityCatalog.getEntityKey(STRATOS_ENDPOINT_TYPE, endpointEntityType); - - private listActionSyncRepository: IListAction<EndpointModel> = { - action: (item: EndpointModel) => { - const requestArgs = { - headers: null, - params: null - }; - const proxyAPIVersion = environment.proxyAPIVersion; - const url = `/pp/${proxyAPIVersion}/chartrepos/${item.guid}`; - const req = this.httpClient.post(url, requestArgs); - req.subscribe(ok => { - this.snackBar.open('Helm Repository synchronization started', 'Dismiss', { duration: 3000 }); - // Refresh repository status - this.dataSource.refresh(); - }, err => { - this.snackBar.open(`Failed to Synchronize Helm Repository '${item.name}'`, 'Dismiss', { duration: 5000 }); - }); - return req; - }, - label: 'Synchronize', - description: '', - createVisible: () => observableOf(true), - createEnabled: () => observableOf(true) - }; - - private deleteRepository: IListAction<EndpointModel> = { - action: (item) => { - const confirmation = new ConfirmationDialogConfig( - 'Delete Helm Repository', - `Are you sure you want to delete repository '${item.name}'?`, - 'Delete', - true - ); - this.confirmDialog.open(confirmation, () => { - this.store.dispatch(new UnregisterEndpoint(item.guid, item.cnsi_type)); - this.handleDeleteAction(item, ([oldVal, newVal]) => { - this.snackBar.open(`Delete Helm Repository '${item.name}'`, 'Dismiss', { duration: 3000 }); - }); - }); - }, - label: 'Delete', - description: 'Delete Helm Repository', - createVisible: () => this.currentUserPermissionsService.can(StratosCurrentUserPermissions.ENDPOINT_REGISTER) - }; - - private handleDeleteAction(item, handleChange) { - this.handleAction(selectDeletionInfo( - this.endpointEntityKey, - item.guid, - ), handleChange); - } - - private handleAction(storeSelect, handleChange) { - const disSub = this.store.select(storeSelect).pipe( - pairwise()) - .subscribe(([oldVal, newVal]) => { - if (!newVal.error && (oldVal.busy && !newVal.busy)) { - handleChange([oldVal, newVal]); - disSub.unsubscribe(); - } - }); - } - - - constructor( - public store: Store<AppState>, - public activatedRoute: ActivatedRoute, - paginationMonitorFactory: PaginationMonitorFactory, - entityMonitorFactory: EntityMonitorFactory, - internalEventMonitorFactory: InternalEventMonitorFactory, - ngZone: NgZone, - public httpClient: HttpClient, - public snackBar: MatSnackBar, - public confirmDialog: ConfirmationDialogService, - public currentUserPermissionsService: CurrentUserPermissionsService, - ) { - const highlighted = activatedRoute.snapshot.params.guid; - this.dataSource = new MonocularRepositoryDataSource( - this.store, - this, - highlighted, - paginationMonitorFactory, - entityMonitorFactory, - internalEventMonitorFactory, - ngZone, - ); - } - - public getColumns = () => this.columns; - public getGlobalActions = () => []; - public getMultiActions = () => []; - public getSingleActions = () => [this.listActionSyncRepository, this.deleteRepository]; - public getMultiFiltersConfigs = () => []; - public getDataSource = () => this.dataSource; -} diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-repository-list-source.ts b/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-repository-list-source.ts deleted file mode 100644 index fec0503321..0000000000 --- a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-repository-list-source.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { NgZone } from '@angular/core'; -import { Store } from '@ngrx/store'; -import { interval, Observable, of as observableOf, Subscription } from 'rxjs'; - -import { safeUnsubscribe } from '../../../../../core/src/core/utils.service'; -import { RowState } from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; -import { - BaseEndpointsDataSource, - syncPaginationSection, -} from '../../../../../core/src/shared/components/list/list-types/endpoint/base-endpoints-data-source'; -import { IListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; -import { GetAllEndpoints } from '../../../../../store/src/actions/endpoint.actions'; -import { AppState } from '../../../../../store/src/app-state'; -import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; -import { InternalEventMonitorFactory } from '../../../../../store/src/monitors/internal-event-monitor.factory'; -import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; -import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; -import { HELM_ENDPOINT_TYPE } from '../helm-entity-factory'; - -export class MonocularRepositoryDataSource extends BaseEndpointsDataSource { - - private polls: Subscription[]; - private highlighted; - constructor( - store: Store<AppState>, - listConfig: IListConfig<EndpointModel>, - highlighted: string, - paginationMonitorFactory: PaginationMonitorFactory, - entityMonitorFactory: EntityMonitorFactory, - internalEventMonitorFactory: InternalEventMonitorFactory, - ngZone: NgZone, - ) { - const action = new GetAllEndpoints(); - const paginationKey = 'mono-endpoints'; - // We do this here to ensure we sync up with main endpoint table data. - syncPaginationSection(store, action, paginationKey); - action.paginationKey = paginationKey; - super( - store, - listConfig, - action, - HELM_ENDPOINT_TYPE, - paginationMonitorFactory, - entityMonitorFactory, - internalEventMonitorFactory, - false); - this.highlighted = highlighted; - this.getRowState = (row: any): Observable<RowState> => observableOf({ highlighted: row.guid === this.highlighted }); - this.polls = []; - ngZone.runOutsideAngular(() => { - this.polls.push( - interval(10000).subscribe(() => { - ngZone.run(() => { - store.dispatch(action); - }); - }) - ); - }); - } - - destroy() { - safeUnsubscribe(...(this.polls || [])); - } - -} diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.html b/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.html index 106aa1b41e..73783346ce 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.html @@ -1,4 +1,4 @@ -<app-page-header tabsHeader="Helm" [tabs]="tabLinks" [endpointIds$]="endpointIds$"> +<app-page-header [endpointIds$]="endpointIds$"> <h1>Helm</h1> </app-page-header> <router-outlet></router-outlet> \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.ts index e6cb223a67..236ad2c157 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.ts @@ -14,10 +14,6 @@ import { HELM_ENDPOINT_TYPE } from '../helm-entity-factory'; }) export class MonocularTabBaseComponent implements OnInit { - public tabLinks = [ - { link: 'charts', label: 'Charts', icon: 'folder_open' }, - { link: 'repos', label: 'Repositories', icon: 'products', iconFont: 'stratos-icons' } - ]; public endpointIds$: Observable<string[]>; constructor(private store: Store<AppState>) { } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular.interceptor.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular.interceptor.ts new file mode 100644 index 0000000000..ddb24966a1 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular.interceptor.ts @@ -0,0 +1,67 @@ +import { HttpBackend, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs'; + +import { stratosMonocularEndpointGuid } from './monocular/stratos-monocular.helper'; + +/** + * Add information to request to monocular to differ between stratos and helm hub monocular instances + */ +@Injectable() +export class MonocularInterceptor implements HttpInterceptor { + + constructor(private route: ActivatedRoute) { } + + /** + * The interceptor should only run for http clients provided in the helm module, but just in case only apply self for specific urls.. + */ + private readonly includeUrls = [ + '/pp/v1/chartsvc' + ]; + + intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { + const validUrl = this.includeUrls.find(part => req.url.indexOf(part) >= 0); + const endpoint = this.route.snapshot.params.endpoint; + const hasEndpoint = !!endpoint; + const isExternalMonocular = endpoint !== stratosMonocularEndpointGuid; + + const newReq = validUrl && hasEndpoint && isExternalMonocular ? req.clone({ + // Endpoint guid will be helm hub's endpoint + headers: req.headers.set('x-cap-cnsi-list', endpoint) + }) : req; + + return next.handle(newReq); + } +} + +class HttpInterceptorHandler implements HttpHandler { + constructor(private next: HttpHandler, private interceptor: HttpInterceptor) { } + + handle(req: HttpRequest<any>): Observable<HttpEvent<any>> { + return this.interceptor.intercept(req, this.next); + } +} +export class HttpInterceptingHandler implements HttpHandler { + private chain: HttpHandler = null; + + constructor( + private backend: HttpBackend, + private interceptors: HttpInterceptor[], + private intercept?: (req: HttpRequest<any>) => HttpRequest<any> + ) { } + + handle(req: HttpRequest<any>): Observable<HttpEvent<any>> { + if (this.intercept) { + req = this.intercept(req); + } + + if (this.chain === null) { + this.chain = this.interceptors.reduceRight( + (next, interceptor) => new HttpInterceptorHandler(next, interceptor), + this.backend + ); + } + return this.chain.handle(req); + } +} diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.html b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.html index 5801bc679a..aa041718ae 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.html @@ -1,11 +1,13 @@ <div class="chart-details-usage__install"> - <button *ngIf="endpointsService.hasConnectedEndpointType('k8s') | async; else nok8s" class="chart-details-usage__install-btn" color="primary" mat-button mat-raised-button - routerLink="/monocular/install/{{chart.id}}/{{currentVersion}}">Install Chart</button> + <button *ngIf="endpointsService.hasConnectedEndpointType('k8s') | async; else nok8s" + class="chart-details-usage__install-btn" color="primary" mat-button mat-raised-button + [routerLink]="installUrl">Install Chart</button> </div> -<!-- If there are no Kubernetes endpoints connected, show a disabled buitton with a tool tip--> +<!-- If there are no Kubernetes endpoints connected, show a disabled button with a tool tip--> <ng-template #nok8s> <div matTooltip="Add a Kubernetes endpoint to install charts"> - <button disabled="true" class="chart-details-usage__install-btn" color="primary" mat-button mat-raised-button>Install Chart</button> + <button disabled="true" class="chart-details-usage__install-btn" color="primary" mat-button + mat-raised-button>Install Chart</button> </div> -</ng-template> +</ng-template> \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.ts index d6c91d4a44..73025fe6ac 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.ts @@ -2,9 +2,11 @@ import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core'; import { MatIconRegistry } from '@angular/material/icon'; import { MatSnackBar } from '@angular/material/snack-bar'; import { DomSanitizer } from '@angular/platform-browser'; +import { ActivatedRoute } from '@angular/router'; import { EndpointsService } from '../../../../../../../core/src/core/endpoints.service'; import { Chart } from '../../shared/models/chart'; +import { getMonocularEndpoint } from '../../stratos-monocular.helper'; @Component({ selector: 'app-chart-details-usage', @@ -22,7 +24,8 @@ export class ChartDetailsUsageComponent implements OnInit { private mdIconRegistry: MatIconRegistry, private sanitizer: DomSanitizer, public snackBar: MatSnackBar, - public endpointsService: EndpointsService + public endpointsService: EndpointsService, + private route: ActivatedRoute, ) { } ngOnInit() { @@ -35,24 +38,8 @@ export class ChartDetailsUsageComponent implements OnInit { ); } - // Show an snack bar to confirm the user that the code has been copied - showSnackBar(): void { - this.snackBar.open('Copied to the clipboard', '', { - duration: 1500 - }); - } - - get showRepoInstructions(): boolean { - return this.chart.attributes.repo.name !== 'stable'; - } - - get repoAddInstructions(): string { - return `helm repo add ${this.chart.attributes.repo.name} ${this.chart - .attributes.repo.url}`; - } - - get installInstructions(): string { - return `helm install ${this.chart.id} --version ${this.currentVersion}`; + get installUrl(): string { + return `/monocular/install/${getMonocularEndpoint(this.route, this.chart)}/${this.chart.id}/${this.currentVersion}`; } } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.ts index 9e17557519..553b028fc9 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.ts @@ -1,7 +1,9 @@ import { Component, Input } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; import { ChartAttributes } from '../../shared/models/chart'; import { ChartVersion } from '../../shared/models/chart-version'; +import { getMonocularEndpoint } from '../../stratos-monocular.helper'; @Component({ selector: 'app-chart-details-versions', @@ -13,9 +15,11 @@ export class ChartDetailsVersionsComponent { @Input() currentVersion: ChartVersion; showAllVersions: boolean; + constructor(private route: ActivatedRoute) { } + goToVersionUrl(version: ChartVersion): string { const chart: ChartAttributes = version.relationships.chart.data; - return `/monocular/charts/${chart.repo.name}/${chart.name}/${version.attributes.version}`; + return `/monocular/charts/${getMonocularEndpoint(this.route)}/${chart.repo.name}/${chart.name}/${version.attributes.version}`; } isSelected(version: ChartVersion): boolean { diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.ts index 6e0d22df33..76262da43a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.ts @@ -6,6 +6,7 @@ import { Chart } from '../shared/models/chart'; import { ChartVersion } from '../shared/models/chart-version'; import { ChartsService } from '../shared/services/charts.service'; import { ConfigService } from '../shared/services/config.service'; +import { getMonocularEndpoint } from '../stratos-monocular.helper'; @Component({ selector: 'app-chart-details', @@ -55,6 +56,6 @@ export class ChartDetailsComponent implements OnInit { updateMetaTags(): void { } goToRepoUrl(): string { - return `/charts/${this.chart.attributes.repo.name}`; + return `/charts/${getMonocularEndpoint(null, this.chart)}/${this.chart.attributes.repo.name}`; } } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.spec.ts index 47188d55a5..b9512b6483 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.spec.ts @@ -2,6 +2,7 @@ import { HttpClient } from '@angular/common/http'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; import { LoggerService } from '../../../../../../core/src/core/logger.service'; import { createBasicStoreModule } from '../../../../../../store/testing/public-api'; @@ -22,7 +23,17 @@ describe('Component: ChartItem', () => { HttpClient, ConfigService, ChartsService, - LoggerService + LoggerService, + { + provide: ActivatedRoute, + useValue: { + snapshot: { + params: { + }, + queryParams: {} + }, + } + } ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.ts index ae51cd2620..e17d058c7d 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core'; import { Chart } from '../shared/models/chart'; import { ChartsService } from '../shared/services/charts.service'; +import { getMonocularEndpoint } from '../stratos-monocular.helper'; @Component({ selector: 'app-chart-item', @@ -26,11 +27,11 @@ export class ChartItemComponent implements OnInit { } goToDetailUrl(): string { - return `/monocular/charts/${this.chart.attributes.repo.name}/${this.chart.attributes + return `/monocular/charts/${getMonocularEndpoint(null, this.chart)}/${this.chart.attributes.repo.name}/${this.chart.attributes .name}`; } goToRepoUrl(): string { - return `/monocular/charts/${this.chart.attributes.repo.name}`; + return `/monocular/charts/${getMonocularEndpoint(null, this.chart)}/${this.chart.attributes.repo.name}`; } } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/monocular.module.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/monocular.module.ts new file mode 100644 index 0000000000..240e637d44 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/monocular.module.ts @@ -0,0 +1,53 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; + +import { CoreModule, SharedModule } from '../../../../../core/src/public-api'; +import { ChartDetailsInfoComponent } from './chart-details/chart-details-info/chart-details-info.component'; +import { ChartDetailsReadmeComponent } from './chart-details/chart-details-readme/chart-details-readme.component'; +import { ChartDetailsUsageComponent } from './chart-details/chart-details-usage/chart-details-usage.component'; +import { ChartDetailsVersionsComponent } from './chart-details/chart-details-versions/chart-details-versions.component'; +import { ChartDetailsComponent } from './chart-details/chart-details.component'; +import { ChartIndexComponent } from './chart-index/chart-index.component'; +import { ChartItemComponent } from './chart-item/chart-item.component'; +import { ChartListComponent } from './chart-list/chart-list.component'; +import { ChartsComponent } from './charts/charts.component'; +import { ListFiltersComponent } from './list-filters/list-filters.component'; +import { ListItemComponent } from './list-item/list-item.component'; +import { LoaderComponent } from './loader/loader.component'; +import { PanelComponent } from './panel/panel.component'; +import { createMonocularProviders } from './stratos-monocular-providers.helpers'; + +const components = [ + PanelComponent, + ChartListComponent, + ChartItemComponent, + ListItemComponent, + ListFiltersComponent, + LoaderComponent, + ChartsComponent, + ChartIndexComponent, + ChartDetailsComponent, + ChartDetailsUsageComponent, + ChartDetailsVersionsComponent, + ChartDetailsReadmeComponent, + ChartDetailsInfoComponent, +]; + +@NgModule({ + imports: [ + CoreModule, + CommonModule, + SharedModule, + ], + declarations: [ + ...components, + ], + providers: [ + // Note - not really needed here, given need to bring in with a component where route with endpoint id param exists + ...createMonocularProviders() + ], + exports: [ + ...components + ] +}) +export class MonocularModule { } \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/models/chart.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/models/chart.ts index 5c2a35dac3..4953b56f06 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/models/chart.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/models/chart.ts @@ -8,6 +8,7 @@ export class Chart { links: string[]; attributes: ChartAttributes; relationships: ChartRelationships; + monocularEndpointId?: string; } @@ -29,6 +30,6 @@ class ChartRelationships { class ChartVersionRelationship { data: ChartVersionAttributes; links: { - self: string + self: string, }; } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts index 85cfae1317..77f23474d7 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts @@ -1,9 +1,11 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { Observable, throwError } from 'rxjs'; +import { ActivatedRoute } from '@angular/router'; +import { Observable, of, throwError } from 'rxjs'; import { catchError, map, tap } from 'rxjs/operators'; import { LoggerService } from '../../../../../../../core/src/core/logger.service'; +import { getMonocularEndpoint, stratosMonocularEndpointGuid } from '../../stratos-monocular.helper'; import { Chart } from '../models/chart'; import { ChartVersion } from '../models/chart-version'; import { ConfigService } from './config.service'; @@ -19,13 +21,31 @@ export class ChartsService { constructor( private http: HttpClient, config: ConfigService, - private loggerService: LoggerService + private loggerService: LoggerService, + private route: ActivatedRoute, ) { this.hostname = `${config.backendHostname}/chartsvc`; this.cacheCharts = {}; this.hostname = '/pp/v1/chartsvc'; } + /** + * Update url to go to a monocular instance other than stratos + * These are used as img src values so won't hit our http interceptor + */ + private updateStratosUrl(chart: Chart, url: string): string { + const endpoint = getMonocularEndpoint(this.route, chart); + if (!endpoint || endpoint === stratosMonocularEndpointGuid) { + return url; + } + const parts = url.split('/'); + const chartsvcIndex = parts.findIndex(part => part === 'chartsvc'); + if (chartsvcIndex >= 0) { + parts.splice(chartsvcIndex, 0, `monocular/${endpoint}`); + } + return parts.join('/'); + } + /** * Get all charts from the API * @@ -48,7 +68,7 @@ export class ChartsService { observer.next(this.cacheCharts[repo]); }); } else { - return this.http.get<{ data: any }>(url).pipe( + return this.http.get<{ data: any, }>(url).pipe( map(r => this.extractData(r)), tap((data) => this.storeCache(data, repo)), catchError(this.handleError) @@ -119,7 +139,7 @@ export class ChartsService { * @return An observable that will be the json schema */ getChartSchema(chartVersion: ChartVersion): Observable<any> { - return this.http.get(`${this.hostname}${chartVersion.attributes.schema}`); + return chartVersion.attributes.schema ? this.http.get(`${this.hostname}${chartVersion.attributes.schema}`) : of(null); } /** @@ -130,7 +150,7 @@ export class ChartsService { * @return An observable containing an array of ChartVersions */ getVersions(repo: string, chartName: string): Observable<ChartVersion[]> { - return this.http.get<{ data: any }>(`${this.hostname}/v1/charts/${repo}/${chartName}/versions`).pipe( + return this.http.get<{ data: any; }>(`${this.hostname}/v1/charts/${repo}/${chartName}/versions`).pipe( map(m => this.extractData(m)), catchError(this.handleError) ); @@ -157,7 +177,7 @@ export class ChartsService { */ getChartIconURL(chart: Chart): string { if (chart.attributes.icon) { - return `${this.hostname}${chart.attributes.icon}`; + return this.updateStratosUrl(chart, `${this.hostname}${chart.attributes.icon}`); } else { return '/core/assets/custom/placeholder.png'; } @@ -175,7 +195,7 @@ export class ChartsService { } - private extractData(res: { data: any }) { + private extractData(res: { data: any; }) { return res.data || {}; } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/repos.service.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/repos.service.ts index 9ac9582e7e..23a0880309 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/repos.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/repos.service.ts @@ -8,7 +8,6 @@ import { RepoAttributes } from '../models/repo'; import { ConfigService } from './config.service'; - @Injectable() export class ReposService { @@ -34,7 +33,7 @@ export class ReposService { ); } - private extractData(res: { data: any }) { + private extractData(res: { data: any; }) { return res.data || {}; } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/stratos-monocular-providers.helpers.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/stratos-monocular-providers.helpers.ts new file mode 100644 index 0000000000..ac028762ec --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/stratos-monocular-providers.helpers.ts @@ -0,0 +1,28 @@ +import { HTTP_INTERCEPTORS, HttpBackend, HttpClient, HttpInterceptor } from '@angular/common/http'; + +import { HttpInterceptingHandler, MonocularInterceptor } from '../monocular.interceptor'; +import { ChartsService } from './shared/services/charts.service'; +import { ConfigService } from './shared/services/config.service'; +import { MenuService } from './shared/services/menu.service'; +import { ReposService } from './shared/services/repos.service'; + +/** + * Helm Method to ensure http client with custom monocular interceptor is used in the monocular services + */ +export const createMonocularProviders = () => [ + ChartsService, + ConfigService, + MenuService, + ReposService, + MonocularInterceptor, + { + provide: HttpClient, + useFactory: (httpBackend: HttpBackend, interceptors: HttpInterceptor[], monocularInterceptor: MonocularInterceptor) => { + return new HttpClient(new HttpInterceptingHandler(httpBackend, [ + ...interceptors, + monocularInterceptor + ])); + }, + deps: [HttpBackend, HTTP_INTERCEPTORS, MonocularInterceptor] + } +]; \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/stratos-monocular.helper.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/stratos-monocular.helper.ts new file mode 100644 index 0000000000..7a79192d1f --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/stratos-monocular.helper.ts @@ -0,0 +1,19 @@ +import { ActivatedRoute } from '@angular/router'; + +import { Chart } from './shared/models/chart'; + + +/** + * Stratos Monocular has no concept of an endpoint (it has monocular repo endpoints...) so give it a default string + * Note - This could be the guid for the helm hub endpoint + */ +export const stratosMonocularEndpointGuid = 'default'; + +/** + * Add the monocular endpoint id to a url. This could be the helm hub endpoint guid or `default` for stratos monocular + */ +export const getMonocularEndpoint = (route?: ActivatedRoute, chart?: Chart, ifEmpty = stratosMonocularEndpointGuid) => { + const endpointFromRoute = route ? route.snapshot.params.endpoint : null; + const endpointFromChart = chart ? chart.monocularEndpointId : null; + return endpointFromRoute || endpointFromChart || ifEmpty; +}; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.action-builders.ts b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.action-builders.ts index df443d3ea6..708227b098 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.action-builders.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.action-builders.ts @@ -1,31 +1,49 @@ import { OrchestratedActionBuilders } from '../../../../../store/src/entity-catalog/action-orchestrator/action-orchestrator'; -import { GetHelmChartVersions, GetHelmVersions, GetMonocularCharts, HelmInstall } from './helm.actions'; +import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; +import { GetHelmChartVersions, GetHelmVersions, GetMonocularCharts, HelmInstall, HelmSynchronise } from './helm.actions'; import { HelmInstallValues } from './helm.types'; export interface HelmChartActionBuilders extends OrchestratedActionBuilders { getMultiple: () => GetMonocularCharts, // Helm install added to chart action builder and not helm release/workload to ensure action & effect are available in this module // (others may not have loaded) - install: (values: HelmInstallValues) => HelmInstall + install: (values: HelmInstallValues) => HelmInstall, + synchronise: (endpoint: EndpointModel) => HelmSynchronise; } export const helmChartActionBuilders: HelmChartActionBuilders = { getMultiple: () => new GetMonocularCharts(), - install: (values: HelmInstallValues) => new HelmInstall(values) -} + install: (values: HelmInstallValues) => new HelmInstall(values), + synchronise: (endpoint: EndpointModel) => new HelmSynchronise(endpoint) +}; export interface HelmVersionActionBuilders extends OrchestratedActionBuilders { - getMultiple: () => GetHelmVersions + getMultiple: () => GetHelmVersions; } export const helmVersionActionBuilders: HelmVersionActionBuilders = { getMultiple: () => new GetHelmVersions() -} +}; export interface HelmChartVersionsActionBuilders extends OrchestratedActionBuilders { - getMultiple: (repoName: string, chartName: string) => GetHelmChartVersions + getMultiple: ( + endpointGuid: string, + paginationKey: string, + extraArgs: { + monocularEndpoint: string, + repoName: string, + chartName: string; + }) => GetHelmChartVersions; } export const helmChartVersionsActionBuilders: HelmChartVersionsActionBuilders = { - getMultiple: (repoName: string, chartName: string) => new GetHelmChartVersions(repoName, chartName) -} + getMultiple: ( + endpointGuid: string, + paginationKey: string, + extraArgs: { + monocularEndpoint: string, + repoName: string, + chartName: string; + }) => + new GetHelmChartVersions(extraArgs.monocularEndpoint, extraArgs.repoName, extraArgs.chartName) +}; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.actions.ts b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.actions.ts index df552269dd..883cdd4414 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.actions.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.actions.ts @@ -1,3 +1,6 @@ +import { Action } from '@ngrx/store'; + +import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; import { PaginatedAction } from '../../../../../store/src/types/pagination.types'; import { EntityRequestAction } from '../../../../../store/src/types/request.types'; import { @@ -25,6 +28,8 @@ export const HELM_INSTALL = '[Helm] Install'; export const HELM_INSTALL_SUCCESS = '[Helm] Install Success'; export const HELM_INSTALL_FAILURE = '[Helm] Install Failure'; +export const HELM_SYNCHRONISE = '[Helm] Synchronise'; + export interface MonocularPaginationAction extends PaginatedAction, EntityRequestAction { } export class GetMonocularCharts implements MonocularPaginationAction { @@ -48,6 +53,15 @@ export class GetMonocularCharts implements MonocularPaginationAction { flattenPagination = true; } +export class HelmSynchronise implements Action { + public type = HELM_SYNCHRONISE; + public guid: string; + + constructor(public endpoint: EndpointModel) { + this.guid = endpoint.guid; + } +} + export class HelmInstall implements EntityRequestAction { type = HELM_INSTALL; endpointType = HELM_ENDPOINT_TYPE; @@ -80,7 +94,7 @@ export class GetHelmVersions implements MonocularPaginationAction { } export class GetHelmChartVersions implements MonocularPaginationAction { - constructor(public repoName: string, public chartName: string) { + constructor(public monocularEndpoint: string, public repoName: string, public chartName: string) { this.paginationKey = `'monocular-chart-versions-${repoName}-${chartName}`; } type = GET_MONOCULAR_CHART_VERSIONS; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts index 31508d886f..440f452122 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts @@ -1,9 +1,10 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; import { Actions, Effect, ofType } from '@ngrx/effects'; import { Action, Store } from '@ngrx/store'; -import { Observable } from 'rxjs'; -import { catchError, flatMap, mergeMap } from 'rxjs/operators'; +import { combineLatest, Observable, of } from 'rxjs'; +import { catchError, flatMap, map, mergeMap, withLatestFrom } from 'rxjs/operators'; import { environment } from '../../../../../core/src/environments/environment'; import { GET_ENDPOINTS_SUCCESS, GetAllEndpointsSuccess } from '../../../../../store/src/actions/endpoint.actions'; @@ -12,6 +13,7 @@ import { AppState } from '../../../../../store/src/app-state'; import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; import { isJetstreamError } from '../../../../../store/src/jetstream'; import { ApiRequestTypes } from '../../../../../store/src/reducers/api-request-reducer/request-helpers'; +import { endpointOfTypeSelector } from '../../../../../store/src/selectors/endpoint.selectors'; import { NormalizedResponse } from '../../../../../store/src/types/api.types'; import { EntityRequestAction, @@ -20,7 +22,9 @@ import { WrapperRequestActionSuccess, } from '../../../../../store/src/types/request.types'; import { helmEntityCatalog } from '../helm-entity-catalog'; -import { getHelmVersionId, getMonocularChartId, HELM_ENDPOINT_TYPE } from '../helm-entity-factory'; +import { getHelmVersionId, getMonocularChartId, HELM_ENDPOINT_TYPE, HELM_HUB_ENDPOINT_TYPE } from '../helm-entity-factory'; +import { Chart } from '../monocular/shared/models/chart'; +import { stratosMonocularEndpointGuid } from '../monocular/stratos-monocular.helper'; import { GET_HELM_VERSIONS, GET_MONOCULAR_CHART_VERSIONS, @@ -29,17 +33,60 @@ import { GetHelmVersions, GetMonocularCharts, HELM_INSTALL, + HELM_SYNCHRONISE, HelmInstall, + HelmSynchronise, } from './helm.actions'; import { HelmVersion } from './helm.types'; +type MonocularChartsResponse = { + data: Chart[]; +}; + +const mapMonocularChartResponse = (entityKey: string, response: MonocularChartsResponse): NormalizedResponse => { + const base: NormalizedResponse = { + entities: { [entityKey]: {} }, + result: [] + }; + + const items = response.data as Array<any>; + const processedData: NormalizedResponse = items.reduce((res, data) => { + const id = getMonocularChartId(data); + res.entities[entityKey][id] = data; + // Promote the name to the top-level object for simplicity + data.name = data.attributes.name; + res.result.push(id); + return res; + }, base); + return processedData; +}; + +const mergeMonocularChartResponses = (entityKey: string, responses: MonocularChartsResponse[]): NormalizedResponse => { + const combined = responses.reduce((res, response) => { + res.data = res.data.concat(response.data); + return res; + }, { data: [] }); + return mapMonocularChartResponse(entityKey, combined); +}; + +const addMonocularId = (endpointId: string, response: MonocularChartsResponse): MonocularChartsResponse => { + const data = response.data.map(chart => ({ + ...chart, + monocularEndpointId: endpointId + })); + return { + data + }; +}; + @Injectable() export class HelmEffects { constructor( private httpClient: HttpClient, private actions$: Actions, - private store: Store<AppState> + private store: Store<AppState>, + public snackBar: MatSnackBar, ) { } // Endpoints that we know are synchronizing @@ -53,7 +100,7 @@ export class HelmEffects { updateOnSyncFinished$ = this.actions$.pipe( ofType<GetAllEndpointsSuccess>(GET_ENDPOINTS_SUCCESS), flatMap(action => { - // Look to see if we have any endpoints that are sycnhronizing + // Look to see if we have any endpoints that are synchronizing let updated = false; Object.values(action.payload.entities.stratosEndpoint).forEach(endpoint => { if (endpoint.cnsi_type === HELM_ENDPOINT_TYPE && endpoint.endpoint_metadata) { @@ -78,25 +125,43 @@ export class HelmEffects { @Effect() fetchCharts$ = this.actions$.pipe( ofType<GetMonocularCharts>(GET_MONOCULAR_CHARTS), - flatMap(action => { + withLatestFrom(this.store), + flatMap(([action, appState]) => { const entityKey = entityCatalog.getEntityKey(action); - return this.makeRequest(action, `/pp/${this.proxyAPIVersion}/chartsvc/v1/charts`, (response) => { - const base = { - entities: { [entityKey]: {} }, - result: [] - } as NormalizedResponse; - const items = response.data as Array<any>; - const processedData = items.reduce((res, data) => { - const id = getMonocularChartId(data); - res.entities[entityKey][id] = data; - // Promote the name to the top-level object for simplicity - data.name = data.attributes.name; - res.result.push(id); - return res; - }, base); - return processedData; - }, []); + this.store.dispatch(new StartRequestAction(action)); + + const helmEndpoints = Object.values(endpointOfTypeSelector(HELM_ENDPOINT_TYPE)(appState)); + const helmHubEndpoint = helmEndpoints.find(endpoint => endpoint.sub_type === HELM_HUB_ENDPOINT_TYPE); + + // See https://github.com/SUSE/stratos/issues/466. It would be better to use the standard proxy for this request and go out to all + // valid helm sub types + const stratosMonocular = this.httpClient.get<MonocularChartsResponse>(`/pp/${this.proxyAPIVersion}/chartsvc/v1/charts`); + const helmHubMonocular = helmHubEndpoint ? this.createHelmHubRequest(helmHubEndpoint.guid) : of({ data: [] }); + + return combineLatest([ + stratosMonocular, + helmHubMonocular + ]).pipe( + map(res => mergeMonocularChartResponses(entityKey, res)), + mergeMap((response: NormalizedResponse) => [new WrapperRequestActionSuccess(response, action)]), + catchError(error => { + const { status, message } = HelmEffects.createHelmError(error); + const endpointIds = helmEndpoints.map(e => e.guid); + if (helmHubEndpoint) { + endpointIds.push(helmHubEndpoint.guid); + } + return [ + new WrapperRequestActionFailed(message, action, 'fetch', { + endpointIds, + url: null, + eventCode: status, + message, + error + }) + ]; + }) + ); }) ); @@ -106,10 +171,10 @@ export class HelmEffects { flatMap(action => { const entityKey = entityCatalog.getEntityKey(action); return this.makeRequest(action, `/pp/${this.proxyAPIVersion}/helm/versions`, (response) => { - const processedData = { + const processedData: NormalizedResponse = { entities: { [entityKey]: {} }, result: [] - } as NormalizedResponse; + }; // Go through each endpoint ID Object.keys(response).forEach(endpoint => { @@ -136,23 +201,25 @@ export class HelmEffects { flatMap(action => { const entityKey = entityCatalog.getEntityKey(action); return this.makeRequest(action, `/pp/${this.proxyAPIVersion}/chartsvc/v1/charts/${action.repoName}/${action.chartName}/versions`, - (response) => { - const base = { - entities: { [entityKey]: {} }, - result: [] - } as NormalizedResponse; + (response) => { + const base: NormalizedResponse = { + entities: { [entityKey]: {} }, + result: [] + }; - const items = response.data as Array<any>; - const processedData = items.reduce((res, data) => { - const id = getMonocularChartId(data); - res.entities[entityKey][id] = data; - // Promote the name to the top-level object for simplicity - data.name = data.attributes.name; - res.result.push(id); - return res; - }, base); - return processedData; - }, []); + const items = response.data as Array<any>; + const processedData = items.reduce((res, data) => { + const id = getMonocularChartId(data); + res.entities[entityKey][id] = data; + // Promote the name to the top-level object for simplicity + data.name = data.attributes.name; + res.result.push(id); + return res; + }, base); + return processedData; + }, [], { + 'x-cap-cnsi-list': action.monocularEndpoint !== stratosMonocularEndpointGuid ? action.monocularEndpoint : '' + }); }) ); @@ -187,6 +254,26 @@ export class HelmEffects { }) ); + @Effect() + helmSynchronise$ = this.actions$.pipe( + ofType<HelmSynchronise>(HELM_SYNCHRONISE), + flatMap(action => { + const requestArgs = { + headers: null, + params: null + }; + const proxyAPIVersion = environment.proxyAPIVersion; + const url = `/pp/${proxyAPIVersion}/chartrepos/${action.endpoint.guid}`; + const req = this.httpClient.post(url, requestArgs); + req.subscribe(ok => { + this.snackBar.open('Helm Repository synchronization started', 'Dismiss', { duration: 3000 }); + }, err => { + this.snackBar.open(`Failed to Synchronize Helm Repository '${action.endpoint.name}'`, 'Dismiss', { duration: 5000 }); + }); + return []; + }) + ); + private static createHelmErrorMessage(err: any): string { if (err) { if (err.error && err.error.message) { @@ -222,15 +309,24 @@ export class HelmEffects { }; } + private createHelmHubRequest(endpointId: string): Observable<MonocularChartsResponse> { + return this.httpClient.get<MonocularChartsResponse>(`/pp/${this.proxyAPIVersion}/chartsvc/v1/charts`, { + headers: { + 'x-cap-cnsi-list': endpointId + } + }).pipe(map(res => addMonocularId(endpointId, res))); + } + private makeRequest( action: EntityRequestAction, url: string, mapResult: (response: any) => NormalizedResponse, - endpointIds: string[] + endpointIds: string[], + headers = {} ): Observable<Action> { this.store.dispatch(new StartRequestAction(action)); const requestArgs = { - headers: null, + headers, params: null }; return this.httpClient.get(url, requestArgs).pipe( diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.types.ts b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.types.ts index 4ddf46021c..7c9c4afa9c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.types.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.types.ts @@ -1,5 +1,5 @@ -import { Chart } from './../monocular/shared/models/chart'; -import { ChartVersion } from './../monocular/shared/models/chart-version'; +import { Chart } from '../monocular/shared/models/chart'; +import { ChartVersion } from '../monocular/shared/models/chart-version'; export interface MonocularRepository { name: string; @@ -47,10 +47,26 @@ export enum HelmStatus { export interface HelmInstallValues { endpoint: string; + monocularEndpoint: string; releaseName: string; releaseNamespace: string; values: string; - chart: string; + chart: { + name: string; + repo: string; + version: string; + }; +} + +export interface HelmUpgradeValues { + values: string; + chart: { + name: string; + repo: string; + version: string; + }; + restartPods?: boolean; + monocularEndpoint?: string; } export interface HelmUpgradeValues { diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/tabs/repository-tab/repository-tab.component.html b/src/frontend/packages/suse-extensions/src/custom/helm/tabs/repository-tab/repository-tab.component.html deleted file mode 100644 index dd2b6cb351..0000000000 --- a/src/frontend/packages/suse-extensions/src/custom/helm/tabs/repository-tab/repository-tab.component.html +++ /dev/null @@ -1 +0,0 @@ -<app-list></app-list> \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/tabs/repository-tab/repository-tab.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/helm/tabs/repository-tab/repository-tab.component.spec.ts deleted file mode 100644 index 57e2407ba1..0000000000 --- a/src/frontend/packages/suse-extensions/src/custom/helm/tabs/repository-tab/repository-tab.component.spec.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { HelmBaseTestModules } from '../../helm-testing.module'; -import { RepositoryTabComponent } from './repository-tab.component'; - -describe('RepositoryTabComponent', () => { - let component: RepositoryTabComponent; - let fixture: ComponentFixture<RepositoryTabComponent>; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - ...HelmBaseTestModules - ], - declarations: [RepositoryTabComponent] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(RepositoryTabComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/tabs/repository-tab/repository-tab.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/tabs/repository-tab/repository-tab.component.ts deleted file mode 100644 index cea4439854..0000000000 --- a/src/frontend/packages/suse-extensions/src/custom/helm/tabs/repository-tab/repository-tab.component.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Component } from '@angular/core'; - -import { ListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; -import { MonocularRepositoryListConfig } from '../../list-types/monocular-repository-list-config.service'; - -@Component({ - selector: 'app-repository-tab', - templateUrl: './repository-tab.component.html', - styleUrls: ['./repository-tab.component.scss'], - providers: [{ - provide: ListConfig, - useClass: MonocularRepositoryListConfig, - }] -}) -export class RepositoryTabComponent { - -} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts index 749d08e560..99899a0b92 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts @@ -3,6 +3,7 @@ import { combineLatest, Observable } from 'rxjs'; import { filter, map } from 'rxjs/operators'; import { helmEntityCatalog } from '../../../../helm/helm-entity-catalog'; +import { ChartAttributes } from '../../../../helm/monocular/shared/models/chart'; import { ChartMetadata } from '../../../../helm/store/helm.types'; import { kubeEntityCatalog } from '../../../kubernetes-entity-catalog'; import { ContainerStateCollection, KubernetesPod } from '../../../store/kube.types'; @@ -60,6 +61,13 @@ class Version { } } +type InternalHelmUpgrade = { + release: HelmRelease, + upgrade: ChartAttributes, + version: string, + monocularEndpointId: string; +}; + @Injectable() export class HelmReleaseHelperService { @@ -206,7 +214,7 @@ export class HelmReleaseHelperService { return false; } - public hasUpgrade(returnLatest = false): Observable<any> { + public hasUpgrade(returnLatest = false): Observable<InternalHelmUpgrade> { const updates = combineLatest(this.getCharts(), this.release$); return updates.pipe( map(([charts, release]) => { @@ -219,7 +227,8 @@ export class HelmReleaseHelperService { return { release, upgrade: c.attributes, - version: c.relationships.latestChartVersion.data.version + version: c.relationships.latestChartVersion.data.version, + monocularEndpointId: c.monocularEndpointId }; } } @@ -233,8 +242,9 @@ export class HelmReleaseHelperService { return { release, upgrade: releaseChart.attributes, - version: releaseChart.relationships.latestChartVersion.data.version - } + version: releaseChart.relationships.latestChartVersion.data.version, + monocularEndpointId: releaseChart.monocularEndpointId + }; } } return null; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.effects.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.effects.ts index a572f14604..7c235a2766 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.effects.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.effects.ts @@ -163,7 +163,7 @@ export class WorkloadsEffects { }) ); -private mapHelmRelease(data, endpointId, guid: string) { + private mapHelmRelease(data, endpointId, guid: string) { const helmRelease: HelmRelease = { ...data, endpointId @@ -201,7 +201,7 @@ private mapHelmRelease(data, endpointId, guid: string) { message: errorMessage, error }) - ] + ]; }) ); } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-data-source.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-data-source.ts index f98ddea450..204cc8b66a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-data-source.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-data-source.ts @@ -1,15 +1,11 @@ import { Store } from '@ngrx/store'; import { Observable, of } from 'rxjs'; -import { - DataFunction, - ListDataSource, -} from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; +import { ListDataSource } from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; import { RowState } from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; import { IListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; import { PaginationEntityState } from '../../../../../../store/src/types/pagination.types'; import { helmEntityCatalog } from '../../../helm/helm-entity-catalog'; -import { helmEntityFactory, monocularChartVersionsEntityType } from '../../../helm/helm-entity-factory'; import { MonocularVersion } from './../../../helm/store/helm.types'; @@ -26,13 +22,19 @@ export class HelmReleaseVersionsDataSource extends ListDataSource<MonocularVersi repoName: string, chartName: string, version: string, + monocularEndpoint: string, ) { + const action = helmEntityCatalog.chartVersions.actions.getMultiple(null, null, { + repoName, + chartName, + monocularEndpoint + }); super({ store, - action: helmEntityCatalog.chartVersions.actions.getMultiple(repoName, chartName), - schema: helmEntityFactory(monocularChartVersionsEntityType), - getRowUniqueId: (object: MonocularVersion) => object.id, - paginationKey: helmEntityCatalog.chartVersions.actions.getMultiple(repoName, chartName).paginationKey, + action, + schema: action.entity[0], + getRowUniqueId: (object: MonocularVersion) => action.entity[0].getId(object), + paginationKey: action.paginationKey, isLocal: true, transformEntities: [ (entities: MonocularVersion[], paginationState: PaginationEntityState) => this.endpointTypeFilter(entities, paginationState) @@ -45,7 +47,7 @@ export class HelmReleaseVersionsDataSource extends ListDataSource<MonocularVersi } - public endpointTypeFilter: DataFunction<MonocularVersion> = (entities: MonocularVersion[], paginationState: PaginationEntityState) => { + public endpointTypeFilter(entities: MonocularVersion[], paginationState: PaginationEntityState): MonocularVersion[] { if ( !paginationState.clientPagination || !paginationState.clientPagination.filter || @@ -55,7 +57,7 @@ export class HelmReleaseVersionsDataSource extends ListDataSource<MonocularVersi // Filter out development versions if configured const showAll = paginationState.clientPagination.filter.items[typeFilterKey] === 'all'; - return showAll ? entities : entities.filter(e => e.attributes.version.indexOf('-') === -1); + return showAll ? entities : entities.filter(e => e.attributes.version.indexOf('-') === -1); } } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-list-config.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-list-config.ts index 6d712da1d5..fe811a1a78 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-list-config.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-list-config.ts @@ -22,7 +22,7 @@ import { HelmReleaseVersionsDataSource } from './release-version-data-source'; const typeFilterKey = 'versionType'; -export class ReleaseUpgradeVersionsListConfig implements IListConfig<any> { +export class ReleaseUpgradeVersionsListConfig implements IListConfig<MonocularVersion> { public versionsDataSource: ListDataSource<MonocularVersion>; @@ -32,7 +32,7 @@ export class ReleaseUpgradeVersionsListConfig implements IListConfig<any> { getMultiActions: () => IMultiListAction<any>[]; getSingleActions: () => IListAction<any>[]; - columns: Array<ITableColumn<any>> = [ + columns: Array<ITableColumn<MonocularVersion>> = [ { columnId: 'radio', headerCell: () => '', @@ -80,33 +80,34 @@ export class ReleaseUpgradeVersionsListConfig implements IListConfig<any> { repoName: string, chartName: string, version: string, + monocularEndpoint: string ) { this.getGlobalActions = () => []; this.getMultiActions = () => []; this.getSingleActions = () => []; - this.versionsDataSource = new HelmReleaseVersionsDataSource(store, this, repoName, chartName, version); + this.versionsDataSource = new HelmReleaseVersionsDataSource(store, this, repoName, chartName, version, monocularEndpoint); this.multiFiltersConfigs = [{ hideAllOption: true, autoSelectFirst: true, key: typeFilterKey, - label: 'Endpoint Type', - list$: of([ - { - label: 'Release Versions', - item: {}, - value: 'release' - }, - { - label: 'All Versions', - item: {}, - value: 'all' - } - ]), - loading$: of(false), - select: new BehaviorSubject(undefined) - }]; + label: 'Endpoint Type', + list$: of([ + { + label: 'Release Versions', + item: {}, + value: 'release' + }, + { + label: 'All Versions', + item: {}, + value: 'all' + } + ]), + loading$: of(false), + select: new BehaviorSubject(undefined) + }]; // Auto-select first non-development version setTimeout(() => { @@ -122,7 +123,7 @@ export class ReleaseUpgradeVersionsListConfig implements IListConfig<any> { private getFirstNonDevelopmentVersion(rows: MonocularVersion[]): MonocularVersion { for (const mv of rows) { if (mv.attributes.version.indexOf('-') === -1) { - return mv + return mv; } } return rows[0]; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts index 8ef8a83118..34f8ef5c45 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts @@ -7,7 +7,8 @@ import { filter, first, map, pairwise } from 'rxjs/operators'; import { StepComponent, StepOnNextFunction } from '../../../../../../core/src/shared/components/stepper/step/step.component'; import { ActionState } from '../../../../../../store/src/reducers/api-request-reducer/types'; -import { HelmUpgradeValues } from '../../../helm/store/helm.types'; +import { stratosMonocularEndpointGuid } from '../../../helm/monocular/stratos-monocular.helper'; +import { HelmUpgradeValues, MonocularVersion } from '../../../helm/store/helm.types'; import { HelmReleaseHelperService } from '../release/tabs/helm-release-helper.service'; import { HelmReleaseGuid } from '../workload.types'; import { workloadsEntityCatalog } from './../workloads-entity-catalog'; @@ -33,15 +34,20 @@ import { ReleaseUpgradeVersionsListConfig } from './release-version-list-config' export class UpgradeReleaseComponent { public cancelUrl; - public listConfig; + public listConfig: ReleaseUpgradeVersionsListConfig; public validate$: Observable<boolean>; - private version; + private version: MonocularVersion; public overrides: FormGroup; + private monocularEndpointId: string; + // Future public showAdvancedOptions = false; - constructor(store: Store<any>, public helper: HelmReleaseHelperService) { + constructor( + store: Store<any>, + public helper: HelmReleaseHelperService + ) { this.cancelUrl = `/workloads/${this.helper.guid}`; @@ -57,7 +63,8 @@ export class UpgradeReleaseComponent { const name = chart.upgrade.name; const repoName = chart.upgrade.repo.name; const version = chart.release.chart.metadata.version; - this.listConfig = new ReleaseUpgradeVersionsListConfig(store, repoName, name, version); + this.listConfig = new ReleaseUpgradeVersionsListConfig(store, repoName, name, version, chart.monocularEndpointId); + this.monocularEndpointId = chart.monocularEndpointId; // First step is valid when a version has been selected this.validate$ = this.listConfig.versionsDataSource.selectedRows$.pipe( @@ -79,36 +86,37 @@ export class UpgradeReleaseComponent { doUpgrade: StepOnNextFunction = (index: number, step: StepComponent) => { // If we are showing the advanced options, don't upgrade if we aer not on the last step - if (this.showAdvancedOptions && index === 1 ) { + if (this.showAdvancedOptions && index === 1) { return of({ success: true }); } const values: HelmUpgradeValues = { - ...this.overrides.value, + values: this.overrides.controls.values.value, restartPods: false, chart: { name: this.version.relationships.chart.data.name, repo: this.version.relationships.chart.data.repo.name, version: this.version.attributes.version, }, + monocularEndpoint: this.monocularEndpointId === stratosMonocularEndpointGuid ? null : this.monocularEndpointId }; // Make the request return workloadsEntityCatalog.release.api.upgrade<ActionState>(this.helper.releaseTitle, this.helper.endpointGuid, this.helper.namespace, values).pipe( - // Wait for result of request - filter(state => !!state), - pairwise(), - filter(([oldVal, newVal]) => (oldVal.busy && !newVal.busy)), - map(([, newVal]) => newVal), - map(result => ({ - success: !result.error, - redirect: !result.error, - redirectPayload: { - path: !result.error ? this.cancelUrl : '' - }, - message: !result.error ? '' : result.message - })) - ); - } + // Wait for result of request + filter(state => !!state), + pairwise(), + filter(([oldVal, newVal]) => (oldVal.busy && !newVal.busy)), + map(([, newVal]) => newVal), + map(result => ({ + success: !result.error, + redirect: !result.error, + redirectPayload: { + path: !result.error ? this.cancelUrl : '' + }, + message: !result.error ? '' : result.message + })) + ); + }; } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.routing.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.routing.ts index a7732ac7a5..a9773315f8 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.routing.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.routing.ts @@ -28,6 +28,7 @@ const routes: Routes = [ pathMatch: 'full', }, { + // guid = kube endpoint path: ':guid/upgrade', component: UpgradeReleaseComponent, pathMatch: 'full', diff --git a/src/jetstream/datastore/20200902162200_HelmSubtype.go b/src/jetstream/datastore/20200902162200_HelmSubtype.go new file mode 100644 index 0000000000..ebb97be983 --- /dev/null +++ b/src/jetstream/datastore/20200902162200_HelmSubtype.go @@ -0,0 +1,21 @@ +package datastore + +import ( + "database/sql" + + "bitbucket.org/liamstask/goose/lib/goose" +) + +func init() { + RegisterMigration(20200902162200, "HelmSubtype", func(txn *sql.Tx, conf *goose.DBConf) error { + + // Make sure all previous helm endpoints type shave the correct 'repo' sub type + updateHelmRepoSubtype := "UPDATE cnsis SET sub_type='repo' WHERE cnsi_type='helm';" + _, err := txn.Exec(updateHelmRepoSubtype) + if err != nil { + return err + } + + return nil + }) +} diff --git a/src/jetstream/passthrough.go b/src/jetstream/passthrough.go index 4a6cfeeba0..b104ba5fed 100644 --- a/src/jetstream/passthrough.go +++ b/src/jetstream/passthrough.go @@ -221,7 +221,7 @@ func (p *portalProxy) proxy(c echo.Context) error { } func (p *portalProxy) ProxyRequest(c echo.Context, uri *url.URL) (map[string]*interfaces.CNSIRequest, error) { - log.Debug("proxy") + log.Debug("ProxyRequest") cnsiList := strings.Split(c.Request().Header.Get("x-cap-cnsi-list"), ",") shouldPassthrough := "true" == c.Request().Header.Get("x-cap-passthrough") longRunning := "true" == c.Request().Header.Get(longRunningTimeoutHeader) diff --git a/src/jetstream/plugins/kubernetes/install_release.go b/src/jetstream/plugins/kubernetes/install_release.go index 15cf67623e..575c0b7065 100644 --- a/src/jetstream/plugins/kubernetes/install_release.go +++ b/src/jetstream/plugins/kubernetes/install_release.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" - "github.com/helm/monocular/chartsvc" "github.com/labstack/echo" log "github.com/sirupsen/logrus" "sigs.k8s.io/yaml" @@ -24,11 +23,12 @@ import ( const chartCollection = "charts" type installRequest struct { - Endpoint string `json:"endpoint"` - Name string `json:"releaseName"` - Namespace string `json:"releaseNamespace"` - Values string `json:"values"` - Chart struct { + Endpoint string `json:"endpoint"` + MonocularEndpoint string `json:"monocularEndpoint"` + Name string `json:"releaseName"` + Namespace string `json:"releaseNamespace"` + Values string `json:"values"` + Chart struct { Name string `json:"chartName"` Repository string `json:"repo"` Version string `json:"version"` @@ -36,17 +36,19 @@ type installRequest struct { } type upgradeRequest struct { - Values string `json:"values"` - Chart struct { + MonocularEndpoint string `json:"monocularEndpoint"` + Values string `json:"values"` + Chart struct { Name string `json:"name"` Repository string `json:"repo"` Version string `json:"version"` } `json:"chart"` + RestartPods bool `json:"restartPods"` } // Monocular is a plugin for Monocular type Monocular interface { - GetChartStore() *chartsvc.ChartSvcDatastore + GetChartDownloadUrl(monocularEndpoint, chartID, chartVersion string) (string, error) } // InstallRelease will install a Helm 3 release @@ -61,7 +63,7 @@ func (c *KubernetesSpecification) InstallRelease(ec echo.Context) error { return interfaces.NewJetstreamErrorf("Could not get Create Release Parameters: %v+", err) } - chart, err := c.loadChart(params.Chart.Repository, params.Chart.Name, params.Chart.Version) + chart, err := c.loadChart(params.MonocularEndpoint, params.Chart.Repository, params.Chart.Name, params.Chart.Version) if err != nil { return interfaces.NewJetstreamErrorf("Could not load chart: %v+", err) } @@ -110,7 +112,7 @@ func (c *KubernetesSpecification) InstallRelease(ec echo.Context) error { return ec.JSON(200, release) } -func (c *KubernetesSpecification) getChart(chartID, version string) (string, error) { +func (c *KubernetesSpecification) getChart(monocularEndpoint, chartID, version string) (string, error) { helm := c.portalProxy.GetPlugin("monocular") if helm == nil { return "", errors.New("Could not find monocular plugin") @@ -121,31 +123,16 @@ func (c *KubernetesSpecification) getChart(chartID, version string) (string, err return "", errors.New("Could not find monocular plugin interface") } - store := monocular.GetChartStore() - chart, err := store.GetChart(chartID) - if err != nil { - return "", errors.New("Could not find Chart") - } - - // Find the download URL for the version - for _, chartVersion := range chart.ChartVersions { - if chartVersion.Version == version { - if len(chartVersion.URLs) == 1 { - return chartVersion.URLs[0], nil - } - } - } - - return "", errors.New("Could not find Chart Version") + return monocular.GetChartDownloadUrl(monocularEndpoint, chartID, version) } // Load the Helm chart for the given repository, name and version -func (c *KubernetesSpecification) loadChart(repo, name, version string) (*chart.Chart, error) { +func (c *KubernetesSpecification) loadChart(monocularEndpoint, repo, name, version string) (*chart.Chart, error) { chartID := fmt.Sprintf("%s/%s", repo, name) - downloadURL, err := c.getChart(chartID, version) + downloadURL, err := c.getChart(monocularEndpoint, chartID, version) if err != nil { - return nil, fmt.Errorf("Could not get the Download URL for the Helm Chart") + return nil, fmt.Errorf("Could not get the Download URL for the Helm Chart: %+v", err) } log.Debugf("Helm Chart Download URL: %s", downloadURL) @@ -238,7 +225,7 @@ func (c *KubernetesSpecification) UpgradeRelease(ec echo.Context) error { defer hc.Cleanup() - chart, err := c.loadChart(params.Chart.Repository, params.Chart.Name, params.Chart.Version) + chart, err := c.loadChart(params.MonocularEndpoint, params.Chart.Repository, params.Chart.Name, params.Chart.Version) if err != nil { return interfaces.NewJetstreamErrorf("Could not load chart for upgrade: %+v", err) } diff --git a/src/jetstream/plugins/metrics/main.go b/src/jetstream/plugins/metrics/main.go index b04ae68f3f..535e52706d 100644 --- a/src/jetstream/plugins/metrics/main.go +++ b/src/jetstream/plugins/metrics/main.go @@ -348,9 +348,7 @@ func (m *MetricsSpecification) UpdateMetadata(info *interfaces.Info, userGUID st for _, values := range info.Endpoints { for _, endpoint := range values { // Look to see if we can find the metrics provider for this URL - log.Debugf("Processing endpoint: %+v", endpoint) log.Debugf("Processing endpoint: %+v", endpoint.CNSIRecord) - if provider, ok := hasMetricsProvider(metricsProviders, endpoint.DopplerLoggingEndpoint); ok { endpoint.Metadata["metrics"] = provider.EndpointGUID endpoint.Metadata["metrics_job"] = provider.Job diff --git a/src/jetstream/plugins/monocular/main.go b/src/jetstream/plugins/monocular/main.go index 0a6b38438e..01f12dc905 100644 --- a/src/jetstream/plugins/monocular/main.go +++ b/src/jetstream/plugins/monocular/main.go @@ -1,14 +1,17 @@ package monocular import ( + "encoding/json" "errors" "fmt" + "io/ioutil" "math/rand" "net/http" "os" "os/exec" "path/filepath" "strings" + "time" "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/labstack/echo" @@ -16,10 +19,15 @@ import ( "github.com/helm/monocular/chartsvc" "github.com/helm/monocular/chartsvc/foundationdb" + "github.com/helm/monocular/chartsvc/models" + "github.com/helm/monocular/chartsvc/utils" ) const ( helmEndpointType = "helm" + helmHubEndpointType = "hub" + helmRepoEndpointType = "repo" + stratosPrefix = "/pp/v1/" prefix = "/pp/v1/chartsvc/" kubeReleaseNameEnvVar = "STRATOS_HELM_RELEASE" foundationDBURLEnvVar = "FDB_URL" @@ -41,16 +49,20 @@ type Monocular struct { devSyncPID int } +type HelmHubChart struct { + utils.ApiResponse + Attributes *models.ChartVersion `json:"attributes"` +} + +type HelmHubChartResponse struct { + Data HelmHubChart `json:"data"` +} + // Init creates a new Monocular func Init(portalProxy interfaces.PortalProxy) (interfaces.StratosPlugin, error) { return &Monocular{portalProxy: portalProxy}, nil } -// GetChartStore gets the chart store -func (m *Monocular) GetChartStore() *chartsvc.ChartSvcDatastore { - return m.RepoQueryStore -} - // Init performs plugin initialization func (m *Monocular) Init() error { log.Debug("Monocular init .... ") @@ -135,26 +147,31 @@ func (m *Monocular) syncOnStartup() { // Get the repositories that we currently have repos, err := foundationdb.ListRepositories() if err != nil { - log.Errorf("Chart Repostiory Startup: Unable to sync repositories: %v+", err) + log.Errorf("Chart Repository Startup: Unable to sync repositories: %v+", err) return } // Get all of the helm endpoints endpoints, err := m.portalProxy.ListEndpoints() if err != nil { - log.Errorf("Chart Repostiory Startup: Unable to sync repositories: %v+", err) + log.Errorf("Chart Repository Startup: Unable to sync repositories: %v+", err) return } helmRepos := make([]string, 0) for _, ep := range endpoints { if ep.CNSIType == helmEndpointType { - helmRepos = append(helmRepos, ep.Name) - - // Is this an endpoint that we don't have charts for ? - if !arrayContainsString(repos, ep.Name) { - log.Infof("Syncing helm repository to chart store: %s", ep.Name) - m.Sync(interfaces.EndpointRegisterAction, ep) + if ep.SubType == helmRepoEndpointType { + helmRepos = append(helmRepos, ep.Name) + + // Is this an endpoint that we don't have charts for ? + if !arrayContainsString(repos, ep.Name) { + log.Infof("Syncing helm repository to chart store: %s", ep.Name) + m.Sync(interfaces.EndpointRegisterAction, ep) + } + } else { + metadata := "{}" + m.portalProxy.UpdateEndpointMetadata(ep.GUID, metadata) } } } @@ -195,7 +212,7 @@ func (m *Monocular) ConfigureChartSVC(fdbURL *string, fDB *string, cACertFile st } func (m *Monocular) OnEndpointNotification(action interfaces.EndpointAction, endpoint *interfaces.CNSIRecord) { - if endpoint.CNSIType == helmEndpointType { + if endpoint.CNSIType == helmEndpointType && endpoint.SubType == helmRepoEndpointType { m.Sync(action, endpoint) } } @@ -222,15 +239,57 @@ func (m *Monocular) AddAdminGroupRoutes(echoGroup *echo.Group) { // AddSessionGroupRoutes adds the session routes for this plugin to the Echo server func (m *Monocular) AddSessionGroupRoutes(echoGroup *echo.Group) { + // Requests to Monocular Instances + echoGroup.Any("/chartsvc/*", m.handleAPI) + // Reach out to a monocular instance other than Stratos (like helm hub). This is usually done via `x-cap-cnsi-list` + // however cannot be done for things like img src + echoGroup.Any("/monocular/:guid/chartsvc/*", m.handleMonocularInstance) + // API for Helm Chart Repositories echoGroup.GET("/chartrepos", m.ListRepos) - echoGroup.Any("/chartsvc/*", m.handleAPI) echoGroup.POST("/chartrepos/status", m.GetRepoStatuses) echoGroup.POST("/chartrepos/:guid", m.SyncRepo) } +// isExternalMonocularRequest .. Should this request go out to an external monocular instance? IF so returns external monocular endpoint +func (m *Monocular) isExternalMonocularRequest(c echo.Context) (*interfaces.CNSIRecord, error) { + cnsiList := strings.Split(c.Request().Header.Get("x-cap-cnsi-list"), ",") + + // If this has a cnsi then test if it for an external monocular instance + if len(cnsiList) == 1 && len(cnsiList[0]) > 0 { + return m.validateExternalMonocularEndpoint(cnsiList[0]) + } + + return nil, nil +} + +// validateExternalMonocularEndpoint .. Is this endpoint related to an external moncular instance (not stratos's) +func (m *Monocular) validateExternalMonocularEndpoint(cnsi string) (*interfaces.CNSIRecord, error) { + endpoint, err := m.portalProxy.GetCNSIRecord(cnsi) + if err != nil { + err := errors.New("Failed to fetch endpoint") + return nil, echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + + if endpoint.CNSIType == helmEndpointType && endpoint.SubType != helmRepoEndpointType { + return &endpoint, nil + } + + return nil, nil +} + // Forward requests to the Chart Service API func (m *Monocular) handleAPI(c echo.Context) error { + externalMonocularEndpoint, err := m.isExternalMonocularRequest(c) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + + // If this request is associated with an external monocular instance forward the request on to it + if externalMonocularEndpoint != nil { + return m.baseHandleMonocularInstance(c, externalMonocularEndpoint) + } + // Modify the path to remove our prefix for the Chart Service API path := c.Request().URL.Path log.Debugf("URL to chartsvc requested: %v", path) @@ -242,3 +301,150 @@ func (m *Monocular) handleAPI(c echo.Context) error { m.chartSvcRoutes.ServeHTTP(c.Response().Writer, c.Request()) return nil } + +func (m *Monocular) handleMonocularInstance(c echo.Context) error { + log.Debug("handleMonocularInstance") + guid := c.Param("guid") + monocularEndpoint, err := m.validateExternalMonocularEndpoint(guid) + if monocularEndpoint == nil || err != nil { + err := errors.New("No monocular endpoint") + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + + return m.baseHandleMonocularInstance(c, monocularEndpoint) +} + +func removeBreakingHeaders(oldRequest, emptyRequest *http.Request) { + for k, v := range oldRequest.Header { + switch { + // Skip these + // - "Referer" causes CF to fail with a 403 + // - "Connection", "X-Cap-*" and "Cookie" are consumed by us + // - "Accept-Encoding" must be excluded otherwise the transport will expect us to handle the encoding/compression + // - X-Forwarded-* headers - these will confuse Cloud Foundry in some cases (e.g. load balancers) + case k == "Connection", k == "Cookie", k == "Referer", k == "Accept-Encoding", + strings.HasPrefix(strings.ToLower(k), "x-cap-"), + strings.HasPrefix(strings.ToLower(k), "x-forwarded-"): + + // Forwarding everything else + default: + emptyRequest.Header[k] = v + } + } +} + +// baseHandleMonocularInstance .. Forward request to monocular of endpoint +func (m *Monocular) baseHandleMonocularInstance(c echo.Context, monocularEndpoint *interfaces.CNSIRecord) error { + log.Debug("baseHandleMonocularInstance") + // Generic proxy is handled last, after plugins. + + if monocularEndpoint == nil { + err := errors.New("No monocular endpoint") + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + + // We should be able to use `DoProxySingleRequest`, which goes through to `doRequest`, however the actual forwarded request is handled + // by the 'authHandler' associated with the endpoint OR defaults to an OAuth request. For this case there's no auth at all so falls over. + // Tracked in https://github.com/SUSE/stratos/issues/466 + + url := monocularEndpoint.APIEndpoint + path := c.Request().URL.Path + log.Debug("URL to monocular requested: %v", path) + if strings.Index(path, stratosPrefix) == 0 { + // drop stratos pp/v1 + path = path[len(stratosPrefix)-1:] + + // drop leading slash + if path[0] == '/' { + path = path[1:] + } + + // drop monocular/:guid + parts := strings.Split(path, "/") + if parts[0] == "monocular" { + parts = parts[2:] + } + + // Bring all back together + url.Path += "/" + strings.Join(parts, "/") + } + log.Debugf("URL to monocular: %v", url.String()) + + req, err := http.NewRequest(c.Request().Method, url.String(), nil) + + removeBreakingHeaders(c.Request(), req) + + client := &http.Client{Timeout: 30 * time.Second} + res, err := client.Do(req) + + if err != nil { + c.Response().Status = 500 + c.Response().Write([]byte(err.Error())) + } else if res.Body != nil { + c.Response().Status = res.StatusCode + c.Response().Header().Set("Content-Type", res.Header.Get("Content-Type")) + body, _ := ioutil.ReadAll(res.Body) + c.Response().Write(body) + defer res.Body.Close() + } else { + c.Response().Status = 200 + } + + return nil +} + +// GetChartDownloadUrl ... Get the download url for the bits required to install the given chart +func (m *Monocular) GetChartDownloadUrl(monocularEndpoint, chartID, version string) (string, error) { + if len(monocularEndpoint) > 0 { + // Fetch the monocular endpoint for the url + endpoint, err := m.validateExternalMonocularEndpoint(monocularEndpoint) + if err != nil { + return "", err + } + url := endpoint.APIEndpoint + + // Fetch the chart, this will give us the url to download the bits + url.Path += "/chartsvc/v1/charts/" + chartID + "/versions/" + version + req, err := http.NewRequest(http.MethodGet, url.String(), nil) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + client := &http.Client{Timeout: 30 * time.Second} + res, err := client.Do(req) + if err != nil { + return "", err + } else if res.StatusCode >= 400 { + return "", fmt.Errorf("Couldn't download monocular chart (%+v) from '%+v'", res.StatusCode, req.URL) + } else if res.Body != nil { + body, _ := ioutil.ReadAll(res.Body) + defer res.Body.Close() + + // Reach into the chart response for the download URL + chartVersionResponse := &HelmHubChartResponse{} + err := json.Unmarshal(body, chartVersionResponse) + if err != nil { + return "", err + } + if len(chartVersionResponse.Data.Attributes.URLs) < 1 { + return "", errors.New("Response contained no chart package urls") + } + return chartVersionResponse.Data.Attributes.URLs[0], err + } else { + return "", errors.New("No body in response to chart request") + } + } else { + store := m.RepoQueryStore + chart, err := store.GetChart(chartID) + if err != nil { + return "", errors.New("Could not find Chart") + } + + // Find the download URL for the version + for _, chartVersion := range chart.ChartVersions { + if chartVersion.Version == version { + if len(chartVersion.URLs) == 1 { + return chartVersion.URLs[0], nil + } + } + } + return "", errors.New("Could not find Chart Version") + } +} From 82c99982f5ad2669fa2326708ff936847fa80507 Mon Sep 17 00:00:00 2001 From: Richard Cox <richard-cox@users.noreply.github.com> Date: Fri, 11 Sep 2020 16:55:03 +0100 Subject: [PATCH 612/648] Fix valid state of helm install & fixes missing from helm hub pr (#472) * Add fixes missed from helm hub PR * Fix valid state of install helm stepper next button on missing namespace --- .../custom/helm/create-release/create-release.component.ts | 2 +- .../suse-extensions/src/custom/helm/store/helm.effects.ts | 4 +++- .../workloads/release/tabs/helm-release-helper.service.ts | 3 ++- .../helm-release-history-tab.component.ts | 4 +++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.ts index c57b1175cf..26ccf5c874 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.ts @@ -89,7 +89,7 @@ export class CreateReleaseComponent implements OnInit, OnDestroy { this.details = new FormGroup({ endpoint: new FormControl('', Validators.required), releaseName: new FormControl('', Validators.required), - releaseNamespace: new FormControl(''), + releaseNamespace: new FormControl('', Validators.required), createNamespace: new FormControl(false), }); this.details.controls.createNamespace.disable(); diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts index 440f452122..c963c2fa7e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts @@ -218,7 +218,9 @@ export class HelmEffects { }, base); return processedData; }, [], { - 'x-cap-cnsi-list': action.monocularEndpoint !== stratosMonocularEndpointGuid ? action.monocularEndpoint : '' + 'x-cap-cnsi-list': action.monocularEndpoint && action.monocularEndpoint !== stratosMonocularEndpointGuid ? + action.monocularEndpoint : + '' }); }) ); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts index 99899a0b92..d3af46d384 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts @@ -14,6 +14,7 @@ import { HelmReleaseGraph, HelmReleaseGuid, HelmReleaseResources, + HelmReleaseRevision, } from '../../workload.types'; import { workloadsEntityCatalog } from '../../workloads-entity-catalog'; @@ -147,7 +148,7 @@ export class HelmReleaseHelperService { ); } - public fetchReleaseHistory(): Observable<any> { + public fetchReleaseHistory(): Observable<HelmReleaseRevision[]> { // Get the history for a Helm release return workloadsEntityCatalog.history.store.getEntityService( this.releaseTitle, diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.ts index df49f034bc..2e0ada870e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.ts @@ -74,7 +74,9 @@ export class HelmReleaseHistoryTabComponent { }, ]; - const data$ = this.helmReleaseHelper.fetchReleaseHistory(); + const data$ = this.helmReleaseHelper.fetchReleaseHistory().pipe( + map(history => [...history].sort((a, b) => b.revision - a.revision)) + ); this.dataSource = { connect: () => data$, disconnect: () => { }, From b0e37981f7627941c3fc5b20f1f7d15e4f9964e4 Mon Sep 17 00:00:00 2001 From: Richard Cox <richard-cox@users.noreply.github.com> Date: Fri, 11 Sep 2020 16:55:59 +0100 Subject: [PATCH 613/648] Workloads List: Show chart version instead of release version (#474) --- .../helm-release-card/helm-release-card.component.html | 4 ++-- .../list-types/helm-releases-list-config.service.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.html index ad120d3cea..8dde7f3341 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.html @@ -29,9 +29,9 @@ </app-meta-card-value> </app-meta-card-item> <app-meta-card-item> - <app-meta-card-key>Version</app-meta-card-key> + <app-meta-card-key>Chart Version</app-meta-card-key> <app-meta-card-value> - {{ row.version }} + {{ row.chart.metadata.version }} </app-meta-card-value> </app-meta-card-item> <app-meta-card-item> diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-releases-list-config.service.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-releases-list-config.service.ts index 9e2b68e4b8..3ab9f96d4d 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-releases-list-config.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-releases-list-config.service.ts @@ -88,14 +88,14 @@ export class HelmReleasesListConfig implements IListConfig<HelmRelease> { }, { columnId: 'version', - headerCell: () => 'Version', + headerCell: () => 'Chart Version', cellDefinition: { - valuePath: 'version' + valuePath: 'chart.metadata.version' }, sort: { type: 'sort', orderKey: 'version', - field: 'version' + field: 'chart.metadata.version' }, cellFlex: '1' }, From 2c3ccbd4d34454ba549648ddc0c70d607000f378 Mon Sep 17 00:00:00 2001 From: Richard Cox <richard-cox@users.noreply.github.com> Date: Mon, 14 Sep 2020 11:36:59 +0100 Subject: [PATCH 614/648] Login Screen: Hide the form during initial verify (#476) - on fresh load, not logged in, the form is shown once verify fails - on load when logged in, form does not flash up --- .../login/login-page/login-page.component.ts | 49 ++++++++++++------- .../suse-login/suse-login.component.html | 10 ++-- .../suse-login/suse-login.component.scss | 4 ++ 3 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/frontend/packages/core/src/features/login/login-page/login-page.component.ts b/src/frontend/packages/core/src/features/login/login-page/login-page.component.ts index 8764ac68f0..3a4d78324d 100644 --- a/src/frontend/packages/core/src/features/login/login-page/login-page.component.ts +++ b/src/frontend/packages/core/src/features/login/login-page/login-page.component.ts @@ -29,6 +29,7 @@ export class LoginPageComponent implements OnInit, OnDestroy { loggedIn: boolean; loggingIn: boolean; + isLoginFlow: boolean; verifying: boolean; error: boolean; @@ -36,6 +37,7 @@ export class LoginPageComponent implements OnInit, OnDestroy { ssoOptions: string; busy$: Observable<boolean>; + initialLoad$: Observable<boolean>; redirect: RouterRedirect; @@ -47,29 +49,37 @@ export class LoginPageComponent implements OnInit, OnDestroy { this.ssoLogin = false; this.store.dispatch(new VerifySession()); const auth$ = this.store.select(s => ({ auth: s.auth, endpoints: s.endpoints })); + this.initialLoad$ = auth$.pipe( + map(({ auth, }) => + auth.verifying && !auth.loggingIn || // checking if user is logged in but not in processed of logging in + (auth.sessionData && auth.sessionData.valid) && !this.isLoginFlow // logged in but haven't hit log in + ), + ); + this.busy$ = auth$.pipe( - map( - ({ auth, endpoints }) => !auth.error || !(auth.sessionData && auth.sessionData.valid) && - (auth.sessionData && auth.sessionData.valid) || auth.verifying || auth.loggingIn || endpoints.loading + map(({ auth, endpoints }) => + !auth.error || + !(auth.sessionData && auth.sessionData.valid) && (auth.sessionData && auth.sessionData.valid) || + auth.verifying || + auth.loggingIn || + endpoints.loading ), startWith(true) ); - this.subscription = - auth$ - .pipe( - tap(({ auth }) => { - this.redirect = auth.redirect; - this.handleOther(auth); - }), - takeWhile(({ auth }) => { - const loggedIn = !auth.loggingIn && auth.loggedIn; - const validSession = auth.sessionData && auth.sessionData.valid; - return !(loggedIn && validSession); - }), - ) - .subscribe({ - complete: () => this.handleSuccess() - }); + this.subscription = auth$.pipe( + tap(({ auth }) => { + this.redirect = auth.redirect; + this.handleOther(auth); + }), + takeWhile(({ auth }) => { + const loggedIn = !auth.loggingIn && auth.loggedIn; + const validSession = auth.sessionData && auth.sessionData.valid; + return !(loggedIn && validSession); + }), + ) + .subscribe({ + complete: () => this.handleSuccess() + }); } ngOnDestroy() { @@ -107,6 +117,7 @@ export class LoginPageComponent implements OnInit, OnDestroy { private handleOther(auth: AuthState) { this.loggedIn = auth.loggedIn; this.loggingIn = auth.loggingIn; + this.isLoginFlow = this.isLoginFlow || auth.loggingIn; this.verifying = auth.verifying; this.ssoOptions = auth.sessionData && auth.sessionData.ssoOptions; this.ssoLogin = !!this.ssoOptions; diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.html b/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.html index 57577ea756..e7155c7e67 100644 --- a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.html @@ -1,4 +1,4 @@ -<div class="suse-login" id="app-login-page" [ngClass]="{'suse-login-sso': ssoLogin}" > +<div class="suse-login" id="app-login-page" [ngClass]="{'suse-login-sso': ssoLogin}"> <div class="suse-login__content"> <div class="suse-login__intro"> <img class="suse-login__intro-img-left" src="/core/assets/custom/login/suse-logo-light.svg"> @@ -26,7 +26,7 @@ <h1 class="suse-login__headline">SUSE<br>Stratos Console</h1> </div> <div class="suse-login__box" [ngClass]="{'suse-login__busy': busy$ | async }"> - <div *ngIf="!ssoLogin" class="suse-login__form-title">Sign in</div> + <div *ngIf="!ssoLogin" class="suse-login__form-title" [ngClass]="{'hidden': initialLoad$ | async}">Sign in</div> <ng-container *ngTemplateOutlet="loginform"></ng-container> </div> </div> @@ -34,7 +34,7 @@ <h1 class="suse-login__headline">SUSE<br>Stratos Console</h1> </div> <ng-template #loginform> - <div class="suse-login__form-outer"> + <div class="suse-login__form-outer" [ngClass]="{'hidden': initialLoad$ | async}"> <form class="suse-login__form" name="loginForm" (ngSubmit)="login()" #loginForm="ngForm"> <mat-form-field *ngIf="!ssoLogin" [hideRequiredMarker]="true"> <input matInput required [(ngModel)]="username" name="username" placeholder="Username"> @@ -42,8 +42,8 @@ <h1 class="suse-login__headline">SUSE<br>Stratos Console</h1> <mat-form-field *ngIf="!ssoLogin" [hideRequiredMarker]="true"> <input matInput required type="password" [(ngModel)]="password" name="password" placeholder="Password"> </mat-form-field> - <button class="suse-login__submit" [ngClass]="{'suse-login__sso-button': ssoLogin}" color="primary" *ngIf="!loggedIn" type="submit" mat-button - mat-raised-button [disabled]="!loginForm.valid"> + <button class="suse-login__submit" [ngClass]="{'suse-login__sso-button': ssoLogin}" color="primary" + *ngIf="!loggedIn" type="submit" mat-button mat-raised-button [disabled]="!loginForm.valid"> {{ !ssoLogin ? 'Login' : 'Login with SSO' }} </button> </form> diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss b/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss index a7b3338ac5..aaf7e88a7a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss @@ -172,6 +172,10 @@ app-suse-login { padding: 0 60px; } + .hidden { + visibility: hidden; + } + } // If SSO Login change the presentation From 2122aae82080617e7863aad8d424996b7a900a4b Mon Sep 17 00:00:00 2001 From: Richard Cox <richard-cox@users.noreply.github.com> Date: Mon, 14 Sep 2020 17:30:21 +0100 Subject: [PATCH 615/648] Fix SSO Error message & helm chart list on helm hub register/unregister (#477) * Fix sso warning message * Reset helm pagination sections on unregister helm endpoint * Reset helm chart pagination on helm hub register --- .../login-page.component.theme.scss | 8 ++++ .../src/custom/helm/store/helm.effects.ts | 47 +++++++++++++++++-- .../suse-login/suse-login.component.scss | 5 +- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/src/frontend/packages/core/src/features/login/login-page/login-page.component.theme.scss b/src/frontend/packages/core/src/features/login/login-page/login-page.component.theme.scss index 9a682c63c1..e6a56746e2 100644 --- a/src/frontend/packages/core/src/features/login/login-page/login-page.component.theme.scss +++ b/src/frontend/packages/core/src/features/login/login-page/login-page.component.theme.scss @@ -1,5 +1,7 @@ @mixin login-page-theme($theme, $app-theme) { $primary: map-get($theme, primary); + $warn: map-get($theme, warn); + .login { background-color: map-get($app-theme, app-background-color); &__title { @@ -9,4 +11,10 @@ color: mat-contrast($primary, 500); } } + + .suse-login-sso { + .suse-login__message { + color: mat-color($warn); + } + } } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts index c963c2fa7e..351e05ffc1 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts @@ -4,17 +4,26 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { Actions, Effect, ofType } from '@ngrx/effects'; import { Action, Store } from '@ngrx/store'; import { combineLatest, Observable, of } from 'rxjs'; -import { catchError, flatMap, map, mergeMap, withLatestFrom } from 'rxjs/operators'; +import { catchError, first, flatMap, map, mergeMap, withLatestFrom } from 'rxjs/operators'; import { environment } from '../../../../../core/src/environments/environment'; -import { GET_ENDPOINTS_SUCCESS, GetAllEndpointsSuccess } from '../../../../../store/src/actions/endpoint.actions'; -import { ClearPaginationOfType } from '../../../../../store/src/actions/pagination.actions'; +import { + EndpointActionComplete, + GET_ENDPOINTS_SUCCESS, + GetAllEndpointsSuccess, + REGISTER_ENDPOINTS_SUCCESS, + UNREGISTER_ENDPOINTS_SUCCESS, + UnregisterEndpoint, +} from '../../../../../store/src/actions/endpoint.actions'; +import { ClearPaginationOfType, ResetPaginationOfType } from '../../../../../store/src/actions/pagination.actions'; import { AppState } from '../../../../../store/src/app-state'; import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; import { isJetstreamError } from '../../../../../store/src/jetstream'; import { ApiRequestTypes } from '../../../../../store/src/reducers/api-request-reducer/request-helpers'; import { endpointOfTypeSelector } from '../../../../../store/src/selectors/endpoint.selectors'; +import { stratosEntityCatalog } from '../../../../../store/src/stratos-entity-catalog'; import { NormalizedResponse } from '../../../../../store/src/types/api.types'; +import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; import { EntityRequestAction, StartRequestAction, @@ -276,6 +285,38 @@ export class HelmEffects { }) ); + @Effect() + endpointUnregister$ = this.actions$.pipe( + ofType<UnregisterEndpoint>(UNREGISTER_ENDPOINTS_SUCCESS), + flatMap(action => stratosEntityCatalog.endpoint.store.getEntityMonitor(action.guid).entity$.pipe( + first(), + mergeMap(endpoint => { + if (endpoint.cnsi_type !== HELM_ENDPOINT_TYPE) { + return []; + } + return [ + new ResetPaginationOfType(helmEntityCatalog.chart.getSchema()), + new ResetPaginationOfType(helmEntityCatalog.chartVersions.getSchema()), + new ResetPaginationOfType(helmEntityCatalog.version.getSchema()), + ]; + }) + )) + ); + + @Effect() + registerEndpoint$ = this.actions$.pipe( + ofType<EndpointActionComplete>(REGISTER_ENDPOINTS_SUCCESS), + flatMap(action => { + const endpoint: EndpointModel = action.endpoint as EndpointModel; + if (endpoint && endpoint.cnsi_type === HELM_ENDPOINT_TYPE && endpoint.sub_type === HELM_HUB_ENDPOINT_TYPE) { + return [ + new ResetPaginationOfType(helmEntityCatalog.chart.getSchema()), + ]; + } + return []; + }) + ); + private static createHelmErrorMessage(err: any): string { if (err) { if (err.error && err.error.message) { diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss b/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss index aaf7e88a7a..0bab1c84ab 100644 --- a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss @@ -190,8 +190,9 @@ app-suse-login { .suse-login__sso-button.mat-button.suse-login__submit { align-self: flex-start; } - - + .suse-login__message { + padding-top: 0; + } } @media only screen and (max-width: 1120px) { From e568f98e2da239c94330dd1b7c67fb42a2688269 Mon Sep 17 00:00:00 2001 From: Neil MacDougall <nwmac@users.noreply.github.com> Date: Mon, 14 Sep 2020 18:06:51 +0100 Subject: [PATCH 616/648] Simpler Helm Chart store (#467) * Update go.sum * Replace foundation DB with sql db and file cache * Tidy ups and final tweaks to get versions working * Tidy up build and remove fdb * Update .gitignore * Remove unused functions * FIx upgrade and add in sources * Fix unit test * Remove fdb helm chart values * Fix default cache folder name * Remove unused commented code * Fix comments * Formatting * Tidy ups * Add missing import following refactor * Fix issues with async delete and edit * Tidy up chartName * Fix HelmChartID rename to HelmChartReference * FIx backend merge issues * Set volume for helm cache when deployed to k8s * Fix fro kuberenetes deployment * Ensure db statements aer modified for different DBs * Address PR feedback * Fix missing param to error log msg * Add support for retrieving icon for a specific version * Use icon for the chart version. Reduce loading indicators * Bug fix for icon on list view * Fix icons on Helm Hub * Address final PR feedback --- .cfignore | 1 + .gitignore | 1 + deploy/Dockerfile.all-in-one | 105 +-- deploy/aio-entrypoint.sh | 26 - .../all-in-one/config.all-in-one.properties | 3 +- deploy/all-in-one/dig.sh | 8 - deploy/ci/suse-console-dev-releases.yml | 51 +- deploy/cloud-foundry/config.properties | 4 +- .../monocular/fdb-doclayer/Dockerfile | 64 -- .../fdb-doc-layer-networkpolicy.yaml | 21 - .../monocular/fdb-doclayer/fdbdoc.bash | 75 -- .../monocular/fdb-server/Dockerfile | 89 -- .../monocular/fdb-server/configure_db.bash | 45 - .../fdb-server/create_cluster_file.bash | 61 -- .../fdb-server/create_server_environment.bash | 49 - .../download_multiversion_libraries.bash | 31 - .../containers/monocular/fdb-server/fdb.bash | 46 - .../fdb-server/fdbserver-networkpolicy.yaml | 21 - .../console/templates/chartsync.yaml | 70 -- .../console/templates/deployment.yaml | 12 +- .../console/templates/fdb-secrets.yaml | 27 - .../templates/fdbdoclayer/deployment.yaml | 123 --- .../fdbdoclayer/fdb-doc-layer-service.yaml | 33 - deploy/kubernetes/console/values.yaml | 3 - deploy/kubernetes/custom/__stratos.tpl | 10 - deploy/kubernetes/custom/custom-build.sh | 10 - .../stepper/steppers/steppers.component.ts | 2 +- .../create-release.component.spec.ts | 9 +- .../create-release.component.ts | 59 +- .../src/custom/helm/helm.routing.ts | 4 +- .../chart-details-readme.component.ts | 5 +- .../chart-details.component.html | 2 +- .../chart-details/chart-details.component.ts | 13 +- .../shared/services/charts.service.ts | 12 +- .../src/custom/helm/store/helm.types.ts | 39 +- .../upgrade-release.component.ts | 5 +- .../kubernetes/workloads/workload.utils.ts | 9 + src/jetstream/cnsi.go | 7 + src/jetstream/config.dev | 3 + src/jetstream/config.example | 10 +- src/jetstream/go.mod | 8 +- src/jetstream/go.sum | 19 + src/jetstream/plugins/kubernetes/go.mod | 2 - .../plugins/kubernetes/install_release.go | 53 +- .../monocular/20200819184800_ChartStore.go | 49 + src/jetstream/plugins/monocular/README.md | 82 -- src/jetstream/plugins/monocular/README.txt | 13 - src/jetstream/plugins/monocular/cache.go | 341 +++++++ .../plugins/monocular/chart-repo/.gitignore | 1 - .../plugins/monocular/chart-repo/Dockerfile | 12 - .../plugins/monocular/chart-repo/Makefile | 8 - .../monocular/chart-repo/api_endpoint.go | 323 ------- .../monocular/chart-repo/chart_repo.go | 64 -- .../chart-repo/common/chart_utils.go | 205 ----- .../monocular/chart-repo/common/types.go | 162 ---- .../plugins/monocular/chart-repo/delete.go | 39 - .../chart-repo/foundationdb/datastore.go | 121 --- .../chart-repo/foundationdb/delete.go | 66 -- .../chart-repo/foundationdb/mockstore.go | 82 -- .../monocular/chart-repo/foundationdb/sync.go | 70 -- .../chart-repo/foundationdb/utils.go | 531 ----------- .../chart-repo/foundationdb/utils_test.go | 696 -------------- .../plugins/monocular/chart-repo/go.mod | 37 - .../plugins/monocular/chart-repo/go.sum | 230 ----- .../plugins/monocular/chart-repo/serve.go | 67 -- .../plugins/monocular/chart-repo/sync.go | 39 - .../chart-repo/testdata/empty-repo-index.yaml | 1 - .../chart-repo/testdata/valid-index.yaml | 69 -- .../monocular/chart-repo/utils/version.go | 51 -- .../monocular/{chartsvc/models => }/chart.go | 42 +- src/jetstream/plugins/monocular/chart_svc.go | 283 ++++++ .../plugins/monocular/chartsvc/Dockerfile | 9 - .../plugins/monocular/chartsvc/Makefile | 6 - .../plugins/monocular/chartsvc/README.md | 6 - .../plugins/monocular/chartsvc/datastore.go | 41 - .../foundationdb/datastore/datastore.go | 146 --- .../foundationdb/datastore/mockstore.go | 87 -- .../chartsvc/foundationdb/handler.go | 541 ----------- .../chartsvc/foundationdb/handler_test.go | 867 ------------------ .../plugins/monocular/chartsvc/go.mod | 31 - .../plugins/monocular/chartsvc/go.sum | 139 --- .../plugins/monocular/chartsvc/main.go | 168 ---- .../plugins/monocular/chartsvc/main_test.go | 544 ----------- .../monocular/chartsvc/utils/testutils.go | 41 - .../plugins/monocular/chartsvc_main.txt | 15 - .../plugins/monocular/git-merge-subpath.sh | 65 -- .../plugins/monocular/git-pull-downstream.sh | 77 -- .../plugins/monocular/git-push-upstream.sh | 82 -- src/jetstream/plugins/monocular/go.mod | 13 +- src/jetstream/plugins/monocular/go.sum | 10 + src/jetstream/plugins/monocular/main.go | 296 ++---- src/jetstream/plugins/monocular/repository.go | 41 +- .../{chartsvc/utils => }/responses.go | 14 +- .../plugins/monocular/store/chart_store_db.go | 211 +++++ src/jetstream/plugins/monocular/store/main.go | 28 + .../plugins/monocular/store/types.go | 38 + .../plugins/monocular/store/version.go | 60 ++ .../plugins/monocular/sync-source.sh | 38 - src/jetstream/plugins/monocular/sync.go | 238 ++--- .../plugins/monocular/sync_worker.go | 139 +++ src/jetstream/plugins/monocular/types.go | 70 ++ .../plugins/monocular/utils/urlpoll.go | 144 --- .../repository/interfaces/endpoints.go | 5 + 103 files changed, 1585 insertions(+), 7559 deletions(-) delete mode 100644 deploy/aio-entrypoint.sh delete mode 100644 deploy/all-in-one/dig.sh delete mode 100644 deploy/containers/monocular/fdb-doclayer/Dockerfile delete mode 100644 deploy/containers/monocular/fdb-doclayer/fdb-doc-layer-networkpolicy.yaml delete mode 100755 deploy/containers/monocular/fdb-doclayer/fdbdoc.bash delete mode 100644 deploy/containers/monocular/fdb-server/Dockerfile delete mode 100755 deploy/containers/monocular/fdb-server/configure_db.bash delete mode 100644 deploy/containers/monocular/fdb-server/create_cluster_file.bash delete mode 100644 deploy/containers/monocular/fdb-server/create_server_environment.bash delete mode 100644 deploy/containers/monocular/fdb-server/download_multiversion_libraries.bash delete mode 100644 deploy/containers/monocular/fdb-server/fdb.bash delete mode 100644 deploy/containers/monocular/fdb-server/fdbserver-networkpolicy.yaml delete mode 100644 deploy/kubernetes/console/templates/chartsync.yaml delete mode 100644 deploy/kubernetes/console/templates/fdb-secrets.yaml delete mode 100644 deploy/kubernetes/console/templates/fdbdoclayer/deployment.yaml delete mode 100644 deploy/kubernetes/console/templates/fdbdoclayer/fdb-doc-layer-service.yaml create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workload.utils.ts create mode 100644 src/jetstream/plugins/monocular/20200819184800_ChartStore.go delete mode 100644 src/jetstream/plugins/monocular/README.md delete mode 100644 src/jetstream/plugins/monocular/README.txt create mode 100644 src/jetstream/plugins/monocular/cache.go delete mode 100644 src/jetstream/plugins/monocular/chart-repo/.gitignore delete mode 100644 src/jetstream/plugins/monocular/chart-repo/Dockerfile delete mode 100644 src/jetstream/plugins/monocular/chart-repo/Makefile delete mode 100644 src/jetstream/plugins/monocular/chart-repo/api_endpoint.go delete mode 100644 src/jetstream/plugins/monocular/chart-repo/chart_repo.go delete mode 100644 src/jetstream/plugins/monocular/chart-repo/common/chart_utils.go delete mode 100644 src/jetstream/plugins/monocular/chart-repo/common/types.go delete mode 100644 src/jetstream/plugins/monocular/chart-repo/delete.go delete mode 100644 src/jetstream/plugins/monocular/chart-repo/foundationdb/datastore.go delete mode 100644 src/jetstream/plugins/monocular/chart-repo/foundationdb/delete.go delete mode 100644 src/jetstream/plugins/monocular/chart-repo/foundationdb/mockstore.go delete mode 100644 src/jetstream/plugins/monocular/chart-repo/foundationdb/sync.go delete mode 100644 src/jetstream/plugins/monocular/chart-repo/foundationdb/utils.go delete mode 100644 src/jetstream/plugins/monocular/chart-repo/foundationdb/utils_test.go delete mode 100644 src/jetstream/plugins/monocular/chart-repo/go.mod delete mode 100644 src/jetstream/plugins/monocular/chart-repo/go.sum delete mode 100644 src/jetstream/plugins/monocular/chart-repo/serve.go delete mode 100644 src/jetstream/plugins/monocular/chart-repo/sync.go delete mode 100644 src/jetstream/plugins/monocular/chart-repo/testdata/empty-repo-index.yaml delete mode 100644 src/jetstream/plugins/monocular/chart-repo/testdata/valid-index.yaml delete mode 100644 src/jetstream/plugins/monocular/chart-repo/utils/version.go rename src/jetstream/plugins/monocular/{chartsvc/models => }/chart.go (57%) create mode 100644 src/jetstream/plugins/monocular/chart_svc.go delete mode 100644 src/jetstream/plugins/monocular/chartsvc/Dockerfile delete mode 100644 src/jetstream/plugins/monocular/chartsvc/Makefile delete mode 100644 src/jetstream/plugins/monocular/chartsvc/README.md delete mode 100644 src/jetstream/plugins/monocular/chartsvc/datastore.go delete mode 100644 src/jetstream/plugins/monocular/chartsvc/foundationdb/datastore/datastore.go delete mode 100644 src/jetstream/plugins/monocular/chartsvc/foundationdb/datastore/mockstore.go delete mode 100644 src/jetstream/plugins/monocular/chartsvc/foundationdb/handler.go delete mode 100644 src/jetstream/plugins/monocular/chartsvc/foundationdb/handler_test.go delete mode 100644 src/jetstream/plugins/monocular/chartsvc/go.mod delete mode 100644 src/jetstream/plugins/monocular/chartsvc/go.sum delete mode 100644 src/jetstream/plugins/monocular/chartsvc/main.go delete mode 100644 src/jetstream/plugins/monocular/chartsvc/main_test.go delete mode 100644 src/jetstream/plugins/monocular/chartsvc/utils/testutils.go delete mode 100644 src/jetstream/plugins/monocular/chartsvc_main.txt delete mode 100755 src/jetstream/plugins/monocular/git-merge-subpath.sh delete mode 100755 src/jetstream/plugins/monocular/git-pull-downstream.sh delete mode 100755 src/jetstream/plugins/monocular/git-push-upstream.sh rename src/jetstream/plugins/monocular/{chartsvc/utils => }/responses.go (85%) create mode 100644 src/jetstream/plugins/monocular/store/chart_store_db.go create mode 100644 src/jetstream/plugins/monocular/store/main.go create mode 100644 src/jetstream/plugins/monocular/store/types.go create mode 100644 src/jetstream/plugins/monocular/store/version.go delete mode 100755 src/jetstream/plugins/monocular/sync-source.sh create mode 100644 src/jetstream/plugins/monocular/sync_worker.go create mode 100644 src/jetstream/plugins/monocular/types.go delete mode 100644 src/jetstream/plugins/monocular/utils/urlpoll.go diff --git a/.cfignore b/.cfignore index 92be10a235..4775f324c9 100644 --- a/.cfignore +++ b/.cfignore @@ -22,3 +22,4 @@ docs/ build/dev_config.json e2e-reports/ website/ +.helm-cache/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 730c756be1..ac5cbdb218 100644 --- a/.gitignore +++ b/.gitignore @@ -96,6 +96,7 @@ src/jetstream/config.properties src/jetstream/db/dbconf.yml src/jetstream/plugins/monocular/chart-repo/chartrepo src/jetstream/plugins/analysis/container/analyzers +src/jetstream/.helm-cache # Customisations - these can be removed in the future # Left in for now to prevent these files being checked-in, if they are still present diff --git a/deploy/Dockerfile.all-in-one b/deploy/Dockerfile.all-in-one index 9ff9b1a73a..f9dbdf8adf 100644 --- a/deploy/Dockerfile.all-in-one +++ b/deploy/Dockerfile.all-in-one @@ -16,65 +16,6 @@ RUN deploy/all-in-one/src-build.sh # Generate dev-certs RUN CERTS_PATH=/home/stratos/dev-certs ./generate_cert.sh -# --------------------------------------------------------------------------------------------------- -# Docker build for FDB Server component -FROM splatform/stratos-bk-build-base:leap15_1 as fdbserver-builder - -# Install FoundationDB Binaries -ARG FDB_VERSION=6.2.15 -ARG FDB_WEBSITE=https://www.foundationdb.org - -USER root -WORKDIR /home/stratos/tmp -WORKDIR /home/stratos -RUN pwd && ls -al -RUN curl $FDB_WEBSITE/downloads/$FDB_VERSION/linux/fdb_$FDB_VERSION.tar.gz -o fdb_$FDB_VERSION.tar.gz && \ - tar -xzf fdb_$FDB_VERSION.tar.gz --strip-components=1 && \ - rm fdb_$FDB_VERSION.tar.gz && \ - chmod u+x fdbbackup fdbcli fdbdr fdbmonitor fdbrestore fdbserver backup_agent dr_agent && \ - mv fdbbackup fdbcli fdbdr fdbmonitor fdbrestore fdbserver backup_agent dr_agent /usr/bin - -WORKDIR /var/fdb - -# Install FoundationDB Client Libraries - -ARG FDB_VERSION=6.2.15 -ARG FDB_ADDITIONAL_VERSIONS="5.1.7" -ARG FDB_WEBSITE=https://www.foundationdb.org - -COPY deploy/containers/monocular/fdb-server/download_multiversion_libraries.bash scripts/ - -# Set Up Runtime Scripts and Directories -COPY deploy/containers/monocular/fdb-server/fdb.bash scripts/ -COPY deploy/containers/monocular/fdb-server/create_server_environment.bash scripts/ -COPY deploy/containers/monocular/fdb-server/create_cluster_file.bash scripts/ -COPY deploy/containers/monocular/fdb-server/configure_db.bash scripts/ -RUN chmod u+x scripts/*.bash && \ - mkdir -p logs - -# --------------------------------------------------------------------------------------------------- -# Docker build for FDB Document Layer component -FROM splatform/stratos-bk-build-base:leap15_1 as fdbdoclayer-builder - -# Install FoundationDB Document Layer Binaries -ARG FDB_DOC_VERSION=1.6.3 -ARG FDB_WEBSITE=https://www.foundationdb.org - -WORKDIR /home/stratos -RUN curl $FDB_WEBSITE/downloads/$FDB_DOC_VERSION/linux/fdb-document-layer-$FDB_DOC_VERSION-Linux.tar.gz -o fdb-document-layer-$FDB_DOC_VERSION-Linux.tar.gz && \ - tar -xvf fdb-document-layer-$FDB_DOC_VERSION-Linux.tar.gz && \ - mv fdb-document-layer-$FDB_DOC_VERSION-Linux doclayer - -# --------------------------------------------------------------------------------------------------- -# Docker build for Chart Repo component -FROM splatform/stratos-bk-build-base:leap15_1 as chartrepo-builder - -COPY --chown=stratos:users src/jetstream/plugins/monocular/chart-repo /go/src/github.com/helm/monocular -WORKDIR /go/src/github.com/helm/monocular -ARG VERSION -RUN GO111MODULE=on GOPROXY=https://gocenter.io CGO_ENABLED=0 go build -a -installsuffix cgo -ldflags "-X main.version=$VERSION" . - - # --------------------------------------------------------------------------------------------------- # Final AIO Container # --------------------------------------------------------------------------------------------------- @@ -92,59 +33,15 @@ COPY --from=jetstream-builder /home/stratos/config.properties /srv/config.proper # User Invite templates COPY --from=jetstream-builder /home/stratos/src/jetstream/templates /srv/templates -# Pull in binaries from fdbserver-builder -COPY --from=fdbserver-builder /usr/bin /usr/bin -COPY --from=fdbserver-builder /var/fdb/scripts /var/fdb/scripts - -# Install FoundationDB Client Libraries directly here rather than in the fdbserver-builder, as the install destination depends on an external script. -ARG FDB_VERSION=6.2.15 -ARG FDB_ADDITIONAL_VERSIONS="5.1.7" -ARG FDB_WEBSITE=https://www.foundationdb.org -RUN mkdir -p /mnt/website -RUN curl $FDB_WEBSITE/downloads/$FDB_VERSION/linux/libfdb_c_$FDB_VERSION.so -o /usr/lib64/libfdb_c.so && \ - bash /var/fdb/scripts/download_multiversion_libraries.bash $FDB_WEBSITE $FDB_ADDITIONAL_VERSIONS && \ - rm -rf /mnt/website -VOLUME /var/fdb/data -RUN mkdir -p /var/fdb/logs - -# Pull in binaries from fdbdoclayer-builder -COPY --from=fdbdoclayer-builder /home/stratos/doclayer/bin/fdbdoc /usr/bin/fdbdoc -COPY --from=fdbdoclayer-builder /home/stratos/doclayer/lib/foundationdb/document/fdbmonitor /usr/bin/fdbmonitor -# Bring in doclayer startup script -COPY deploy/containers/monocular/fdb-doclayer/fdbdoc.bash /var/fdb/scripts -RUN chmod u+x /var/fdb/scripts/fdbdoc.bash -# Doclayer startup script dependency -# FoundationDB environment variables -ENV FDB_PORT 4500 -ENV FDB_CLUSTER_FILE /var/fdb/fdb.cluster -# Set to host, since all processes run in single container -ENV FDB_NETWORKING_MODE host -ENV FDB_COORDINATOR_PORT 4500 -ENV FDB_PROCESS_CLASS unset -ENV CLUSTER_ID docker:docker -ENV FDB_COORDINATOR localhost -ENV FDB_DOC_PORT 27016 -ENV FDB_LISTEN_IP 0.0.0.0 - -# Pull in binaries from chartrepo-builder -COPY --from=chartrepo-builder /go/src/github.com/helm/monocular/chartrepo /chartrepo - # Enable persistence features if canary build flag is set RUN if [ "x$CANARY_BUILD" != "x" ] ; then printf "\nFORCE_ENABLE_PERSISTENCE_FEATURES=true\n" >> /srv/config.properties ; fi # Enable tech preview features if canary build flag is set RUN if [ "x$CANARY_BUILD" != "x" ] ; then printf "\nENABLE_TECH_PREVIEW=true\n" >> /srv/config.properties ; fi -# Fix dig to resolve localhost to 127.0.0.1 -RUN mv /usr/bin/dig /usr/bin/digit -COPY deploy/all-in-one/dig.sh /usr/bin/dig -RUN chmod +x /usr/bin/dig - EXPOSE 443 # Need to be root to bind to port 443 USER root -COPY deploy/aio-entrypoint.sh . -RUN chmod +x ./aio-entrypoint.sh -ENTRYPOINT ["bash", "-c", "./aio-entrypoint.sh -u mongodb://localhost:${FDB_DOC_PORT}"] +ENTRYPOINT ["./jetstream"] diff --git a/deploy/aio-entrypoint.sh b/deploy/aio-entrypoint.sh deleted file mode 100644 index 939eea2a85..0000000000 --- a/deploy/aio-entrypoint.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -while getopts ":u:" opt; do - case ${opt} in - u ) - doclayer=$OPTARG - ;; - \? ) echo "Invalid option: $OPTARG" 1>&2 - ;; - : ) - echo "$OPTARG requires a value set to the URL of the document layer." 1>&2 - ;; - esac -done -shift $((OPTIND -1)) - -if [ -z "$doclayer" ]; -then - echo "--doclayer-url must be set." >&2 - exit 1 -fi - -/var/fdb/scripts/fdb.bash & -/var/fdb/scripts/fdbdoc.bash & -/chartrepo serve --doclayer-url=$doclayer & -./jetstream \ No newline at end of file diff --git a/deploy/all-in-one/config.all-in-one.properties b/deploy/all-in-one/config.all-in-one.properties index aab49027df..458d85e2e7 100644 --- a/deploy/all-in-one/config.all-in-one.properties +++ b/deploy/all-in-one/config.all-in-one.properties @@ -14,5 +14,4 @@ STRATOS_DEPLOYMENT_DOCKER_AIO=true SKIP_SSL_VALIDATION=true SQLITE_KEEP_DB=true TEMPLATE_DIR=./templates -FDB_URL=mongodb://localhost:27016 -SYNC_SERVER_URL=http://localhost:8080 \ No newline at end of file +HELM_CACHE_FOLDER=./helm-cache \ No newline at end of file diff --git a/deploy/all-in-one/dig.sh b/deploy/all-in-one/dig.sh deleted file mode 100644 index 82fb0a6fe6..0000000000 --- a/deploy/all-in-one/dig.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -HOST="${@: -1}" -if [ "$HOST" == "localhost" ]; then - echo "127.0.0.1" -else - /usr/bin/digit $@ -fi diff --git a/deploy/ci/suse-console-dev-releases.yml b/deploy/ci/suse-console-dev-releases.yml index be33e148f5..eb40b5b8c0 100644 --- a/deploy/ci/suse-console-dev-releases.yml +++ b/deploy/ci/suse-console-dev-releases.yml @@ -47,24 +47,6 @@ resources: username: ((docker-username)) password: ((docker-password)) repository: ((docker-repository))/stratos-console -- name: fdbserver-image - type: docker-image - source: - username: ((docker-username)) - password: ((docker-password)) - repository: ((docker-repository))/stratos-fdbserver -- name: fdbdoclayer-image - type: docker-image - source: - username: ((docker-username)) - password: ((docker-password)) - repository: ((docker-repository))/stratos-fdbdoclayer -- name: chartsync-image - type: docker-image - source: - username: ((docker-username)) - password: ((docker-password)) - repository: ((docker-repository))/stratos-chartsync - name: kube-terminal-image type: docker-image source: @@ -159,13 +141,6 @@ jobs: tag: image-tag/v2-alpha-tag patch_base_reg: ((patch-base-reg)) patch_base_tag: ((patch-base-tag)) - - put: kube-terminal-image - params: - dockerfile: stratos/deploy/containers/kube-terminal/Dockerfile.kubeterminal - build: stratos/deploy/containers/kube-terminal - tag: image-tag/v2-alpha-tag - patch_base_reg: ((patch-base-reg)) - patch_base_tag: ((patch-base-tag)) - do: - put: ui-image params: @@ -184,31 +159,17 @@ jobs: patch_base_reg: ((patch-base-reg)) patch_base_tag: ((patch-base-tag)) - do: - - put: fdbserver-image - params: - dockerfile: stratos/deploy/containers/monocular/fdb-server/Dockerfile - build: stratos/deploy/containers/monocular/fdb-server/ - tag: image-tag/v2-alpha-tag - patch_base_reg: ((patch-base-reg)) - patch_base_tag: ((patch-base-tag)) - - put: fdbdoclayer-image - params: - dockerfile: stratos/deploy/containers/monocular/fdb-doclayer/Dockerfile - build: stratos/deploy/containers/monocular/fdb-doclayer/ - tag: image-tag/v2-alpha-tag - patch_base_reg: ((patch-base-reg)) - patch_base_tag: ((patch-base-tag)) - - put: chartsync-image + - put: analyzers-image params: - dockerfile: stratos/src/jetstream/plugins/monocular/chart-repo/Dockerfile - build: stratos/src/jetstream/plugins/monocular/chart-repo/ + dockerfile: stratos/src/jetstream/plugins/analysis/container/Dockerfile + build: stratos/src/jetstream/plugins/analysis/container/ tag: image-tag/v2-alpha-tag patch_base_reg: ((patch-base-reg)) patch_base_tag: ((patch-base-tag)) - - put: analyzers-image + - put: kube-terminal-image params: - dockerfile: stratos/src/jetstream/plugins/analysis/container/Dockerfile - build: stratos/src/jetstream/plugins/analysis/container/ + dockerfile: stratos/deploy/containers/kube-terminal/Dockerfile.kubeterminal + build: stratos/deploy/containers/kube-terminal tag: image-tag/v2-alpha-tag patch_base_reg: ((patch-base-reg)) patch_base_tag: ((patch-base-tag)) diff --git a/deploy/cloud-foundry/config.properties b/deploy/cloud-foundry/config.properties index a5cb9bd20a..648d77d54a 100644 --- a/deploy/cloud-foundry/config.properties +++ b/deploy/cloud-foundry/config.properties @@ -19,4 +19,6 @@ ENCRYPTION_KEY=B374A26A71490437AA024E4FADD5B497FDFF1A8EA6FF12F6FB65AF2720B59CCF #VCAP_APPLICATION={"cf_api": "https://api.10.4.21.240.nip.io:8443"} # User invite templates -TEMPLATE_DIR=./templates \ No newline at end of file +TEMPLATE_DIR=./templates + +HELM_CACHE_FOLDER=./helm-cache \ No newline at end of file diff --git a/deploy/containers/monocular/fdb-doclayer/Dockerfile b/deploy/containers/monocular/fdb-doclayer/Dockerfile deleted file mode 100644 index e670a88e2a..0000000000 --- a/deploy/containers/monocular/fdb-doclayer/Dockerfile +++ /dev/null @@ -1,64 +0,0 @@ - -# Original Source: https://raw.githubusercontent.com/kreinecke/fdb-document-layer/master/packaging/docker/Dockerfile - -# Dockerfile -# -# This source file is part of the FoundationDB open source project -# -# Copyright 2013-2018 Apple Inc. and the FoundationDB project authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# Builder container -FROM splatform/stratos-bk-build-base:leap15_1 as builder - -# Install dependencies - -# Our Stratos base image already has curl and less in it - -# Install FoundationDB Document Layer Binaries - -ARG FDB_DOC_VERSION=1.6.3 -ARG FDB_WEBSITE=https://www.foundationdb.org - -WORKDIR /home/stratos -RUN curl $FDB_WEBSITE/downloads/$FDB_DOC_VERSION/linux/fdb-document-layer-$FDB_DOC_VERSION-Linux.tar.gz -o fdb-document-layer-$FDB_DOC_VERSION-Linux.tar.gz && \ - tar -xvf fdb-document-layer-$FDB_DOC_VERSION-Linux.tar.gz && \ - mv fdb-document-layer-$FDB_DOC_VERSION-Linux doclayer - - -# This Docker image is just packing 6.0 client libraries. Document Layer works with -# any FoundationDB server >= 5.1.0. If your server version is not 6.0, then you might -# have to add the correct version client library here. -ARG FDB_CLIENT_VERSION=6.2.15 -RUN curl $FDB_WEBSITE/downloads/$FDB_CLIENT_VERSION/linux/libfdb_c_$FDB_CLIENT_VERSION.so -o /home/stratos/libfdb_c.so && \ - chmod +x /home/stratos/libfdb_c.so && \ - rm -rf /mnt/website - -# Final doclayer container -FROM splatform/stratos-bk-init-base:leap15_1 -WORKDIR /var/fdb - -COPY --from=builder /home/stratos/libfdb_c.so /usr/lib64/libfdb_c.so -COPY --from=builder /home/stratos/doclayer/bin/fdbdoc /usr/bin/fdbdoc -COPY --from=builder /home/stratos/doclayer/lib/foundationdb/document/fdbmonitor /usr/bin/fdbmonitor - -COPY fdbdoc.bash scripts/ -RUN chmod u+x scripts/*.bash && mkdir -p logs - -CMD /var/fdb/scripts/fdbdoc.bash - -# Runtime Configuration Options -ENV FDB_DOC_PORT 27016 -ENV FDB_NETWORKING_MODE container \ No newline at end of file diff --git a/deploy/containers/monocular/fdb-doclayer/fdb-doc-layer-networkpolicy.yaml b/deploy/containers/monocular/fdb-doclayer/fdb-doc-layer-networkpolicy.yaml deleted file mode 100644 index 7322378bd0..0000000000 --- a/deploy/containers/monocular/fdb-doclayer/fdb-doc-layer-networkpolicy.yaml +++ /dev/null @@ -1,21 +0,0 @@ - -{{- if .Values.fdbdoclayer.networkPolicy.enabled }} -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: {{ template "fullname" . }}-fdbdoclayer - labels: - app: {{ template "fullname" . }}-fdbdoclayer - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" -spec: - podSelector: - matchLabels: - app: {{ template "fullname" . }}-fdbdoclayer - release: {{ .Release.Name }} - ingress: - ports: - port: 27016 - protocol: TCP -{{- end }} diff --git a/deploy/containers/monocular/fdb-doclayer/fdbdoc.bash b/deploy/containers/monocular/fdb-doclayer/fdbdoc.bash deleted file mode 100755 index 935c119741..0000000000 --- a/deploy/containers/monocular/fdb-doclayer/fdbdoc.bash +++ /dev/null @@ -1,75 +0,0 @@ -#! /bin/bash - -function setup_cluster_file() { - FDB_CLUSTER_FILE=${FDB_CLUSTER_FILE:-/etc/foundationdb/fdb.cluster} - mkdir -p $(dirname $FDB_CLUSTER_FILE) - - if [[ -n $FDB_COORDINATOR ]]; then - echo "FDB coordinator: $FDB_COORDINATOR" - if [[ -z $coordinator_ip ]]; then - coordinator_ip=$(dig +short +search $FDB_COORDINATOR) - fi - echo "Coordinator IP: $coordinator_ip" - if [[ -z "$coordinator_ip" ]]; then - echo "Failed to look up coordinator address for $FDB_COORDINATOR" 1>&2 - exit 1 - fi - coordinator_port=${FDB_COORDINATOR_PORT:-4500} - if [[ -z $CLUSTER_ID ]]; then - echo "CLUSTER_ID environment variable not defined" 1>&2 - exit 1 - fi - echo "$CLUSTER_ID@$coordinator_ip:$coordinator_port" > $FDB_CLUSTER_FILE - else - echo "FDB_COORDINATOR environment variable not defined" 1>&2 - exit 1 - fi - - if [ ! -f ${FDB_CLUSTER_FILE} ]; then - echo "Failed to locate cluster file at $FDB_CLUSTER_FILE" 1>&2 - exit 1 - fi -} - -function setup_public_ip() { - if [[ "$FDB_NETWORKING_MODE" == "host" ]]; then - public_ip=127.0.0.1 - elif [[ "$FDB_NETWORKING_MODE" == "container" ]]; then - public_ip=$(grep `hostname` /etc/hosts | sed -e "s/\s *`hostname`.*//") - coordinator_ip=$public_ip - else - echo "Unknown FDB Networking mode \"$FDB_NETWORKING_MODE\"" 1>&2 - exit 1 - fi - - PUBLIC_IP=$public_ip -} - -setup_public_ip -setup_cluster_file - -if [ -n ${FDB_LISTEN_IP} ]; then - LISTEN_IP=${FDB_LISTEN_IP} -else - LISTEN_IP=${PUBLIC_IP} -fi - -echo "====================================================================================" -echo "FDB Document Layer starting" -echo "====================================================================================" -echo "" - -echo "Connecting to FDB server at: $CLUSTER_ID@$coordinator_ip:$coordinator_port" -echo "Cluster file contents: " -cat $FDB_CLUSTER_FILE - -echo "Listen IP is ${LISTEN_IP}" -echo "Public IP is ${PUBLIC_IP}" - -if [[ "$ENABLE_TLS" == "true" ]]; then - echo "Starting FDB Document Layer on $LISTEN_IP:$FDB_DOC_PORT:tls. TLS enabled." - fdbdoc -V --listen_address $LISTEN_IP:$FDB_DOC_PORT:tls --tls_certificate_file $SERVER_CRT --tls_ca_file $CA_CRT --tls_key_file $SERVER_KEY --logdir /var/fdb/logs -else - echo "Starting FDB Document Layer on $LISTEN_IP:$FDB_DOC_PORT. No TLS." - fdbdoc -V --listen_address $LISTEN_IP:$FDB_DOC_PORT --logdir /var/fdb/logs -fi diff --git a/deploy/containers/monocular/fdb-server/Dockerfile b/deploy/containers/monocular/fdb-server/Dockerfile deleted file mode 100644 index 48e589f3b1..0000000000 --- a/deploy/containers/monocular/fdb-server/Dockerfile +++ /dev/null @@ -1,89 +0,0 @@ -# Taken from: https://github.com/kreinecke/foundationdb/tree/fdb-k8s/packaging/docker - -# Dockerfile -# -# This source file is part of the FoundationDB open source project -# -# Copyright 2013-2018 Apple Inc. and the FoundationDB project authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -FROM splatform/stratos-bk-build-base:leap15_1 as builder - -# Install dependencies - -# RUN apt-get update && \ -# apt-get install -y curl>=7.58.0-2ubuntu3.6 \ -# dnsutils>=1:9.11.3+dfsg-1ubuntu1.7 && \ -# rm -r /var/lib/apt/lists/* - -# NOTE: Dependencies are in the base image - -# Install FoundationDB Binaries - -ARG FDB_VERSION=6.2.15 -ARG FDB_WEBSITE=https://www.foundationdb.org - -USER root -WORKDIR /home/stratos/tmp -WORKDIR /home/stratos -RUN pwd && ls -al -RUN curl $FDB_WEBSITE/downloads/$FDB_VERSION/linux/fdb_$FDB_VERSION.tar.gz -o fdb_$FDB_VERSION.tar.gz && \ - tar -xzf fdb_$FDB_VERSION.tar.gz --strip-components=1 && \ - rm fdb_$FDB_VERSION.tar.gz && \ - chmod u+x fdbbackup fdbcli fdbdr fdbmonitor fdbrestore fdbserver backup_agent dr_agent && \ - mv fdbbackup fdbcli fdbdr fdbmonitor fdbrestore fdbserver backup_agent dr_agent /home/stratos/tmp - - -# Main container -FROM splatform/stratos-bk-base:leap15_1 - -WORKDIR /var/fdb - -# Install FoundationDB Client Libraries - -ARG FDB_VERSION=6.2.15 -ARG FDB_ADDITIONAL_VERSIONS="5.1.7" -ARG FDB_WEBSITE=https://www.foundationdb.org - -COPY --from=builder /home/stratos/tmp /usr/bin - -COPY download_multiversion_libraries.bash scripts/ - -RUN mkdir -p /mnt/website -RUN curl $FDB_WEBSITE/downloads/$FDB_VERSION/linux/libfdb_c_$FDB_VERSION.so -o /usr/lib/libfdb_c.so && \ - bash scripts/download_multiversion_libraries.bash $FDB_WEBSITE $FDB_ADDITIONAL_VERSIONS && \ - rm -rf /mnt/website - -# Set Up Runtime Scripts and Directories - -COPY fdb.bash scripts/ -COPY create_server_environment.bash scripts/ -COPY create_cluster_file.bash scripts/ -COPY configure_db.bash scripts/ -RUN chmod u+x scripts/*.bash && \ - mkdir -p logs -VOLUME /var/fdb/data - -CMD /var/fdb/scripts/fdb.bash - -# Runtime Configuration Options - -ENV FDB_PORT 4500 -ENV FDB_CLUSTER_FILE /var/fdb/fdb.cluster -ENV FDB_NETWORKING_MODE container -ENV FDB_COORDINATOR "" -ENV FDB_COORDINATOR_PORT 4500 -ENV FDB_CLUSTER_FILE_CONTENTS "" -ENV FDB_PROCESS_CLASS unset \ No newline at end of file diff --git a/deploy/containers/monocular/fdb-server/configure_db.bash b/deploy/containers/monocular/fdb-server/configure_db.bash deleted file mode 100755 index df2a8d6e91..0000000000 --- a/deploy/containers/monocular/fdb-server/configure_db.bash +++ /dev/null @@ -1,45 +0,0 @@ -#! /bin/bash - -# -# create_cluster_file.bash -# -# This source file is part of the FoundationDB open source project -# -# Copyright 2013-2018 Apple Inc. and the FoundationDB project authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# This script creates a cluster file for a server or client. -# This takes the cluster file path from the FDB_CLUSTER_FILE -# environment variable, with a default of /etc/foundationdb/fdb.cluster -# -# The name of the coordinator must be defined in the FDB_COORDINATOR environment -# variable, and it must be a name that can be resolved through DNS. - -function configure_db() { - max_retry=10 - counter=0 - until fdbcli --exec "configure new single memory" | grep 'Database created' - do - sleep 1 - [[ counter -eq $max_retry ]] && echo "Failed!" && exit 1 - echo "Could not init db yet. Trying again. Try #$counter" - ((counter++)) - done - echo "Database created." -} - -if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - configure_db "$@" -fi \ No newline at end of file diff --git a/deploy/containers/monocular/fdb-server/create_cluster_file.bash b/deploy/containers/monocular/fdb-server/create_cluster_file.bash deleted file mode 100644 index 0a710264a4..0000000000 --- a/deploy/containers/monocular/fdb-server/create_cluster_file.bash +++ /dev/null @@ -1,61 +0,0 @@ -#! /bin/bash - -# -# create_cluster_file.bash -# -# This source file is part of the FoundationDB open source project -# -# Copyright 2013-2018 Apple Inc. and the FoundationDB project authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# This script creates a cluster file for a server or client. -# This takes the cluster file path from the FDB_CLUSTER_FILE -# environment variable, with a default of /etc/foundationdb/fdb.cluster -# -# The name of the coordinator must be defined in the FDB_COORDINATOR environment -# variable, and it must be a name that can be resolved through DNS. - -function create_cluster_file() { - FDB_CLUSTER_FILE=${FDB_CLUSTER_FILE:-/etc/foundationdb/fdb.cluster} - mkdir -p $(dirname $FDB_CLUSTER_FILE) - - if [[ -n "$FDB_CLUSTER_FILE_CONTENTS" ]]; then - echo "$FDB_CLUSTER_FILE_CONTENTS" > $FDB_CLUSTER_FILE - elif [[ -n $FDB_COORDINATOR ]]; then - #We are in k8s and must lookup the fdb service hostname, not the pod IP. - coordinator_ip=$(dig +short +search $FDB_COORDINATOR) - if [[ -z "$coordinator_ip" ]]; then - echo "Failed to look up coordinator address for $FDB_COORDINATOR" 1>&2 - exit 1 - fi - if [[ -z $CLUSTER_ID ]]; then - echo "CLUSTER_ID environment variable not defined" 1>&2 - exit 1 - fi - coordinator_port=${FDB_COORDINATOR_PORT:-4500} - echo "export COORDINATOR_IP=$coordinator_ip" >> $env_file - echo "export COORDINATOR_PORT=$coordinator_port" >> $env_file - source $env_file - #echo "$CLUSTER_ID@$coordinator_ip:$coordinator_port,127.0.0.1:4500" > $FDB_CLUSTER_FILE - echo "$CLUSTER_ID@$PUBLIC_IP:4500" > $FDB_CLUSTER_FILE - else - echo "FDB_COORDINATOR environment variable not defined" 1>&2 - exit 1 - fi -} - -if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - create_cluster_file "$@" -fi \ No newline at end of file diff --git a/deploy/containers/monocular/fdb-server/create_server_environment.bash b/deploy/containers/monocular/fdb-server/create_server_environment.bash deleted file mode 100644 index d417f0367d..0000000000 --- a/deploy/containers/monocular/fdb-server/create_server_environment.bash +++ /dev/null @@ -1,49 +0,0 @@ -#! /bin/bash - -# -# create_server_environment.bash -# -# This source file is part of the FoundationDB open source project -# -# Copyright 2013-2018 Apple Inc. and the FoundationDB project authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -source /var/fdb/scripts/create_cluster_file.bash - -function create_server_environment() { - fdb_dir=/var/fdb - env_file=$fdb_dir/.fdbenv - - : > $env_file - - # If using docker in host network mode, or if we are running our client in the same container, use 127.0.0.1 - if [[ "$FDB_NETWORKING_MODE" == "host" ]]; then - public_ip=127.0.0.1 - elif [[ "$FDB_NETWORKING_MODE" == "container" ]]; then - #We are running in k8s - public_ip=$(grep `hostname` /etc/hosts | sed -e "s/\s *`hostname`.*//") - else - echo "Unknown FDB Networking mode \"$FDB_NETWORKING_MODE\"" 1>&2 - exit 1 - fi - - echo "export PUBLIC_IP=$public_ip" >> $env_file - # If the FDB_COORDINATOR node is not set, then we are the coordinator - use our public IP - if [[ -z $FDB_COORDINATOR ]]; then - FDB_CLUSTER_FILE_CONTENTS="docker:docker@$public_ip:$FDB_PORT" - fi - - create_cluster_file -} diff --git a/deploy/containers/monocular/fdb-server/download_multiversion_libraries.bash b/deploy/containers/monocular/fdb-server/download_multiversion_libraries.bash deleted file mode 100644 index 651947857c..0000000000 --- a/deploy/containers/monocular/fdb-server/download_multiversion_libraries.bash +++ /dev/null @@ -1,31 +0,0 @@ -#! /bin/bash - -# -# download_multiversion_libraries.bash -# -# This source file is part of the FoundationDB open source project -# -# Copyright 2013-2018 Apple Inc. and the FoundationDB project authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -mkdir -p /usr/lib/fdb/multiversion -website=$1 -shift -for version in $*; do - origin=$website/downloads/$version/linux/libfdb_c_$version.so - destination=/usr/lib/fdb/multiversion/libfdb_c_$version.so - echo "Downloading $origin to $destination" - curl $origin -o $destination -done \ No newline at end of file diff --git a/deploy/containers/monocular/fdb-server/fdb.bash b/deploy/containers/monocular/fdb-server/fdb.bash deleted file mode 100644 index 44842089e4..0000000000 --- a/deploy/containers/monocular/fdb-server/fdb.bash +++ /dev/null @@ -1,46 +0,0 @@ -#! /bin/bash - -# -# fdb.bash -# -# This source file is part of the FoundationDB open source project -# -# Copyright 2013-2018 Apple Inc. and the FoundationDB project authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -source /var/fdb/scripts/create_server_environment.bash -create_server_environment -source /var/fdb/.fdbenv -echo "Starting FDB server listening on: $PUBLIC_IP:$FDB_PORT public address: $PUBLIC_IP:$FDB_PORT" -cat /var/fdb/fdb.cluster -source /var/fdb/scripts/configure_db.bash -configure_db & - -if [ -n ${FDB_LISTEN_IP} ]; then - LISTEN_IP=${FDB_LISTEN_IP} -else - LISTEN_IP=${PUBLIC_IP} -fi - -echo "====================================================================================" -echo "FDB Server starting" -echo "====================================================================================" -echo "" - -echo "Listen IP is ${LISTEN_IP}" -echo "Public IP is ${PUBLIC_IP}" - -fdbserver --listen_address $LISTEN_IP:$FDB_PORT --public_address $PUBLIC_IP:$FDB_PORT \ - --datadir /var/fdb/data --logdir /var/fdb/logs \ No newline at end of file diff --git a/deploy/containers/monocular/fdb-server/fdbserver-networkpolicy.yaml b/deploy/containers/monocular/fdb-server/fdbserver-networkpolicy.yaml deleted file mode 100644 index 2752d371e2..0000000000 --- a/deploy/containers/monocular/fdb-server/fdbserver-networkpolicy.yaml +++ /dev/null @@ -1,21 +0,0 @@ - -{{- if .Values.fdbserver.networkPolicy.enabled }} -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: {{ template "fullname" . }}-fdbserver - labels: - app: {{ template "fullname" . }}-fdbserver - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" -spec: - podSelector: - matchLabels: - app: {{ template "fullname" . }}-fdbserver - release: {{ .Release.Name }} - ingress: - - ports: - - port: 4500 - protocol: TCP -{{- end }} diff --git a/deploy/kubernetes/console/templates/chartsync.yaml b/deploy/kubernetes/console/templates/chartsync.yaml deleted file mode 100644 index 1eeab2affd..0000000000 --- a/deploy/kubernetes/console/templates/chartsync.yaml +++ /dev/null @@ -1,70 +0,0 @@ ---- -{{- if semverCompare ">=1.16" (printf "%s.%s" .Capabilities.KubeVersion.Major (trimSuffix "+" .Capabilities.KubeVersion.Minor) )}} -apiVersion: apps/v1 -{{- else }} -apiVersion: extensions/v1beta1 -{{- end }} -kind: Deployment -metadata: - name: stratos-chartsync - labels: - app.kubernetes.io/name: "stratos" - app.kubernetes.io/instance: "{{ .Release.Name }}" - app.kubernetes.io/version: "{{ .Chart.AppVersion }}" - app.kubernetes.io/component: "stratos-chartsync" - helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" -spec: - selector: - matchLabels: - app.kubernetes.io/name: "stratos" - app.kubernetes.io/component: "stratos-chartsync" - template: - metadata: - labels: - app.kubernetes.io/name: "stratos" - app.kubernetes.io/instance: "{{ .Release.Name }}" - app.kubernetes.io/version: "{{ .Chart.AppVersion }}" - app.kubernetes.io/component: "stratos-chartsync" - helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" - app: "{{ .Release.Name }}" - spec: - containers: - - name: chartsync - image: {{.Values.kube.registry.hostname}}/{{.Values.kube.organization}}/{{ default "stratos-chartsync" .Values.images.chartsync}}:{{.Values.consoleVersion}} - imagePullPolicy: {{.Values.imagePullPolicy}} - command: ["/chartrepo"] - args: ["serve", "--doclayer-url=mongodb://{{ .Release.Name }}-fdbdoclayer:27016", "--cafile=/etc/certs/ca.crt", "--certfile=/etc/certs/tls.crt", "--keyfile=/etc/certs/tls.key"] - env: - - name: STRATOS_IMAGE_REF - value: "{{.Values.consoleVersion}}:{{ .Release.Revision }}" - ports: - - name: endpoint - containerPort: 8080 - volumeMounts: - - name: certs - mountPath: "/etc/certs" - readOnly: true - volumes: - - name: certs - secret: - secretName: {{ .Release.Name }}-fdbdoclayer-certs ---- -apiVersion: v1 -kind: Service -metadata: - name: "{{ .Release.Name }}-chartsync" - labels: - app.kubernetes.io/name: "stratos" - app.kubernetes.io/instance: "{{ .Release.Name }}" - app.kubernetes.io/version: "{{ .Chart.AppVersion }}" - app.kubernetes.io/component: "stratos-chartsync-service" - helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" -spec: - type: ClusterIP - ports: - - name: chartsync - port: 8080 - targetPort: 8080 - selector: - app: "{{ .Release.Name }}" - app.kubernetes.io/component: "stratos-chartsync" diff --git a/deploy/kubernetes/console/templates/deployment.yaml b/deploy/kubernetes/console/templates/deployment.yaml index 2cd00464d0..50d94a2c5d 100644 --- a/deploy/kubernetes/console/templates/deployment.yaml +++ b/deploy/kubernetes/console/templates/deployment.yaml @@ -282,6 +282,8 @@ spec: value: {{ default "" .Values.console.userInviteSubject | quote }} - name: ENABLE_TECH_PREVIEW value: {{ default "false" .Values.console.techPreview | quote }} + - name: HELM_CACHE_FOLDER + value: /helm-cache {{- if .Values.console.ui }} {{- if .Values.console.ui.listMaxSize }} - name: UI_LIST_MAX_SIZE @@ -315,9 +317,8 @@ spec: name: "{{ .Release.Name }}-templates" readOnly: true {{- end }} - - mountPath: "/etc/monocular-certs" - name: "{{ .Release.Name }}-monocular-certs" - readOnly: true + - mountPath: /helm-cache + name: helm-cache-volume {{- if and .Values.kube.registry.username .Values.kube.registry.password }} imagePullSecrets: - name: {{.Values.dockerRegistrySecret}} @@ -347,6 +348,5 @@ spec: configMap: name: {{ .Values.console.templatesConfigMapName }} {{- end }} - - name: "{{ .Release.Name }}-monocular-certs" - secret: - secretName: {{ .Release.Name }}-fdbdoclayer-certs + - name: helm-cache-volume + emptyDir: {} diff --git a/deploy/kubernetes/console/templates/fdb-secrets.yaml b/deploy/kubernetes/console/templates/fdb-secrets.yaml deleted file mode 100644 index 7e9598779f..0000000000 --- a/deploy/kubernetes/console/templates/fdb-secrets.yaml +++ /dev/null @@ -1,27 +0,0 @@ -{{/* -Generate self-signed certificate -*/}} -{{- define "fdbdoclayer.generateCertificate" -}} -{{- $altNames := list ( printf "%s%s" .Release.Name "-fdbdoclayer") ( printf "%s%s.%s" (include "fullname" .) "-fdbdoclayer" .Release.Namespace ) ( printf "%s.%s.svc" (include "console.certName" .) .Release.Namespace ) -}} -{{- $ca := genCA "stratos-ca" 365 -}} -{{- $cert := genSignedCert "fdbdoclayer" nil $altNames 365 $ca -}} -tls.crt: {{ $cert.Cert | b64enc }} -tls.key: {{ $cert.Key | b64enc }} -tls.pem: {{ printf "%s\n%s\n%s\n" ($cert.Cert) ($ca.Cert) ($cert.Key) | b64enc }} -ca.crt: {{ $ca.Cert | b64enc }} -{{- end -}} - ---- -apiVersion: v1 -kind: Secret -type: kubernetes.io/tls -metadata: - name: {{ .Release.Name }}-fdbdoclayer-certs - labels: - app.kubernetes.io/name: "stratos" - app.kubernetes.io/instance: "{{ .Release.Name }}" - app.kubernetes.io/version: "{{ .Chart.AppVersion }}" - app.kubernetes.io/component: "fdbdoclayer-certs" - helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" -data: -{{ (include "fdbdoclayer.generateCertificate" .) | indent 2 }} \ No newline at end of file diff --git a/deploy/kubernetes/console/templates/fdbdoclayer/deployment.yaml b/deploy/kubernetes/console/templates/fdbdoclayer/deployment.yaml deleted file mode 100644 index 90923912b0..0000000000 --- a/deploy/kubernetes/console/templates/fdbdoclayer/deployment.yaml +++ /dev/null @@ -1,123 +0,0 @@ - -{{- if semverCompare ">=1.16" (printf "%s.%s" .Capabilities.KubeVersion.Major (trimSuffix "+" .Capabilities.KubeVersion.Minor) )}} -apiVersion: apps/v1 -{{- else }} -apiVersion: apps/v1beta2 -{{- end }} -kind: Deployment -metadata: - name: stratos-chartstore -{{- if .Values.console.deploymentAnnotations }} - annotations: -{{ toYaml .Values.console.deploymentAnnotations | indent 4 }} -{{- end }} - labels: - app.kubernetes.io/name: "stratos" - app.kubernetes.io/instance: "{{ .Release.Name }}" - app.kubernetes.io/version: "{{ .Chart.AppVersion }}" - app.kubernetes.io/component: "stratos-chartstore" - helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" -{{- if .Values.console.deploymentExtraLabels }} -{{ toYaml .Values.console.deploymentExtraLabels | indent 4 }} -{{- end }} -spec: - replicas: 1 - selector: - matchLabels: - app.kubernetes.io/name: "stratos" - app.kubernetes.io/instance: "{{ .Release.Name }}" - app.kubernetes.io/component: "stratos-chartstore" - template: - metadata: -{{- if .Values.console.podAnnotations }} - annotations: -{{ toYaml .Values.console.podAnnotations | indent 8 }} -{{- end }} - labels: - app.kubernetes.io/name: "stratos" - app.kubernetes.io/instance: "{{ .Release.Name }}" - app.kubernetes.io/version: "{{ .Chart.AppVersion }}" - app.kubernetes.io/component: "stratos-chartstore" - helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" - {{- if .Values.console.podExtraLabels}} - {{ toYaml .Values.console.podExtraLabels | nindent 8 }} - {{- end}} - spec: -{{- with .Values.securityContext }} - securityContext: -{{ toYaml . | indent 8 }} -{{- end }} - containers: - - name: fdbdoclayer - image: {{.Values.kube.registry.hostname}}/{{.Values.kube.organization}}/{{.Values.images.fdbdoclayer}}:{{.Values.consoleVersion}} - imagePullPolicy: {{.Values.imagePullPolicy}} - env: - - name: STRATOS_IMAGE_REF - value: "{{.Values.consoleVersion}}:{{ .Release.Revision }}" - - name: FDB_COORDINATOR - value: {{ .Release.Name }}-fdbdoclayer - - name: FDB_LISTEN_IP - value: 0.0.0.0 - - name: CLUSTER_ID - value: chartdb:erlklkg - - name: ENABLE_TLS - value: "true" - - name: SERVER_CRT - value: "/etc/secrets/tls.crt" - - name: SERVER_KEY - value: "/etc/secrets/tls.key" - - name: CA_CRT - value: "/etc/secrets/ca.crt" - - name: FDB_NETWORKING_MODE - value: container - ports: - - containerPort: 27016 - livenessProbe: - tcpSocket: - port: 27016 - initialDelaySeconds: 3 - periodSeconds: 20 - readinessProbe: - tcpSocket: - port: 27016 - initialDelaySeconds: 3 - periodSeconds: 10 - volumeMounts: - - name: certs - mountPath: "/etc/secrets" - readOnly: true - resources: - limits: - memory: 384Mi - requests: - memory: 256Mi - - name: fdbserver - image: {{.Values.kube.registry.hostname}}/{{.Values.kube.organization}}/{{.Values.images.fdbserver}}:{{.Values.consoleVersion}} - imagePullPolicy: {{.Values.imagePullPolicy}} - env: - - name: STRATOS_IMAGE_REF - value: "{{.Values.consoleVersion}}:{{ .Release.Revision }}" - - name: FDB_COORDINATOR - value: {{ .Release.Name }}-fdbdoclayer - - name: FDB_LISTEN_IP - value: 0.0.0.0 - - name: CLUSTER_ID - value: chartdb:erlklkg - - name: FDB_NETWORKING_MODE - value: container - ports: - - containerPort: 4500 - livenessProbe: - tcpSocket: - port: 4500 - initialDelaySeconds: 3 - periodSeconds: 20 - readinessProbe: - tcpSocket: - port: 4500 - initialDelaySeconds: 3 - periodSeconds: 10 - volumes: - - name: certs - secret: - secretName: {{ .Release.Name }}-fdbdoclayer-certs diff --git a/deploy/kubernetes/console/templates/fdbdoclayer/fdb-doc-layer-service.yaml b/deploy/kubernetes/console/templates/fdbdoclayer/fdb-doc-layer-service.yaml deleted file mode 100644 index b3c74bfb0a..0000000000 --- a/deploy/kubernetes/console/templates/fdbdoclayer/fdb-doc-layer-service.yaml +++ /dev/null @@ -1,33 +0,0 @@ - -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }}-fdbdoclayer -{{- if .Values.console.service -}} -{{- if .Values.console.service.annotations }} - annotations: -{{ toYaml .Values.console.service.annotations | indent 4 }} -{{- end }} -{{- end }} - labels: - app.kubernetes.io/name: "stratos" - app.kubernetes.io/instance: "{{ .Release.Name }}" - app.kubernetes.io/version: "{{ .Chart.AppVersion }}" - app.kubernetes.io/component: "{{ .Release.Name }}-fdbdoclayer" - helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" -{{- if .Values.console.service -}} -{{- if .Values.console.service.extraLabels }} -{{ toYaml .Values.console.service.extraLabels | indent 4 }} -{{- end }} -{{- end }} -spec: - type: ClusterIP - ports: - - port: 27016 - targetPort: 27016 - protocol: TCP - name: fdbdoclayer - selector: - app.kubernetes.io/name: "stratos" - app.kubernetes.io/instance: "{{ .Release.Name }}" - app.kubernetes.io/component: "stratos-chartstore" diff --git a/deploy/kubernetes/console/values.yaml b/deploy/kubernetes/console/values.yaml index 5ac7dc060c..3e72d57812 100644 --- a/deploy/kubernetes/console/values.yaml +++ b/deploy/kubernetes/console/values.yaml @@ -128,9 +128,6 @@ images: proxy: stratos-jetstream mariadb: stratos-mariadb configInit: stratos-config-init - fdbserver: stratos-fdbserver - fdbdoclayer: stratos-fdbdoclayer - chartsync: stratos-chartsync analyzers: stratos-analyzers # Specify which storage class should be used for PVCs diff --git a/deploy/kubernetes/custom/__stratos.tpl b/deploy/kubernetes/custom/__stratos.tpl index d5eb091928..653bade6f6 100644 --- a/deploy/kubernetes/custom/__stratos.tpl +++ b/deploy/kubernetes/custom/__stratos.tpl @@ -2,16 +2,6 @@ # Extra env vars for the Jetstream Pod in deployment.yaml {{- define "stratosJetstreamEnv" }} -- name: MONOCULAR_CRT_PATH - value: "/etc/monocular-certs/tls.crt" -- name: MONOCULAR_KEY_PATH - value: "/etc/monocular-certs/tls.key" -- name: MONOCULAR_CA_CRT_PATH - value: "/etc/monocular-certs/ca.crt" -- name: FDB_URL - value: "mongodb://{{ .Release.Name }}-fdbdoclayer:27016" -- name: SYNC_SERVER_URL - value: "http://{{ .Release.Name }}-chartsync:8080" - name: ANALYSIS_SERVICES_API value: "http://{{ .Release.Name }}-analyzers:8090" - name: STRATOS_KUBERNETES_NAMESPACE diff --git a/deploy/kubernetes/custom/custom-build.sh b/deploy/kubernetes/custom/custom-build.sh index b44f9fbd41..8b413607cf 100644 --- a/deploy/kubernetes/custom/custom-build.sh +++ b/deploy/kubernetes/custom/custom-build.sh @@ -12,16 +12,6 @@ printf "${RESET}" function custom_image_build() { - log "-- Building/publishing Foundation DB Server" - patchAndPushImage stratos-fdbserver Dockerfile "${STRATOS_PATH}/deploy/containers/monocular/fdb-server" - - log "-- Building/publishing Foundation DB Document Layer" - patchAndPushImage stratos-fdbdoclayer Dockerfile "${STRATOS_PATH}/deploy/containers/monocular/fdb-doclayer" - - # Build and push an image for the Helm Repo Sync Tool - log "-- Building/publishing Monocular Chart Repo Sync Tool" - patchAndPushImage stratos-chartsync Dockerfile "${STRATOS_PATH}/src/jetstream/plugins/monocular/chart-repo" - # Build and push an image for the Kubernetes Terminal log "-- Building/publishing Kubernetes Terminal" patchAndPushImage stratos-kube-terminal Dockerfile.kubeterminal "${STRATOS_PATH}/deploy/containers/kube-terminal" diff --git a/src/frontend/packages/core/src/shared/components/stepper/steppers/steppers.component.ts b/src/frontend/packages/core/src/shared/components/stepper/steppers/steppers.component.ts index a79b0fcc7a..f35cb5c7ba 100644 --- a/src/frontend/packages/core/src/shared/components/stepper/steppers/steppers.component.ts +++ b/src/frontend/packages/core/src/shared/components/stepper/steppers/steppers.component.ts @@ -128,7 +128,7 @@ export class SteppersComponent implements OnInit, AfterContentInit, OnDestroy { this.logger.warn('Stepper failed: ', err); return observableOf({ success: false, - message: 'Failed', + message: err || 'Failed', redirectPayload: null, redirect: false, data: {}, diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.spec.ts index 6cf6078bec..c214981990 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.spec.ts @@ -9,6 +9,9 @@ import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-m import { InternalEventMonitorFactory } from '../../../../../store/src/monitors/internal-event-monitor.factory'; import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; import { KubernetesBaseTestModules } from '../../kubernetes/kubernetes.testing.module'; +import { MockChartService } from '../monocular/shared/services/chart.service.mock'; +import { ChartsService } from '../monocular/shared/services/charts.service'; +import { ConfigService } from '../monocular/shared/services/config.service'; import { CreateReleaseComponent } from './create-release.component'; describe('CreateReleaseComponent', () => { @@ -31,8 +34,10 @@ describe('CreateReleaseComponent', () => { EntityMonitorFactory, InternalEventMonitorFactory, TabNavService, - ConfirmationDialogService - ] + ConfirmationDialogService, + { provide: ChartsService, useValue: new MockChartService() }, + { provide: ConfigService, useValue: { appName: 'appName' } }, + ] }) .compileComponents(); diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.ts index 26ccf5c874..372ea2e145 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.ts @@ -15,10 +15,13 @@ import { RequestInfoState } from '../../../../../store/src/reducers/api-request- import { kubeEntityCatalog } from '../../kubernetes/kubernetes-entity-catalog'; import { KUBERNETES_ENDPOINT_TYPE } from '../../kubernetes/kubernetes-entity-factory'; import { KubernetesNamespace } from '../../kubernetes/store/kube.types'; +import { getFirstChartUrl } from '../../kubernetes/workloads/workload.utils'; import { helmEntityCatalog } from '../helm-entity-catalog'; import { createMonocularProviders } from '../monocular/stratos-monocular-providers.helpers'; import { getMonocularEndpoint, stratosMonocularEndpointGuid } from '../monocular/stratos-monocular.helper'; import { HelmInstallValues } from '../store/helm.types'; +import { ChartsService } from './../monocular/shared/services/charts.service'; +import { HelmChartReference } from './../store/helm.types'; @Component({ selector: 'app-create-release', @@ -59,14 +62,18 @@ export class CreateReleaseComponent implements OnInit, OnDestroy { private subs: Subscription[] = []; private createdNamespace = false; + private chart: HelmChartReference; + constructor( private route: ActivatedRoute, public endpointsService: EndpointsService, private httpClient: HttpClient, private confirmDialog: ConfirmationDialogService, + private chartsService: ChartsService, ) { - const chart = this.route.snapshot.params; - this.cancelUrl = `/monocular/charts/${getMonocularEndpoint(this.route)}/${chart.repo}/${chart.chartName}/${chart.version}`; + const chart = this.route.snapshot.params as HelmChartReference; + this.cancelUrl = `/monocular/charts/${getMonocularEndpoint(this.route)}/${chart.repo}/${chart.name}/${chart.version}`; + this.chart = chart; this.setupDetailsStep(); @@ -75,7 +82,7 @@ export class CreateReleaseComponent implements OnInit, OnDestroy { }); // Fetch the values.yaml for the Chart - const valuesYamlUrl = `/pp/v1/chartsvc/v1/assets/${chart.repo}/${chart.chartName}/versions/${chart.version}/values.yaml`; + const valuesYamlUrl = `/pp/v1/chartsvc/v1/assets/${chart.repo}/${chart.name}/versions/${chart.version}/values.yaml`; this.httpClient.get(valuesYamlUrl, { responseType: 'text' }) .subscribe(response => { @@ -256,33 +263,45 @@ export class CreateReleaseComponent implements OnInit, OnDestroy { ...this.details.value, ...this.overrides.value, chart: { - chartName: this.route.snapshot.params.chartName, + name: this.route.snapshot.params.name, repo: this.route.snapshot.params.repo, version: this.route.snapshot.params.version, }, monocularEndpoint: endpoint === stratosMonocularEndpointGuid ? null : endpoint }; - // Make the request - return helmEntityCatalog.chart.api.install<RequestInfoState>(values).pipe( - // Wait for result of request - filter(state => !!state), - pairwise(), - filter(([oldVal, newVal]) => (oldVal.creating && !newVal.creating)), - map(([, newVal]) => newVal), - map(result => ({ - success: !result.error, - redirect: !result.error, - redirectPayload: { - path: !result.error ? `workloads/${values.endpoint}:${values.releaseNamespace}:${values.releaseName}/summary` : '' - }, - message: !result.error ? '' : result.message - })) + // Get the chart first, so we can get then install URL, then install + return this.chartsService.getVersion(this.chart.repo, this.chart.name, this.chart.version).pipe( + switchMap(chartInfo => { + if (!chartInfo) { + throw new Error('Could not get Chart URL'); + } + // Add the chart url into the values + values.chartUrl = getFirstChartUrl(chartInfo); + if (values.chartUrl.length === 0) { + throw new Error('Could not get Chart URL'); + } + // Make the request + return helmEntityCatalog.chart.api.install<RequestInfoState>(values).pipe( + // Wait for result of request + filter(state => !!state), + pairwise(), + filter(([oldVal, newVal]) => (oldVal.creating && !newVal.creating)), + map(([, newVal]) => newVal), + map(result => ({ + success: !result.error, + redirect: !result.error, + redirectPayload: { + path: !result.error ? `workloads/${values.endpoint}:${values.releaseNamespace}:${values.releaseName}/summary` : '' + }, + message: !result.error ? '' : result.message + })) + ) + }) ); } ngOnDestroy() { safeUnsubscribe(...this.subs); } - } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm.routing.ts b/src/frontend/packages/suse-extensions/src/custom/helm/helm.routing.ts index 37dc491734..e42207d593 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm.routing.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/helm.routing.ts @@ -18,8 +18,8 @@ const monocular: Routes = [ }, { pathMatch: 'full', path: 'charts/:endpoint/:repo/:chartName/:version', component: MonocularChartViewComponent }, { path: 'charts/:endpoint/:repo/:chartName', component: MonocularChartViewComponent }, - { pathMatch: 'full', path: 'install/:endpoint/:repo/:chartName/:version', component: CreateReleaseComponent }, - { path: 'install/:endpoint/:repo/:chartName', component: CreateReleaseComponent }, + { pathMatch: 'full', path: 'install/:endpoint/:repo/:name/:version', component: CreateReleaseComponent }, + { path: 'install/:endpoint/:repo/:name', component: CreateReleaseComponent }, ]; @NgModule({ diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.ts index 50ffa840a5..939eb86e08 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.ts @@ -19,19 +19,22 @@ export class ChartDetailsReadmeComponent { } } - public loading = true; + public loading = false; public readmeContent$: Observable<string>; private renderer = new markdown.Renderer(); + private loadingDelay: any; constructor(private chartsService: ChartsService) { this.renderer.link = (href, title, text) => `<a target="_blank" title="${title}" href="${href}">${text}</a>`; this.renderer.code = (text: string) => `<code>${text}</code>`; + this.loadingDelay = setTimeout(() => this.loading = true, 100); } // TODO: See #150 - This should not require loading the specific version and then the readme private getReadme(currentVersion: ChartVersion): Observable<string> { return this.chartsService.getChartReadme(currentVersion).pipe( map(resp => { + clearTimeout(this.loadingDelay); this.loading = false; return markdown(resp, { renderer: this.renderer diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.html b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.html index fae3d4f7d5..31c68dc996 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.html @@ -1,5 +1,5 @@ <div> - <app-panel *ngIf="!chart && !loading" class="app-panel--container"> + <app-panel *ngIf="!chart && !initing" class="app-panel--container"> <h2>Sorry, we couldn't find the chart</h2> </app-panel> <app-loader [loading]="loading"> diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.ts index 76262da43a..5d63a62325 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.ts @@ -16,16 +16,21 @@ import { getMonocularEndpoint } from '../stratos-monocular.helper'; export class ChartDetailsComponent implements OnInit { /* This resource will be different, probably ChartVersion */ chart: Chart; - loading = true; + loading = false; + initing = true; currentVersion: ChartVersion; iconUrl: string; titleVersion: string; + loadingDelay: any; + constructor( private route: ActivatedRoute, private chartsService: ChartsService, private config: ConfigService, - ) { } + ) { + this.loadingDelay = setTimeout(() => this.loading = true, 100); + } ngOnInit() { this.route.params.forEach((params: Params) => { @@ -34,7 +39,9 @@ export class ChartDetailsComponent implements OnInit { if (!!chartName) { this.chartsService.getChart(repo, chartName).pipe(first()).subscribe(chart => { + clearTimeout(this.loadingDelay); this.loading = false; + this.initing = false; this.chart = chart; const version = params.version || this.chart.relationships.latestChartVersion.data.version; this.chartsService.getVersion(repo, chartName, version).pipe(first()) @@ -42,8 +49,8 @@ export class ChartDetailsComponent implements OnInit { this.currentVersion = chartVersion; this.titleVersion = this.currentVersion.attributes.app_version || ''; this.updateMetaTags(); + this.iconUrl = this.chartsService.getChartIconURL(this.chart, chartVersion); }); - this.iconUrl = this.chartsService.getChartIconURL(this.chart); }); } }); diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts index 77f23474d7..cb22418fba 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts @@ -125,9 +125,9 @@ export class ChartsService { * @return An observable that will be a chartReadme */ getChartReadme(chartVersion: ChartVersion): Observable<string> { - return this.http.get(`${this.hostname}${chartVersion.attributes.readme}`, { + return chartVersion.attributes.readme ? this.http.get(`${this.hostname}${chartVersion.attributes.readme}`, { responseType: 'text' - }); + }) : of('<h1>No Readme available for this chart</h1>'); } /** @@ -175,9 +175,11 @@ export class ChartsService { * * @param chart Chart object */ - getChartIconURL(chart: Chart): string { - if (chart.attributes.icon) { - return this.updateStratosUrl(chart, `${this.hostname}${chart.attributes.icon}`); + getChartIconURL(chart: Chart, version?: ChartVersion): string { + let url = version ? version.relationships.chart.data.icon : null; + url = url || chart.attributes.icon; + if (url) { + return this.updateStratosUrl(chart, `${this.hostname}${url}`); } else { return '/core/assets/custom/placeholder.png'; } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.types.ts b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.types.ts index 7c9c4afa9c..fc79d7039c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.types.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.types.ts @@ -45,35 +45,26 @@ export enum HelmStatus { Pending_Rollback = 8 } -export interface HelmInstallValues { - endpoint: string; +export interface HelmChartReference { + name: string; + repo: string; + version: string; +} + +export interface HelmUpgradeInstallValues { monocularEndpoint: string; - releaseName: string; - releaseNamespace: string; values: string; - chart: { - name: string; - repo: string; - version: string; - }; + chart: HelmChartReference; + chartUrl: string; } -export interface HelmUpgradeValues { - values: string; - chart: { - name: string; - repo: string; - version: string; - }; - restartPods?: boolean; - monocularEndpoint?: string; +export interface HelmInstallValues extends HelmUpgradeInstallValues { + endpoint: string; + releaseName: string; + releaseNamespace: string; } -export interface HelmUpgradeValues { - chart: { - name: string; - repo: string; - version: string; - }; + +export interface HelmUpgradeValues extends HelmUpgradeInstallValues { restartPods?: boolean; } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts index 34f8ef5c45..328ded2c6f 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts @@ -11,6 +11,7 @@ import { stratosMonocularEndpointGuid } from '../../../helm/monocular/stratos-mo import { HelmUpgradeValues, MonocularVersion } from '../../../helm/store/helm.types'; import { HelmReleaseHelperService } from '../release/tabs/helm-release-helper.service'; import { HelmReleaseGuid } from '../workload.types'; +import { getFirstChartUrl } from '../workload.utils'; import { workloadsEntityCatalog } from './../workloads-entity-catalog'; import { ReleaseUpgradeVersionsListConfig } from './release-version-list-config'; @@ -90,6 +91,7 @@ export class UpgradeReleaseComponent { return of({ success: true }); } + // Add the chart url into the values const values: HelmUpgradeValues = { values: this.overrides.controls.values.value, restartPods: false, @@ -98,7 +100,8 @@ export class UpgradeReleaseComponent { repo: this.version.relationships.chart.data.repo.name, version: this.version.attributes.version, }, - monocularEndpoint: this.monocularEndpointId === stratosMonocularEndpointGuid ? null : this.monocularEndpointId + monocularEndpoint: this.monocularEndpointId === stratosMonocularEndpointGuid ? null : this.monocularEndpointId, + chartUrl: getFirstChartUrl(this.version) }; // Make the request diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workload.utils.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workload.utils.ts new file mode 100644 index 0000000000..7c8fb71baa --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workload.utils.ts @@ -0,0 +1,9 @@ +import { ChartVersion } from '../../helm/monocular/shared/models/chart-version'; + +// Get first URL for a Chart or return empty string if none +export function getFirstChartUrl(chart: ChartVersion): string { + if (chart && chart.attributes && chart.attributes.urls && chart.attributes.urls.length > 0) { + return chart.attributes.urls[0]; + } + return ''; +} diff --git a/src/jetstream/cnsi.go b/src/jetstream/cnsi.go index daf0ea3446..685e5fef03 100644 --- a/src/jetstream/cnsi.go +++ b/src/jetstream/cnsi.go @@ -633,5 +633,12 @@ func (p *portalProxy) updateEndpoint(c echo.Context) error { } } + // Notify plugins if they support the notification interface + for _, plugin := range p.Plugins { + if notifier, ok := plugin.(interfaces.EndpointNotificationPlugin); ok { + notifier.OnEndpointNotification(interfaces.EndpointUpdateAction, &endpoint) + } + } + return nil } diff --git a/src/jetstream/config.dev b/src/jetstream/config.dev index a98e5c3ec3..9b98a9b21b 100644 --- a/src/jetstream/config.dev +++ b/src/jetstream/config.dev @@ -53,3 +53,6 @@ AUTH_ENDPOINT_TYPE=local LOCAL_USER=admin LOCAL_USER_PASSWORD=admin LOCAL_USER_SCOPE=stratos.admin + +# Cache folder for Helm Charts +HELM_CACHE_FOLDER=./.helm-cache diff --git a/src/jetstream/config.example b/src/jetstream/config.example index 417f5f8144..3723583c27 100644 --- a/src/jetstream/config.example +++ b/src/jetstream/config.example @@ -68,15 +68,11 @@ INVITE_USER_CLIENT_SECRET= # DB_DATABASE_NAME=stratosdb # DB_SSL_MODE=disable -# Helm Monocular configuration -#FDB_URL="ongodb://127.0.0.1:27016" -#SYNC_SERVER_URL=http://127.0.0.1:8080" - -# Simplify development with FDB (value is port of FDB server: 27016 for FDB, 27017 for MongoDB) -#FDB_LOCAL_DEV=27017 - # Analysis services API #ANALYSIS_SERVICES_API= # Download link when installing the Kubernetes Dashboard in a targetted Kube Endpoint # STRATOS_KUBERNETES_DASHBOARD_IMAGE= + +# Cache folder for Helm Charts +# HELM_CACHE_FOLDER=./helm-cache \ No newline at end of file diff --git a/src/jetstream/go.mod b/src/jetstream/go.mod index 989a9fc1b8..9c59189023 100644 --- a/src/jetstream/go.mod +++ b/src/jetstream/go.mod @@ -35,6 +35,7 @@ require ( github.com/elazarl/goproxy/ext v0.0.0-20200315184450-1f3cb6622dad // indirect github.com/fatih/color v1.7.0 // indirect github.com/go-sql-driver/mysql v1.5.0 + github.com/golang/snappy v0.0.1 // indirect github.com/google/go-querystring v1.0.0 // indirect github.com/google/martian v2.1.0+incompatible github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e // indirect @@ -74,11 +75,12 @@ require ( github.com/valyala/fasttemplate v1.1.0 // indirect github.com/vito/go-interact v0.0.0-20171111012221-fa338ed9e9ec // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect - golang.org/x/crypto v0.0.0-20191205161847-0a08dada0ff9 + golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 gopkg.in/cheggaaa/pb.v1 v1.0.27 // indirect - gopkg.in/yaml.v2 v2.2.8 + gopkg.in/yaml.v2 v2.3.0 k8s.io/apiextensions-apiserver v0.0.0 // indirect + k8s.io/helm v2.16.10+incompatible // indirect k8s.io/kubectl v0.0.0 // indirect ) @@ -90,8 +92,6 @@ replace ( github.com/SermoDigital/jose => github.com/SermoDigital/jose v0.9.2-0.20180104203859-803625baeddc github.com/Sirupsen/logrus => github.com/sirupsen/logrus v1.4.1 github.com/docker/docker => github.com/moby/moby v0.7.3-0.20190826074503-38ab9da00309 - github.com/helm/monocular/chartrepo => ./plugins/monocular/chart-repo - github.com/helm/monocular/chartsvc => ./plugins/monocular/chartsvc github.com/kubernetes-sigs/aws-iam-authenticator => github.com/kubernetes-sigs/aws-iam-authenticator v0.3.1-0.20190111160901-390d9087a4bc github.com/russross/blackfriday v2.0.0+incompatible => github.com/russross/blackfriday v1.5.2 github.com/sergi/go-diff => github.com/sergi/go-diff v1.0.0 diff --git a/src/jetstream/go.sum b/src/jetstream/go.sum index ed4bbbd391..219e52d2ec 100644 --- a/src/jetstream/go.sum +++ b/src/jetstream/go.sum @@ -27,17 +27,21 @@ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbt github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.1.3 h1:sVIxMSTMnnVzl9Bn6BMjW6p5lSbpjHL80mqnxMWJ/oE= github.com/DATA-DOG/go-sqlmock v1.1.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e h1:eb0Pzkt15Bm7f2FFYv7sjY7NPFi3cPkS3tv1CcrFBWA= github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.0.1 h1:2kKm5lb7dKVrt5TYUiAavE6oFc1cFT0057UVGT+JqLk= github.com/Masterminds/semver/v3 v3.0.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk= +github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig v2.18.0+incompatible h1:QoGhlbC6pter1jxKnjMFxT8EqsLuDE6FEcNbWEpw+lI= github.com/Masterminds/sprig v2.18.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/sprig/v3 v3.0.0 h1:KSQz7Nb08/3VU9E4ns29dDxcczhOD1q7O1UfM4G3t3g= @@ -64,6 +68,7 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/antonlindstrom/pgstore v0.0.0-20170604072116-a407030ba6d0 h1:e6PEaXbztY0ViaKotCICNnBQDUeNEJgrQ5UAHWlloh4= github.com/antonlindstrom/pgstore v0.0.0-20170604072116-a407030ba6d0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw= github.com/apoydence/eachers v0.0.0-20181020210610-23942921fe77/go.mod h1:bXvGk6IkT1Agy7qzJ+DjIw/SJ1AaB3AvAuMDVV+Vkoo= +github.com/arschles/assert v1.0.0 h1:NofQbRhtxcLgP+XoKunA7J6UMJNTqX7xR/19tej8UsA= github.com/arschles/assert v1.0.0/go.mod h1:m/u69zW43x0h8dTHcv3JJZljINyEYgBuf5fYJP6WikI= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= @@ -287,6 +292,7 @@ github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORR github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -314,6 +320,7 @@ github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8 github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/helm/monocular v1.4.0 h1:g0sOpuMe+9u+aPfd9ZO8mWV+c8W0dfGyBG9Wl23nwec= github.com/helm/monocular v1.4.0/go.mod h1:PpkCN0v4zVVigsIHnsQdJytKFmaUkwfhxB7z33a9/gE= +github.com/helm/monocular v1.10.0 h1:/tkbVH0+7GR2C4W2ODJGiVGXyHmYCa3vaBb5S+pz6bE= github.com/heptio/authenticator v0.3.0/go.mod h1:Q86X8hc61JXhE5XxYLKmrSRWby/Oe8IIYZIBgmGVkTA= github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40 h1:GT4RsKmHh1uZyhmTkWJTDALRjSHYQp6FRKrotf0zhAs= github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40/go.mod h1:NtmN9h8vrTveVQRLHcX2HQ5wIPBDCsZ351TGbZWgg38= @@ -337,6 +344,7 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= @@ -347,10 +355,12 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a h1:VqeX/fehAB6FtBox0TVYcjOMXGE56INQIfbXegditX4= github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a/go.mod h1:TsgmjeDpbftqhwPKInJ3v+l+xbHs4goiB6DFb2WqY9c= @@ -486,7 +496,9 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3 h1:hBSHahWMEgzwRyS6dRpxY0XyjZsHyQ61s084wo5PJe0= github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20160503033757-d4c757aa9afd h1:ZDVcuZGxvWB1ooKj1e31P/ktQK4A2WumM+LucMENpds= github.com/smartystreets/goconvey v0.0.0-20160503033757-d4c757aa9afd/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -560,6 +572,8 @@ golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191205161847-0a08dada0ff9 h1:abxekknhS/Drh3uoQDk5Hc7BgeiyI39Crb7vhf/1j5s= golang.org/x/crypto v0.0.0-20191205161847-0a08dada0ff9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -599,6 +613,7 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -682,6 +697,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= helm.sh/helm/v3 v3.0.0 h1:or/9cs1GgfcTQeEnR2CVJNw893/rmqIG1KsNHmUiSFw= helm.sh/helm/v3 v3.0.0/go.mod h1:sI7B9yfvMgxtTPMWdk1jSKJ2aa59UyP9qhPydqW6mgo= @@ -693,6 +710,8 @@ k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8 k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/helm v2.16.1+incompatible h1:L+k810plJlaGWEw1EszeT4deK8XVaKxac1oGcuB+WDc= k8s.io/helm v2.16.1+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= +k8s.io/helm v2.16.10+incompatible h1:eFksERw3joHEL62TrcDX8I5fgEQJvit4qxxPXAkYTyQ= +k8s.io/helm v2.16.10+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= diff --git a/src/jetstream/plugins/kubernetes/go.mod b/src/jetstream/plugins/kubernetes/go.mod index ebeac9e5a1..e5d44dd87d 100644 --- a/src/jetstream/plugins/kubernetes/go.mod +++ b/src/jetstream/plugins/kubernetes/go.mod @@ -13,7 +13,6 @@ require ( github.com/ghodss/yaml v1.0.0 github.com/gorilla/websocket v1.4.0 github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect - github.com/helm/monocular/chartsvc v0.0.0-00010101000000-000000000000 github.com/heptio/authenticator v0.3.0 // indirect github.com/kubernetes-sigs/aws-iam-authenticator v0.3.0 github.com/labstack/echo v3.3.10+incompatible @@ -35,7 +34,6 @@ replace ( github.com/SermoDigital/jose => github.com/SermoDigital/jose v0.9.2-0.20180104203859-803625baeddc github.com/cloudfoundry-incubator/stratos/src/jetstream => ../.. github.com/docker/docker => github.com/moby/moby v0.7.3-0.20190826074503-38ab9da00309 - github.com/helm/monocular/chartsvc => ../monocular/chartsvc github.com/kubernetes-sigs/aws-iam-authenticator => github.com/kubernetes-sigs/aws-iam-authenticator v0.3.1-0.20190111160901-390d9087a4bc github.com/russross/blackfriday v2.0.0+incompatible => github.com/russross/blackfriday v1.5.2 github.com/sergi/go-diff => github.com/sergi/go-diff v1.0.0 diff --git a/src/jetstream/plugins/kubernetes/install_release.go b/src/jetstream/plugins/kubernetes/install_release.go index 575c0b7065..7998300c8b 100644 --- a/src/jetstream/plugins/kubernetes/install_release.go +++ b/src/jetstream/plugins/kubernetes/install_release.go @@ -3,7 +3,6 @@ package kubernetes import ( "bytes" "encoding/json" - "errors" "fmt" "github.com/labstack/echo" @@ -28,6 +27,7 @@ type installRequest struct { Name string `json:"releaseName"` Namespace string `json:"releaseNamespace"` Values string `json:"values"` + ChartURL string `json:"chartUrl"` Chart struct { Name string `json:"chartName"` Repository string `json:"repo"` @@ -38,6 +38,7 @@ type installRequest struct { type upgradeRequest struct { MonocularEndpoint string `json:"monocularEndpoint"` Values string `json:"values"` + ChartURL string `json:"chartUrl"` Chart struct { Name string `json:"name"` Repository string `json:"repo"` @@ -46,11 +47,6 @@ type upgradeRequest struct { RestartPods bool `json:"restartPods"` } -// Monocular is a plugin for Monocular -type Monocular interface { - GetChartDownloadUrl(monocularEndpoint, chartID, chartVersion string) (string, error) -} - // InstallRelease will install a Helm 3 release func (c *KubernetesSpecification) InstallRelease(ec echo.Context) error { bodyReader := ec.Request().Body @@ -63,7 +59,12 @@ func (c *KubernetesSpecification) InstallRelease(ec echo.Context) error { return interfaces.NewJetstreamErrorf("Could not get Create Release Parameters: %v+", err) } - chart, err := c.loadChart(params.MonocularEndpoint, params.Chart.Repository, params.Chart.Name, params.Chart.Version) + // Client must give us the download URL for the chart + if len(params.ChartURL) == 0 { + return interfaces.NewJetstreamErrorf("Client did not supply Chart download URL") + } + + chart, err := c.loadChart(params.ChartURL) if err != nil { return interfaces.NewJetstreamErrorf("Could not load chart: %v+", err) } @@ -112,29 +113,8 @@ func (c *KubernetesSpecification) InstallRelease(ec echo.Context) error { return ec.JSON(200, release) } -func (c *KubernetesSpecification) getChart(monocularEndpoint, chartID, version string) (string, error) { - helm := c.portalProxy.GetPlugin("monocular") - if helm == nil { - return "", errors.New("Could not find monocular plugin") - } - - monocular, ok := helm.(Monocular) - if !ok { - return "", errors.New("Could not find monocular plugin interface") - } - - return monocular.GetChartDownloadUrl(monocularEndpoint, chartID, version) -} - // Load the Helm chart for the given repository, name and version -func (c *KubernetesSpecification) loadChart(monocularEndpoint, repo, name, version string) (*chart.Chart, error) { - - chartID := fmt.Sprintf("%s/%s", repo, name) - downloadURL, err := c.getChart(monocularEndpoint, chartID, version) - if err != nil { - return nil, fmt.Errorf("Could not get the Download URL for the Helm Chart: %+v", err) - } - +func (c *KubernetesSpecification) loadChart(downloadURL string) (*chart.Chart, error) { log.Debugf("Helm Chart Download URL: %s", downloadURL) // NWM: Should we look up Helm Repository endpoint and use the value from that @@ -218,6 +198,16 @@ func (c *KubernetesSpecification) UpgradeRelease(ec echo.Context) error { return interfaces.NewJetstreamErrorf("Could not get Upgrade Release Parameters: %+v", err) } + // Client must give us the download URL for the chart + if len(params.ChartURL) == 0 { + return interfaces.NewJetstreamErrorf("Client did not supply Chart download URL") + } + + chart, err := c.loadChart(params.ChartURL) + if err != nil { + return interfaces.NewJetstreamErrorf("Could not load chart for upgrade: %+v", err) + } + config, hc, err := c.GetHelmConfiguration(endpointGUID, userGUID, namespace) if err != nil { return interfaces.NewJetstreamErrorf("Could not get Helm Configuration for endpoint: %+v", err) @@ -225,11 +215,6 @@ func (c *KubernetesSpecification) UpgradeRelease(ec echo.Context) error { defer hc.Cleanup() - chart, err := c.loadChart(params.MonocularEndpoint, params.Chart.Repository, params.Chart.Name, params.Chart.Version) - if err != nil { - return interfaces.NewJetstreamErrorf("Could not load chart for upgrade: %+v", err) - } - userSuppliedValues := map[string]interface{}{} if err := yaml.Unmarshal([]byte(params.Values), &userSuppliedValues); err != nil { // Could not parse the user's values diff --git a/src/jetstream/plugins/monocular/20200819184800_ChartStore.go b/src/jetstream/plugins/monocular/20200819184800_ChartStore.go new file mode 100644 index 0000000000..41ef1ebebf --- /dev/null +++ b/src/jetstream/plugins/monocular/20200819184800_ChartStore.go @@ -0,0 +1,49 @@ +package monocular + +import ( + "database/sql" + + "bitbucket.org/liamstask/goose/lib/goose" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/datastore" +) + +func init() { + datastore.RegisterMigration(20200819184800, "HelmChartStore", func(txn *sql.Tx, conf *goose.DBConf) error { + + createChartsTable := "CREATE TABLE IF NOT EXISTS helm_charts (" + createChartsTable += "endpoint VARCHAR(64) NOT NULL," + createChartsTable += "name VARCHAR(255) NOT NULL," + createChartsTable += "repo_name VARCHAR(255) NOT NULL," + createChartsTable += "version VARCHAR(64) NOT NULL," + createChartsTable += "created TIMESTAMP NOT NULL," + createChartsTable += "app_version VARCHAR(64) NOT NULL," + createChartsTable += "description VARCHAR(255) NOT NULL," + createChartsTable += "icon_url VARCHAR(255) NOT NULL," + createChartsTable += "chart_url VARCHAR(255) NOT NULL," + createChartsTable += "source_url VARCHAR(255) NOT NULL," + createChartsTable += "digest VARCHAR(64) NOT NULL," + createChartsTable += "is_latest BOOLEAN NOT NULL DEFAULT FALSE," + createChartsTable += "update_batch VARCHAR(64) NOT NULL," + createChartsTable += "PRIMARY KEY (endpoint, name, version) );" + + _, err := txn.Exec(createChartsTable) + if err != nil { + return err + } + + createIndex := "CREATE INDEX helm_charts_endpoint ON helm_charts (endpoint, name, version);" + _, err = txn.Exec(createIndex) + if err != nil { + return err + } + + createRepoIndex := "CREATE INDEX helm_charts_repository ON helm_charts (name, repo_name, version);" + _, err = txn.Exec(createRepoIndex) + if err != nil { + return err + } + + return nil + }) +} diff --git a/src/jetstream/plugins/monocular/README.md b/src/jetstream/plugins/monocular/README.md deleted file mode 100644 index 956b10490c..0000000000 --- a/src/jetstream/plugins/monocular/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# Syncing this subtree with the Monocular repo - -## Common Prerequisites - -**NOTE:** - -The scripts do their best to fail gracefully. To avoid problems however, make sure the following pre-requisities are done before attempting. A CTRL-C in the middle of a run will likely require some cleanup. - - -1. Ensure you have the monocular fork as a remote. e.g. - -``` -git remote add -f -t master stratos-monocular-fork git@github.com:cf-stratos/monocular.git -``` - -2. With a second remote it's useful to store remote refs separately in case of branch/tag collisions: - -``` -git config --add remote.stratos-monocular-fork.fetch 'refs/tags/*:refs/rtags/stratos-monocular-fork/* -``` - -## Before running the PULL script -Follow these carefully. - -1. Ensure you are on the branch you want to merge into. -2. Ensure you have no unstaged or uncommitted changes -3. `git status` should be clean - -## Before running the PUSH script -1. Ensure that at least one pull haa been made already -2. Ensure the monocular target branch exists and has been pushed to its monocular remote -3. Ensure you have no unstaged or uncommitted changes. -4. `git status` should be clean. - -## Running the scripts.. - -Both scripts, when run either without arguments or with the `-h` flag, will show usage instructions. - -### PULL script: - -This pulls the subtree from monocular repo under `/cmd` into the stratos repo under `/src/jetstream/plugins/monocular` - -Usage: - -``` -git-pull-downstream.sh -f <MONOCULAR_FORK> [ -b <MONOCULAR_BRANCH> | -r <MONOCULAR_REF> ] --f MONOCULAR_FORK The upstream (monocular) repo" --b MONOCULAR_BRANCH The upstream branch to pull from" >&1 --r MONOCULAR_REF The upsream ref to pull from (e.g. tag or SHA1) -``` - -e.g. - -``` -./git-pull-downstream.sh -f stratos-monocular-fork -b master -``` - -#### To pull a tag: -The tag should be specufied as : `refs/rtags/stratos-monocular-fork/<tag>` - -e.g. - -``` -./git-pull-downstream.sh -f stratos-monocular-fork -r refs/rtags/stratos-monocular-fork/v1.4.0 -``` - - -### PUSH script: -Usage: - -``` -git-push-upstream.sh -f <MONOCULAR_FORK> -t <MONOCULAR_BRANCH> -s <STRATOS_SOURCE_BRANCH> ] --f MONOCULAR_FORK The upstream (monocular) repo to push to" --t MONOCULAR_BRANCH The target (monocular) upstream branch to push to --s STRATOS_SOURCE_BRANCH The source stratos branch containing the changes -``` - -e.g. - -``` -./git-push-upstream.sh -f stratos-monocular-fork -t feature-branch -s dummy-master -``` \ No newline at end of file diff --git a/src/jetstream/plugins/monocular/README.txt b/src/jetstream/plugins/monocular/README.txt deleted file mode 100644 index 149af612ae..0000000000 --- a/src/jetstream/plugins/monocular/README.txt +++ /dev/null @@ -1,13 +0,0 @@ -### Run chart repo in serve mode WITH TLS ### -### Modify the bind mounts according to location of certs on your system - -docker run -p 8080:8080 --rm -e -ti --network=host -v ~/dev/git/fdb-doclayer-fork/packaging/docker/testcerts:/etc/tlscerts kreinecke/suse-fdb-chart-repo:latest /chartrepo serve --doclayer-url=mongodb://localhost:27016 stable https://kubernetes-charts.storage.googleapis.com --cafile /etc/tlscerts/ca.crt --certfile /etc/tlscerts/client.crt --keyfile /etc/tlscerts/client.key --debug - -To test sync and status fetch: -curl -X PUT -H "Content-Type: application/json" -d '{"repoURL":"https://kubernetes-charts.storage.googleapis.com"}' http://localhost:8080/v1/sync/stable - -curl -X GET http://localhost:8080/v1/status/stable - - -### TLS Currently not optional, but for reference, use command below if making optional in future ### -docker run -p 8080:8080 --rm -e -ti --network=host kreinecke/suse-fdb-chart-repo:latest /chartrepo serve --doclayer-url=mongodb://192.168.57.1:27016 stable https://kubernetes-charts.storage.googleapis.com diff --git a/src/jetstream/plugins/monocular/cache.go b/src/jetstream/plugins/monocular/cache.go new file mode 100644 index 0000000000..6e38df02ec --- /dev/null +++ b/src/jetstream/plugins/monocular/cache.go @@ -0,0 +1,341 @@ +package monocular + +import ( + "archive/tar" + "compress/gzip" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + "strings" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/monocular/store" + log "github.com/sirupsen/logrus" + yaml "gopkg.in/yaml.v2" +) + +// Local Helm Chart Cache + +// A local file cache stores chart files +// We only download the files for the latest version - other versions are downloaded on demand +// Within the cache there is a folder for each endpoint (helm repository) and under each of those +// a folder for each chart with chart files in ana a digest tag file + +// If there are multiple Stratos backends, then each maintains its own cache + +// The Chart index is stored in the database + +const digestFilename = "digest" + +// deleteCacheForEndpoint will delete all cached files for the given endpoint +func (m *Monocular) deleteCacheForEndpoint(endpointID string) error { + endpointCacheFolder := path.Join(m.CacheFolder, endpointID) + return os.RemoveAll(endpointCacheFolder) +} + +// cacheCharts will cache charts in the local folder cache +func (m *Monocular) cacheCharts(charts []store.ChartStoreRecord) error { + + var errorCount = 0 + log.Debug("Cacheing charts") + for _, chart := range charts { + log.Debugf("Processing: %s", chart.Name) + if err := m.cacheChart(chart); err != nil { + errorCount++ + log.Warnf("Error cacheing chart: %s - %+v", chart.Name, err) + } + if _, err := m.cacheChartIcon(chart); err != nil { + errorCount++ + log.Warnf("Error cacheing chart icon: %s - %+v", chart.Name, err) + } + + } + if errorCount > 0 { + return errors.New("Error(s) occurred caching charts") + } + + return nil +} + +// Get the cache folder path for a chart +func (m *Monocular) getChartCacheFolder(chart store.ChartStoreRecord) string { + filename := fmt.Sprintf("%s_%s", chart.Name, chart.Version) + return path.Join(m.CacheFolder, chart.EndpointID, filename) +} + +// cleanCacheFiles will Clean all files in the folder for an endpoint that are not referenced by any of the charts we have for that endpoint +func (m *Monocular) cleanCacheFiles(endpointID string, allCharts []store.ChartStoreRecord) error { + + // Build map of the valid chart folder names + validFiles := make(map[string]bool) + for _, chart := range allCharts { + validFiles[m.getChartCacheFolder(chart)] = true + } + + endpointCacheFolder := path.Join(m.CacheFolder, endpointID) + // Don't delete the top-level cache folder for the endpoint + validFiles[endpointCacheFolder] = true + errorCount := 0 + filepath.Walk(endpointCacheFolder, func(path string, info os.FileInfo, err error) error { + if err == nil && info.IsDir() { + if _, ok := validFiles[path]; !ok { + // Filename does not exist in the map of valid file names + log.Debugf("Need to delete unused cache folder: %s", path) + if err := os.RemoveAll(path); err != nil { + log.Errorf("Could not delete folder %s - %+v", path, err) + errorCount++ + } + } + } + return nil + }) + + if errorCount > 0 { + return fmt.Errorf("Error(s) occurred cleaning unused folders from the cache folder for endpoint %s", endpointID) + } + + return nil +} + +// Is there a chart digest in the given folder with the given value? +func hasDigestFile(chartCachePath, digest string) bool { + data, err := ioutil.ReadFile(path.Join(chartCachePath, digestFilename)) + if err == nil { + chk := strings.TrimSpace(string(data)) + return chk == digest + } + + return false +} + +// write the chart digest to a file +func writeDigestFile(chartCachePath, digest string) error { + return ioutil.WriteFile(path.Join(chartCachePath, digestFilename), []byte(digest), 0644) +} + +func (m *Monocular) getChartYaml(chart store.ChartStoreRecord) *ChartMetadata { + + // Cache the Chart if we don't have it already + m.cacheChart(chart) + + chartCacheYamlPath := path.Join(m.getChartCacheFolder(chart), "Chart.yaml") + if _, err := os.Stat(chartCacheYamlPath); os.IsNotExist(err) { + return nil + } + + // Check we can unmarshall the request + data, err := ioutil.ReadFile(chartCacheYamlPath) + if err != nil { + return nil + } + + // Parse as yaml + var chartYaml ChartMetadata + err = yaml.Unmarshal(data, &chartYaml) + if err != nil { + return nil + } + return &chartYaml +} + +// Get the cache file path for the chart icon +func (m *Monocular) getIconCacheFile(chart store.ChartStoreRecord) string { + ext := "" + u, err := url.Parse(chart.IconURL) + if err == nil { + parts := strings.Split(u.Path, "/") + filename := parts[len(parts)-1] + index := strings.LastIndex(filename, ".") + if index != -1 { + ext = filename[index:] + } + } + + filename := fmt.Sprintf("icon%s", ext) + return path.Join(m.getChartCacheFolder(chart), filename) +} + +func (m *Monocular) ensureFolder(path string) error { + if _, err := os.Stat(path); os.IsNotExist(err) { + return os.MkdirAll(path, os.ModePerm) + } + + return nil +} + +// Cache chart +// Check to see if we already have files for the given chart - if not, download the archive +// and extract the files we need: +// Chart.yaml, README.md, values.yaml, values.schema.json +// Download the icon as well +func (m *Monocular) cacheChart(chart store.ChartStoreRecord) error { + log.Debugf("Cacheing chart: %s, %s", chart.Name, chart.Version) + + chartCachePath := m.getChartCacheFolder(chart) + if err := m.ensureFolder(chartCachePath); err != nil { + log.Warnf("Could not create folder for chart downloads: %+v", err) + return err + } + + // Check to see if we have the same digest + if ok := hasDigestFile(chartCachePath, chart.Digest); ok { + log.Debug("Skipping download - already have archive with the same digest") + return nil + } + + archiveFile := path.Join(chartCachePath, "chart.tgz") + if err := m.downloadFile(archiveFile, chart.ChartURL); err != nil { + return fmt.Errorf("Could not download chart from: %s - %+v", chart.ChartURL, err) + } + + sum, err := getFileChecksum(archiveFile) + if err != nil { + return fmt.Errorf("Could not calculate checksum for chart archive: %s - %+v", archiveFile, err) + } + + if err := writeDigestFile(chartCachePath, sum); err != nil { + return fmt.Errorf("Could not write chart digest file in: %s - %+v", chartCachePath, err) + } + + // Now extract the files we need + filenames := []string{"Chart.yaml", "README.md", "values.schema.json", "values.yaml"} + if err := extractArchiveFiles(archiveFile, chart.Name, chartCachePath, filenames); err != nil { + return fmt.Errorf("Could not extract files from chart archive: %s - %+v", archiveFile, err) + } + + // We can delete the Chart archive - don't need it anymore + os.Remove(archiveFile) + + return nil +} + +// Cache a chart icon +func (m *Monocular) cacheChartIcon(chart store.ChartStoreRecord) (string, error) { + log.Debugf("Cacheing chart icon: %s, %s", chart.Name, chart.Version) + if len(chart.IconURL) > 0 { + log.Debugf("Downloading chart icon: %s", chart.IconURL) + // If icon file already exists then don't download again + iconFilePath := m.getIconCacheFile(chart) + if _, err := os.Stat(iconFilePath); os.IsNotExist(err) { + if err := m.ensureFolder(path.Dir(iconFilePath)); err != nil { + log.Error(err) + } else if err := m.downloadFile(iconFilePath, chart.IconURL); err != nil { + log.Errorf("Could not download chart icon: %+v", err) + return "", fmt.Errorf("Could not download Chart icon: %+v", err) + } + } + return iconFilePath, nil + } + + return "", nil +} + +// download a file form the given url sand save to the file path +func (m *Monocular) downloadFile(filepath string, url string) error { + // Get the data + httpClient := m.portalProxy.GetHttpClient(false) + resp, err := httpClient.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("Error downloading icon: %s - %d:%s", url, resp.StatusCode, resp.Status) + } + + // Create the file + out, err := os.Create(filepath) + if err != nil { + return err + } + defer out.Close() + + // Write the body to file + _, err = io.Copy(out, resp.Body) + return err +} + +func extractArchiveFiles(archivePath, chartName, downloadFolder string, filenames []string) error { + // Map the filenames array into a map of path to destination file + requiredFiles := make(map[string]string) + requiredCount := len(filenames) + for _, name := range filenames { + requiredFiles[fmt.Sprintf("%s/%s", chartName, name)] = path.Join(downloadFolder, name) + } + + f, err := os.Open(archivePath) + if err != nil { + log.Error("Helm: Archive extract file: Could not open file %s - %+v", archivePath, err) + return err + } + defer f.Close() + + gzf, err := gzip.NewReader(f) + if err != nil { + log.Error("Helm: Archive extract file: Could not open zip file %s - %+v", archivePath, err) + return err + } + + tarReader := tar.NewReader(gzf) + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } + + if err != nil { + log.Error("Helm: Archive extract file: Could not process archive file %s - %+v", archivePath, err) + return err + } + + name := header.Name + switch header.Typeflag { + case tar.TypeDir: + continue + case tar.TypeReg: + // Is this a file we are looking for? + if downloadPath, ok := requiredFiles[name]; ok { + // Create the file + out, err := os.Create(downloadPath) + if err != nil { + return err + } + defer out.Close() + + io.Copy(out, tarReader) + + // If we have extracted all of the files we are looking for, then return early, rather than + // going through the rest of the files + requiredCount-- + if requiredCount == 0 { + return nil + } + } + } + } + + return nil +} + +// get the SHA256 checksum for a file +func getFileChecksum(file string) (string, error) { + f, err := os.Open(file) + if err != nil { + return "", err + } + defer f.Close() + hasher := sha256.New() + if _, err := io.Copy(hasher, f); err != nil { + return "", err + } + + return hex.EncodeToString(hasher.Sum(nil)), nil +} diff --git a/src/jetstream/plugins/monocular/chart-repo/.gitignore b/src/jetstream/plugins/monocular/chart-repo/.gitignore deleted file mode 100644 index 253eef939d..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/.gitignore +++ /dev/null @@ -1 +0,0 @@ -chart-repo-sync diff --git a/src/jetstream/plugins/monocular/chart-repo/Dockerfile b/src/jetstream/plugins/monocular/chart-repo/Dockerfile deleted file mode 100644 index ac5d67a440..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM splatform/stratos-bk-build-base:leap15_1 as builder - -COPY --chown=stratos:users . /go/src/github.com/helm/monocular -WORKDIR /go/src/github.com/helm/monocular -ARG VERSION -RUN GO111MODULE=on GOPROXY=https://gocenter.io CGO_ENABLED=0 go build -a -installsuffix cgo -ldflags "-X main.version=$VERSION" . - -FROM splatform/stratos-bk-base:leap15_1 -#COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ -COPY --from=builder /go/src/github.com/helm/monocular/chartrepo /chartrepo -USER 1001 -CMD ["/chartrepo"] diff --git a/src/jetstream/plugins/monocular/chart-repo/Makefile b/src/jetstream/plugins/monocular/chart-repo/Makefile deleted file mode 100644 index 42140b79a8..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -IMAGE_REPO ?= docker.io/kreinecke/suse-fdb-chart-repo-tls -IMAGE_TAG ?= latest -# Version of the binary to be produced -VERSION ?= $$(git rev-parse HEAD) - -docker-build: - # We use the context of the root dir - docker build --pull --rm -t ${IMAGE_REPO}:${IMAGE_TAG} --build-arg "VERSION=${VERSION}" -f Dockerfile . diff --git a/src/jetstream/plugins/monocular/chart-repo/api_endpoint.go b/src/jetstream/plugins/monocular/chart-repo/api_endpoint.go deleted file mode 100644 index 8040e204b2..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/api_endpoint.go +++ /dev/null @@ -1,323 +0,0 @@ -/* -Copyright (c) 2017 The Helm Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package main - -import ( - "context" - "crypto/tls" - "crypto/x509" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "os" - "time" - - "github.com/google/uuid" - "github.com/gorilla/mux" - "github.com/heptiolabs/healthcheck" - log "github.com/sirupsen/logrus" - "github.com/urfave/negroni" - "go.mongodb.org/mongo-driver/mongo/options" - - "github.com/helm/monocular/chartrepo/common" - fdb "github.com/helm/monocular/chartrepo/foundationdb" -) - -const pathPrefix = "/v1" - -var fdbClient fdb.Client -var fDBName string -var authorizationHeader string - -// Params a key-value map of path params -type Params map[string]string - -// WithParams can be used to wrap handlers to take an extra arg for path params -type WithParams func(http.ResponseWriter, *http.Request, Params) - -func (h WithParams) ServeHTTP(w http.ResponseWriter, req *http.Request) { - vars := mux.Vars(req) - h(w, req, vars) -} - -func setupRoutes() http.Handler { - r := mux.NewRouter() - - // Healthcheck - health := healthcheck.NewHandler() - r.Handle("/live", health) - r.Handle("/ready", health) - - // Routes - apiv1 := r.PathPrefix(pathPrefix).Subrouter() - apiv1.Methods("PUT").Path("/sync/{repo}").Handler(WithParams(OnDemandSync)) - apiv1.Methods("PUT").Path("/delete/{repo}").Handler(WithParams(OnDemandDelete)) - apiv1.Methods("GET").Path("/status/{repo}").Handler(WithParams(RepoSyncStatus)) - - n := negroni.Classic() - n.UseHandler(r) - return n -} - -func OnDemandSync(w http.ResponseWriter, req *http.Request, params Params) { - - //Running in serve mode, we dont want to close the db client connection after a request - var clientKeepAlive = true - - type syncParams struct { - RepoURL string `json:"repoURL"` - } - - repoName := params["repo"] - if repoName == "" { - err := fmt.Errorf("No Repository name provided in request for Sync action.") - log.Error(err.Error()) - w.Header().Set("Server", "ChartRepo (On-Demand)") - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - var status string - //If this repo is already syncing, don't start another sync, but return in progress response - //Ignore error return value when fetching status - we don't care if the repo does not exist yet - currentRepoStatus, _ := fdb.GetRepoSyncStatus(repoName) - activeSyncJob := currentRepoStatus.Status == common.SyncStatusInProgress || currentRepoStatus.Status == common.SyncStatusInProgress - if activeSyncJob { - status = common.SyncStatusInProgress - } else { - - dec := json.NewDecoder(req.Body) - var url syncParams - if err := dec.Decode(&url); err != nil { - log.Error(err.Error()) - w.Header().Set("Server", "ChartRepo (On-Demand)") - http.Error(w, "Error decoding sync request repository URL: "+err.Error(), http.StatusBadRequest) - return - } - - repoURL := url.RepoURL - - if repoURL == "" { - err := fmt.Errorf("No Repository URL provided in request for Sync action.") - log.Error(err.Error()) - w.Header().Set("Server", "ChartRepo (On-Demand)") - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - go fdb.SyncRepo(fdbClient, fDBName, repoName, repoURL, authorizationHeader, clientKeepAlive) - - status = common.SyncStatusInProgress - } - - requestUUID, err := uuid.NewUUID() - - //Return sync status in response - response := common.SyncJobStatusResponse{requestUUID.String(), status} - js, err := json.Marshal(response) - if err != nil { - log.Error(err.Error()) - w.Header().Set("Server", "ChartRepo (On-Demand)") - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - w.Header().Set("Server", "ChartRepo (On-Demand)") - w.Write(js) -} - -func OnDemandDelete(w http.ResponseWriter, req *http.Request, params Params) { - repoName := params["repo"] - requestUUID, err := uuid.NewUUID() - if repoName == "" { - err := fmt.Errorf("No Repository name provided in request for Delete action.") - log.Error(err.Error()) - w.Header().Set("Server", "ChartRepo (On-Demand)") - http.Error(w, err.Error(), http.StatusBadRequest) - } - - var status string - currentRepoStatus, err := fdb.GetRepoSyncStatus(repoName) - if err != nil { - log.Errorf("Request: requestUUID, %v", err.Error()) - w.Header().Set("Server", "ChartRepo (On-Demand)") - http.Error(w, err.Error(), http.StatusNotFound) - return - } - //If this repo is already being deleted, don't start another delete, but return in progress response - activeDeleteJob := currentRepoStatus.Status == common.DeleteStatusInProgress || currentRepoStatus.Status == common.DeleteStatusInProgress - if activeDeleteJob { - status = common.DeleteStatusInProgress - } else { - - //Running in serve mode, we dont want to close the db client connection after a request - var clientKeepAlive = true - - go fdb.DeleteRepo(fdbClient, fDBName, repoName, clientKeepAlive) - status = common.DeleteStatusInProgress - } - - //Return delete status in response - - response := common.DeleteJobStatusResponse{requestUUID.String(), status} - js, err := json.Marshal(response) - if err != nil { - log.Error(err.Error()) - w.Header().Set("Server", "ChartRepo (On-Demand)") - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - w.Header().Set("Server", "ChartRepo (On-Demand)") - w.Write(js) -} - -func RepoSyncStatus(w http.ResponseWriter, req *http.Request, params Params) { - repoName := params["repo"] - if repoName == "" { - log.Fatal("No Repository name provided in request for sync status.") - } - - requestUUID, err := uuid.NewUUID() - status, err := fdb.GetRepoSyncStatus(repoName) - if err != nil { - log.Errorf("Request: requestUUID, %v, %v", requestUUID, err.Error()) - w.Header().Set("Server", "ChartRepo (On-Demand)") - http.Error(w, err.Error(), http.StatusNotFound) - return - } - - response := common.SyncJobStatusResponse{ - UUID: requestUUID.String(), - Status: status.Status, - } - js, err := json.Marshal(response) - if err != nil { - log.Errorf("Request: requestUUID, %v, %v", requestUUID, err.Error()) - w.Header().Set("Server", "ChartRepo (On-Demand)") - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - w.Header().Set("Server", "ChartRepo (On-Demand)") - w.Write(js) -} - -func RepoDeleteStatus(w http.ResponseWriter, req *http.Request, params Params) { - repoName := params["repo"] - if repoName == "" { - log.Fatal("No Repository name provided in request for delete status.") - } - - requestUUID, err := uuid.NewUUID() - status, err := fdb.GetRepoDeleteStatus(repoName) - if err != nil { - log.Errorf("Request: requestUUID, %v", err.Error()) - w.Header().Set("Server", "ChartRepo (On-Demand)") - http.Error(w, err.Error(), http.StatusNotFound) - return - } - - response := common.DeleteJobStatusResponse{requestUUID.String(), status.Status} - js, err := json.Marshal(response) - if err != nil { - log.Errorf("Request: requestUUID, %v", err.Error()) - w.Header().Set("Server", "ChartRepo (On-Demand)") - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - w.Header().Set("Server", "ChartRepo (On-Demand)") - w.Write(js) -} - -func initOnDemandEndpoint(fdbURL string, fdbName string, tlsEnabled bool, caCertFile string, certFile string, keyFile string, authHeader string, debug bool) { - - authorizationHeader = authHeader - fDBName = fdbName - - if debug { - log.SetLevel(log.DebugLevel) - } - - InitFDBDocLayerConnection(&fdbURL, &fdbName, &tlsEnabled, &caCertFile, &certFile, &keyFile, &debug) - - n := setupRoutes() - - port := os.Getenv("PORT") - if port == "" { - port = "8080" - } - addr := ":" + port - log.Infof("On-Demand endpoint listening on: %v", addr) - if tlsEnabled { - log.Infof("TLS is enabled.") - } else { - log.Infof("TLS is disabled.") - } - http.ListenAndServe(addr, n) -} - -func InitFDBDocLayerConnection(fdbURL *string, fDB *string, tlsEnabled *bool, CAFile *string, certFile *string, keyFile *string, debug *bool) { - - log.Debugf("Attempting to connect to FDB: %v, %v, debug: %v", *fdbURL, *fDB, *debug) - - var tlsConfig *tls.Config - - if *tlsEnabled { - //Load CA Cert from file here - CA, err := ioutil.ReadFile(*CAFile) // just pass the file name - if err != nil { - log.Fatalf("Cannot load CA certificate from file: %v.", err) - return - } - CACert := x509.NewCertPool() - ok := CACert.AppendCertsFromPEM([]byte(CA)) - if !ok { - log.Fatalf("Cannot append CA certificate to certificate pool.") - return - } - //Now load the key pair and create tls options struct - clientKeyPair, err := tls.LoadX509KeyPair(*certFile, *keyFile) - if err != nil { - log.Fatalf("Cannot load server keypair: %v", err) - return - } - - tlsConfig = &tls.Config{RootCAs: CACert, Certificates: []tls.Certificate{clientKeyPair}} - } - - //Init client options and open connection - clientOptions := options.Client().ApplyURI(*fdbURL).SetMinPoolSize(10).SetMaxPoolSize(100) - if *tlsEnabled { - clientOptions.SetTLSConfig(tlsConfig) - } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - client, err := fdb.NewDocLayerClient(ctx, clientOptions) - fdbClient = client - if err != nil { - log.Fatalf("Can't create client for FoundationDB document layer: %v. URL provided was: %v", err, *fdbURL) - return - } - log.Debugf("FDB Document Layer client created.") -} diff --git a/src/jetstream/plugins/monocular/chart-repo/chart_repo.go b/src/jetstream/plugins/monocular/chart-repo/chart_repo.go deleted file mode 100644 index ec917468d8..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/chart_repo.go +++ /dev/null @@ -1,64 +0,0 @@ -/* -Copyright (c) 2018 The Helm Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package main - -import ( - "os" - - "github.com/helm/monocular/chartrepo/utils" - - "github.com/spf13/cobra" -) - -var rootCmd = &cobra.Command{ - Use: "chart-repo", - Short: "Chart Repository utility", - Run: func(cmd *cobra.Command, args []string) { - cmd.Help() - }, -} - -func main() { - cmd := rootCmd - if err := cmd.Execute(); err != nil { - os.Exit(1) - } -} - -func init() { - - cmds := []*cobra.Command{SyncCmd, DeleteCmd, ServeCmd} - - for _, cmd := range cmds { - rootCmd.AddCommand(cmd) - - //Flags for optional FoundationDB + Document Layer backend - cmd.Flags().String("doclayer-url", "mongodb://dev-fdbdoclayer/27016", "FoundationDB Document Layer URL") - cmd.Flags().String("doclayer-database", "monocular-plugin", "FoundationDB Document-Layer database") - - //Flags for Serve-Mode TLS - cmd.Flags().Bool("tls", false, "Enable Mutual TLS") - cmd.Flags().String("cafile", "", "Path to CA certificate to use for client verification.") - cmd.Flags().String("certfile", "", "Path to TLS certificate.") - cmd.Flags().String("keyfile", "", "Path to TLS key.") - - // see version.go - cmd.Flags().StringVarP(&utils.UserAgentComment, "user-agent-comment", "", "", "UserAgent comment used during outbound requests") - cmd.Flags().Bool("debug", false, "verbose logging") - } - rootCmd.AddCommand(utils.VersionCmd) -} diff --git a/src/jetstream/plugins/monocular/chart-repo/common/chart_utils.go b/src/jetstream/plugins/monocular/chart-repo/common/chart_utils.go deleted file mode 100644 index 0a7298c0e2..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/common/chart_utils.go +++ /dev/null @@ -1,205 +0,0 @@ -/* -Copyright (c) 2019 The Helm Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package common - -import ( - "archive/tar" - "bytes" - "crypto/sha256" - "crypto/tls" - "crypto/x509" - "errors" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "os" - "path" - "strings" - "time" - - "github.com/helm/monocular/chartrepo/utils" - - "github.com/ghodss/yaml" - "github.com/jinzhu/copier" - log "github.com/sirupsen/logrus" - helmrepo "k8s.io/helm/pkg/repo" -) - -//ParseRepoURL parses a repo URL string into a URL -func ParseRepoURL(repoURL string) (*url.URL, error) { - repoURL = strings.TrimSpace(repoURL) - return url.ParseRequestURI(repoURL) -} - -//FetchRepoIndex fetches the index.yaml for a repository -func FetchRepoIndex(r Repo, netClient HTTPClient) ([]byte, error) { - indexURL, err := ParseRepoURL(r.URL) - if err != nil { - log.WithFields(log.Fields{"url": r.URL}).WithError(err).Error("failed to parse URL") - return nil, err - } - indexURL.Path = path.Join(indexURL.Path, "index.yaml") - req, err := http.NewRequest("GET", indexURL.String(), nil) - if err != nil { - log.WithFields(log.Fields{"url": req.URL.String()}).WithError(err).Error("could not build repo index request") - return nil, err - } - - req.Header.Set("User-Agent", utils.UserAgent()) - if len(r.AuthorizationHeader) > 0 { - req.Header.Set("Authorization", r.AuthorizationHeader) - } - - res, err := netClient.Do(req) - if res != nil { - defer res.Body.Close() - } - if err != nil { - log.WithFields(log.Fields{"url": req.URL.String()}).WithError(err).Error("error requesting repo index") - return nil, err - } - - if res.StatusCode != http.StatusOK { - log.WithFields(log.Fields{"url": req.URL.String(), "status": res.StatusCode}).Error("error requesting repo index, are you sure this is a chart repository?") - return nil, errors.New("repo index request failed") - } - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - return body, nil -} - -//ParseRepoIndex parses a repository index into an IndexFile -func ParseRepoIndex(body []byte) (*helmrepo.IndexFile, error) { - var index helmrepo.IndexFile - err := yaml.Unmarshal(body, &index) - if err != nil { - return nil, err - } - index.SortEntries() - return &index, nil -} - -//ChartsFromIndex creates an array of Charts from a Repository IndexFile -//Deprecated charts are skipped -func ChartsFromIndex(index *helmrepo.IndexFile, r Repo) []Chart { - var charts []Chart - for _, entry := range index.Entries { - if entry[0].GetDeprecated() { - log.WithFields(log.Fields{"name": entry[0].GetName()}).Info("skipping deprecated chart") - continue - } - charts = append(charts, NewChart(entry, r)) - } - return charts -} - -// NewChart Takes an entry from the index and constructs a database representation of the -// object. -func NewChart(entry helmrepo.ChartVersions, r Repo) Chart { - var c Chart - copier.Copy(&c, entry[0]) - copier.Copy(&c.ChartVersions, entry) - c.Repo = r - c.ID = fmt.Sprintf("%s/%s", r.Name, c.Name) - return c -} - -//ExtractFilesFromTarball extracts the specified files from a tarball into a map -func ExtractFilesFromTarball(filenames []string, tarf *tar.Reader) (map[string]string, error) { - ret := make(map[string]string) - for { - header, err := tarf.Next() - if err == io.EOF { - break - } - if err != nil { - return ret, err - } - - for _, f := range filenames { - if strings.EqualFold(header.Name, f) { - var b bytes.Buffer - io.Copy(&b, tarf) - ret[f] = string(b.Bytes()) - break - } - } - } - return ret, nil -} - -//ChartTarballURL returns the URL for a given chart version -func ChartTarballURL(r Repo, cv ChartVersion) string { - source := cv.URLs[0] - if _, err := ParseRepoURL(source); err != nil { - // If the chart URL is not absolute, join with repo URL. It's fine if the - // URL we build here is invalid as we can catch this error when actually - // making the request - u, _ := url.Parse(r.URL) - u.Path = path.Join(u.Path, source) - return u.String() - } - return source -} - -// InitNetClient configures an HTTP client for making requests to repositories -func InitNetClient(additionalCA string, timeoutSeconds time.Duration) (*http.Client, error) { - // Get the SystemCertPool, continue with an empty pool on error - caCertPool, _ := x509.SystemCertPool() - if caCertPool == nil { - caCertPool = x509.NewCertPool() - } - - // If additionalCA exists, load it - if _, err := os.Stat(additionalCA); !os.IsNotExist(err) { - certs, err := ioutil.ReadFile(additionalCA) - if err != nil { - return nil, fmt.Errorf("Failed to append %s to RootCAs: %v", additionalCA, err) - } - - // Append our cert to the system pool - if ok := caCertPool.AppendCertsFromPEM(certs); !ok { - return nil, fmt.Errorf("Failed to append %s to RootCAs", additionalCA) - } - } - - // Return Transport for testing purposes - return &http.Client{ - Timeout: time.Second * timeoutSeconds, - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - RootCAs: caCertPool, - }, - Proxy: http.ProxyFromEnvironment, - }, - }, nil -} - -//GetSha256 generates a SHA 256 hash for a given byte array -func GetSha256(src []byte) (string, error) { - f := bytes.NewReader(src) - h := sha256.New() - if _, err := io.Copy(h, f); err != nil { - return "", err - } - return fmt.Sprintf("%x", h.Sum(nil)), nil -} diff --git a/src/jetstream/plugins/monocular/chart-repo/common/types.go b/src/jetstream/plugins/monocular/chart-repo/common/types.go deleted file mode 100644 index a1fa3a4076..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/common/types.go +++ /dev/null @@ -1,162 +0,0 @@ -/* -Copyright (c) 2019 The Helm Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package common - -import ( - "net/http" - "sync" - "time" -) - -//Repo holds information to identify a repository -type Repo struct { - Name string - URL string - AuthorizationHeader string `bson:"-"` -} - -//Maintainer describes the maintainer of a Chart -type Maintainer struct { - Name string - Email string -} - -//Chart holds full descriptor of a Helm chart -type Chart struct { - ID string `bson:"_id"` - Name string - Repo Repo - Description string - Home string - Keywords []string - Maintainers []Maintainer - Sources []string - Icon string - ChartVersions []ChartVersion -} - -//ChartVersion holds version information on a Chart -type ChartVersion struct { - Version string - AppVersion string - Created time.Time - Digest string - URLs []string -} - -//ChartFiles describes the chart values, readme, schema and digest components of a chart -type ChartFiles struct { - ID string `bson:"_id"` - Readme string - Values string - Schema string - Repo Repo - Digest string -} - -//RepoCheck describes the state of a repository in terms its current checksum and last update time. -//It is used to determine whether or not to re-sync a respository. -type RepoCheck struct { - ID string `bson:"_id"` - LastUpdate time.Time `bson:"last_update"` - Checksum string `bson:"checksum"` -} - -//ImportChartFilesJob contains the information needed by an -//ImportWorker when import a chart from a repository -type ImportChartFilesJob struct { - Name string - Repo Repo - ChartVersion ChartVersion -} - -//HTTPClient defines a behaviour for making HTTP requests -type HTTPClient interface { - Do(req *http.Request) (*http.Response, error) -} - -type RepoSyncStatus struct { - Repo string `json:"repo"` - URL string `json:"url"` - Status string `json:"status"` -} - -type SyncStatusMap struct { - mut sync.Mutex - statusMap map[string]RepoSyncStatus -} - -func NewSyncStatusMap() *SyncStatusMap { - return &SyncStatusMap{sync.Mutex{}, make(map[string]RepoSyncStatus)} -} - -func (m *SyncStatusMap) Set(repo string, status RepoSyncStatus) { - m.mut.Lock() - defer m.mut.Unlock() - m.statusMap[repo] = status -} - -func (m *SyncStatusMap) Get(repo string) RepoSyncStatus { - m.mut.Lock() - defer m.mut.Unlock() - return m.statusMap[repo] -} - -const SyncStatusFailed = "Failed" -const SyncStatusSynced = "Synchronized" -const SyncStatusInProgress = "Synchronizing" - -type RepoDeleteStatus struct { - Repo string `json:"repo"` - URL string `json:"url"` - Status string `json:"status"` -} - -type DeleteStatusMap struct { - mut sync.Mutex - statusMap map[string]RepoDeleteStatus -} - -func NewDeleteStatusMap() *DeleteStatusMap { - return &DeleteStatusMap{sync.Mutex{}, make(map[string]RepoDeleteStatus)} -} - -func (m *DeleteStatusMap) Set(repo string, status RepoDeleteStatus) { - m.mut.Lock() - defer m.mut.Unlock() - m.statusMap[repo] = status -} - -func (m *DeleteStatusMap) Get(repo string) RepoDeleteStatus { - m.mut.Lock() - defer m.mut.Unlock() - return m.statusMap[repo] -} - -const DeleteStatusFailed = "Failed" -const DeleteStatusDeleted = "Deleted" -const DeleteStatusInProgress = "Deleting" - -type SyncJobStatusResponse struct { - UUID string `json:"uuid"` - Status string `json:"status"` -} - -type DeleteJobStatusResponse struct { - UUID string `json:"uuid"` - Status string `json:"status"` -} diff --git a/src/jetstream/plugins/monocular/chart-repo/delete.go b/src/jetstream/plugins/monocular/chart-repo/delete.go deleted file mode 100644 index 17b1e2dc43..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/delete.go +++ /dev/null @@ -1,39 +0,0 @@ -/* -Copyright (c) 2018 The Helm Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package main - -import ( - "github.com/helm/monocular/chartrepo/foundationdb" - - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -//DeleteCmd Delete a chart repository from Monocular -var DeleteCmd = &cobra.Command{ - Use: "delete [REPO NAME]", - Short: "delete a chart repository", - Run: func(cmd *cobra.Command, args []string) { - if len(args) != 1 { - log.Info("Need exactly one argument: [REPO NAME]") - cmd.Help() - return - } - - foundationdb.Delete(cmd, args) - }, -} diff --git a/src/jetstream/plugins/monocular/chart-repo/foundationdb/datastore.go b/src/jetstream/plugins/monocular/chart-repo/foundationdb/datastore.go deleted file mode 100644 index b5eaf598c0..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/foundationdb/datastore.go +++ /dev/null @@ -1,121 +0,0 @@ -/* -Copyright (c) 2019 The Helm Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package foundationdb - -import ( - "context" - "fmt" - "time" - - log "github.com/sirupsen/logrus" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -const defaultTimeout = 30 * time.Second - -// Config configures the database connection -type Config struct { - URL string - Database string - Timeout time.Duration -} - -// Client is an interface for a MongoDB client -type Client interface { - Database(name string) (Database, func()) -} - -// Database is an interface for accessing a MongoDB database -type Database interface { - Collection(name string) Collection -} - -// Collection is an interface for accessing a MongoDB collection -type Collection interface { - BulkWrite(ctxt context.Context, operations []mongo.WriteModel, options *options.BulkWriteOptions) (*mongo.BulkWriteResult, error) - DeleteMany(ctxt context.Context, filter interface{}, options *options.DeleteOptions) (*mongo.DeleteResult, error) - FindOne(ctxt context.Context, filter interface{}, result interface{}, options *options.FindOneOptions) error - InsertOne(ctxt context.Context, document interface{}, options *options.InsertOneOptions) (*mongo.InsertOneResult, error) - UpdateOne(ctxt context.Context, filter interface{}, update interface{}, options *options.UpdateOptions) (*mongo.UpdateResult, error) -} - -// mongoDatabase wraps an mongo.Database and implements Database -type mongoDatabase struct { - Database *mongo.Database -} - -// mongoClient wraps an mongo.Database and implements Database -type mongoClient struct { - Client *mongo.Client -} - -//NewDocLayerClient creates a mongoDB client using the given options -func NewDocLayerClient(ctx context.Context, options *options.ClientOptions) (Client, error) { - client, err := mongo.Connect(ctx, options) - return &mongoClient{client}, err -} - -//Database Creates a new interface for accessing the specified FDB Document-Layer database -func (c *mongoClient) Database(dbName string) (Database, func()) { - - db := &mongoDatabase{c.Client.Database(dbName)} - - return db, func() { - err := c.Client.Disconnect(context.Background()) - - if err != nil { - log.Fatal(err) - } - fmt.Println("Connection to MongoDB closed.") - } -} - -//Collection returns a reference to a given collection in the FDB Document-layer -func (d *mongoDatabase) Collection(name string) Collection { - return &mongoCollection{d.Database.Collection(name)} -} - -// mongoCollection wraps a mongo.Collection and implements Collection -type mongoCollection struct { - Collection *mongo.Collection -} - -func (c *mongoCollection) BulkWrite(ctxt context.Context, operations []mongo.WriteModel, options *options.BulkWriteOptions) (*mongo.BulkWriteResult, error) { - res, err := c.Collection.BulkWrite(ctxt, operations, options) - return res, err -} - -func (c *mongoCollection) DeleteMany(ctxt context.Context, filter interface{}, options *options.DeleteOptions) (*mongo.DeleteResult, error) { - res, err := c.Collection.DeleteMany(ctxt, filter, options) - return res, err -} - -func (c *mongoCollection) FindOne(ctxt context.Context, filter interface{}, result interface{}, options *options.FindOneOptions) error { - res := c.Collection.FindOne(ctxt, filter, options) - return res.Decode(result) -} - -func (c *mongoCollection) InsertOne(ctxt context.Context, document interface{}, options *options.InsertOneOptions) (*mongo.InsertOneResult, error) { - res, err := c.Collection.InsertOne(ctxt, document, options) - return res, err -} - -func (c *mongoCollection) UpdateOne(ctxt context.Context, filter interface{}, document interface{}, options *options.UpdateOptions) (*mongo.UpdateResult, error) { - res, err := c.Collection.UpdateOne(ctxt, filter, document, options) - return res, err -} diff --git a/src/jetstream/plugins/monocular/chart-repo/foundationdb/delete.go b/src/jetstream/plugins/monocular/chart-repo/foundationdb/delete.go deleted file mode 100644 index d5f29445ab..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/foundationdb/delete.go +++ /dev/null @@ -1,66 +0,0 @@ -/* -Copyright (c) 2019 The Helm Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package foundationdb - -import ( - "context" - "time" - - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "go.mongodb.org/mongo-driver/mongo/options" -) - -//Delete Deletes a chart repository from FoundationDB Document-Layer -func Delete(cmd *cobra.Command, args []string) { - - fdbURL, err := cmd.Flags().GetString("doclayer-url") - if err != nil { - log.Fatal(err) - } - fDB, err := cmd.Flags().GetString("doclayer-database") - if err != nil { - log.Fatal(err) - } - debug, err := cmd.Flags().GetBool("debug") - if err != nil { - log.Fatal(err) - } - if debug { - log.SetLevel(log.DebugLevel) - } - - log.Debugf("Creating client for FDB: %v, %v, %v", fdbURL, fDB, debug) - clientOptions := options.Client().ApplyURI(fdbURL).SetMinPoolSize(10).SetMaxPoolSize(100) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - client, err := NewDocLayerClient(ctx, clientOptions) - if err != nil { - log.Fatalf("Can't create client for FoundationDB document layer: %v URL provided was: %v", err, fdbURL) - return - } - - log.Debugf("Client created.") - - //Close db client after serving request - var clientKeepAlive = false - if err = DeleteRepo(client, fDB, args[0], clientKeepAlive); err != nil { - log.Fatalf("Can't delete chart repository %s from database: %v", args[0], err, clientKeepAlive) - } - - log.Infof("Successfully deleted the chart repository %s from database", args[0]) -} diff --git a/src/jetstream/plugins/monocular/chart-repo/foundationdb/mockstore.go b/src/jetstream/plugins/monocular/chart-repo/foundationdb/mockstore.go deleted file mode 100644 index eaac71db68..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/foundationdb/mockstore.go +++ /dev/null @@ -1,82 +0,0 @@ -/* -Copyright (c) 2019 The Helm Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package foundationdb - -import ( - "context" - - "github.com/stretchr/testify/mock" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -//mockDatabase acts as a mock datastore.Database -type mockDatabase struct { - *mock.Mock -} - -type mockClient struct { - *mock.Mock -} - -//NewMockClient returns a mocked Document-Layer client -func NewMockClient(m *mock.Mock) Client { - return mockClient{m} -} - -//Database returns a mocked datastore.Database and empty closer function -func (c mockClient) Database(dbName string) (Database, func()) { - - db := mockDatabase{c.Mock} - - return db, func() { - } -} - -func (d mockDatabase) Collection(name string) Collection { - return mockCollection{d.Mock} -} - -// mockCollection acts as a mock datastore.Collection -type mockCollection struct { - *mock.Mock -} - -func (c mockCollection) BulkWrite(ctxt context.Context, operations []mongo.WriteModel, options *options.BulkWriteOptions) (*mongo.BulkWriteResult, error) { - args := c.Called(ctxt, operations, options) - return args.Get(0).(*mongo.BulkWriteResult), args.Error(1) -} - -func (c mockCollection) DeleteMany(ctxt context.Context, filter interface{}, options *options.DeleteOptions) (*mongo.DeleteResult, error) { - args := c.Called(ctxt, filter, options) - return args.Get(0).(*mongo.DeleteResult), args.Error(1) -} - -func (c mockCollection) FindOne(ctxt context.Context, filter interface{}, result interface{}, options *options.FindOneOptions) error { - args := c.Called(ctxt, filter, result, options) - return args.Error(0) -} - -func (c mockCollection) InsertOne(ctxt context.Context, document interface{}, options *options.InsertOneOptions) (*mongo.InsertOneResult, error) { - args := c.Called(ctxt, document, options) - return args.Get(0).(*mongo.InsertOneResult), args.Error(1) -} - -func (c mockCollection) UpdateOne(ctxt context.Context, filter interface{}, document interface{}, options *options.UpdateOptions) (*mongo.UpdateResult, error) { - args := c.Called(ctxt, filter, document, options) - return args.Get(0).(*mongo.UpdateResult), args.Error(1) -} diff --git a/src/jetstream/plugins/monocular/chart-repo/foundationdb/sync.go b/src/jetstream/plugins/monocular/chart-repo/foundationdb/sync.go deleted file mode 100644 index 6db32d6b64..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/foundationdb/sync.go +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright (c) 2018 The Helm Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package foundationdb - -import ( - "context" - "os" - "time" - - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "go.mongodb.org/mongo-driver/mongo/options" -) - -//Sync Add a new chart repository to FoundationDB Document-Layer and periodically sync it -func Sync(cmd *cobra.Command, args []string) { - - fdbURL, err := cmd.Flags().GetString("doclayer-url") - if err != nil { - log.Fatal(err) - } - fDB, err := cmd.Flags().GetString("doclayer-database") - if err != nil { - log.Fatal(err) - } - debug, err := cmd.Flags().GetBool("debug") - if err != nil { - log.Fatal(err) - } - if debug { - log.SetLevel(log.DebugLevel) - } - - log.Debugf("Creating client for FDB: %v, %v, %v", fdbURL, fDB, debug) - clientOptions := options.Client().ApplyURI(fdbURL).SetMinPoolSize(10).SetMaxPoolSize(100) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - client, err := NewDocLayerClient(ctx, clientOptions) - if err != nil { - log.Fatalf("Can't create client for FoundationDB document layer: %v. URL provided was: %v", err, fdbURL) - return - } - - log.Debugf("Client created.") - - startTime := time.Now() - //Close db client after serving request - var clientKeepAlive = false - authorizationHeader := os.Getenv("AUTHORIZATION_HEADER") - if err = SyncRepo(client, fDB, args[0], args[1], authorizationHeader, clientKeepAlive); err != nil { - log.Fatalf("Can't add chart repository to database: %v", err) - return - } - timeTaken := time.Since(startTime).Seconds() - log.Infof("Successfully added the chart repository %s to database in %v seconds", args[0], timeTaken) -} diff --git a/src/jetstream/plugins/monocular/chart-repo/foundationdb/utils.go b/src/jetstream/plugins/monocular/chart-repo/foundationdb/utils.go deleted file mode 100644 index 926ed920ad..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/foundationdb/utils.go +++ /dev/null @@ -1,531 +0,0 @@ -/* -Copyright (c) 2019 The Helm Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package foundationdb - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "context" - "errors" - "fmt" - "io/ioutil" - "net/http" - "strings" - "sync" - "time" - - "github.com/helm/monocular/chartrepo/common" - "github.com/helm/monocular/chartrepo/utils" - - "github.com/disintegration/imaging" - log "github.com/sirupsen/logrus" - - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -const ( - chartCollection = "charts" - repositoryCollection = "repos" - chartFilesCollection = "files" - defaultTimeoutSeconds = 10 - additionalCAFile = "/usr/local/share/ca-certificates/ca.crt" -) - -var netClient common.HTTPClient = &http.Client{} -var repoSyncStatus *common.SyncStatusMap = common.NewSyncStatusMap() -var repoDeleteStatus *common.DeleteStatusMap = common.NewDeleteStatusMap() - -func init() { - var err error - netClient, err = common.InitNetClient(additionalCAFile, defaultTimeoutSeconds) - if err != nil { - log.Fatal(err) - } -} - -func createStatus(repoName, repoUrl, status string) common.RepoSyncStatus { - syncStatus := common.RepoSyncStatus{} - syncStatus.Repo = repoName - syncStatus.URL = repoUrl - syncStatus.Status = status - return syncStatus -} - -// SyncRepo Syncing is performed in the following steps: -// 1. Update database to match chart metadata from index -// 2. Concurrently process icons for charts (concurrently) -// 3. Concurrently process the README and values.yaml for the latest chart version of each chart -// 4. Concurrently process READMEs and values.yaml for historic chart versions -// -// These steps are processed in this way to ensure relevant chart data is -// imported into the database as fast as possible. E.g. we want all icons for -// charts before fetching readmes for each chart and version pair. -func SyncRepo(dbClient Client, dbName, repoName, repoURL string, authorizationHeader string, clientKeepAlive bool) error { - - repoSyncStatus.Set(repoName, createStatus(repoName, repoURL, common.SyncStatusInProgress)) - - db, closer := dbClient.Database(dbName) - - log.Infof("Keeping client alive: %v", clientKeepAlive) - if !clientKeepAlive { - defer closer() - } - - url, err := common.ParseRepoURL(repoURL) - if err != nil { - log.WithFields(log.Fields{"url": repoURL}).WithError(err).Error("Failed to parse Repo URL") - repoSyncStatus.Set(repoName, common.RepoSyncStatus{"repoName", repoURL, common.SyncStatusFailed}) - return err - } - - log.Debugf("Checking database connection and readiness...") - collection := db.Collection("numbers") - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - res, err := collection.InsertOne(ctx, bson.M{"name": "pi", "value": 3.14159}, options.InsertOne()) - if err != nil { - log.Errorf("Database readiness/connection test failed: %v", err) - cancel() - repoSyncStatus.Set(repoName, createStatus(repoName, repoURL, common.SyncStatusFailed)) - return err - } - id := res.InsertedID - collection.DeleteMany(ctx, bson.M{"_id": id}, options.Delete()) - cancel() - log.Debugf("Database connection test successful.") - log.Debugf("Inserted a test document to test collection with ID: %v", id) - - r := common.Repo{Name: repoName, URL: url.String(), AuthorizationHeader: authorizationHeader} - repoBytes, err := common.FetchRepoIndex(r, netClient) - if err != nil { - log.Errorf("Failed to fetch repo index: %v", err) - - repoSyncStatus.Set(repoName, createStatus(repoName, repoURL, common.SyncStatusFailed)) - return err - } - - repoChecksum, err := common.GetSha256(repoBytes) - if err != nil { - log.Errorf("Failed to generate repo checksum: %v", err) - repoSyncStatus.Set(repoName, createStatus(repoName, repoURL, common.SyncStatusFailed)) - return err - } - - // Check if the repo has been already processed - if repoAlreadyProcessed(db, repoName, repoChecksum) { - log.WithFields(log.Fields{"url": repoURL}).Info("Skipping repository since there are no updates") - repoSyncStatus.Set(repoName, createStatus(repoName, repoURL, common.SyncStatusSynced)) - return nil - } - - index, err := common.ParseRepoIndex(repoBytes) - if err != nil { - log.Errorf("Error parsing repo index: %v", err) - repoSyncStatus.Set(repoName, createStatus(repoName, repoURL, common.SyncStatusFailed)) - return err - } - - charts := common.ChartsFromIndex(index, r) - log.Debugf("%v Charts in index of repo: %v", len(charts), repoURL) - if len(charts) == 0 { - repoSyncStatus.Set(repoName, createStatus(repoName, repoURL, common.SyncStatusSynced)) - return errors.New("no charts in repository index") - } - - err = importCharts(db, dbName, charts) - if err != nil { - log.Errorf("Error importing charts: %v", err) - repoSyncStatus.Set(repoName, createStatus(repoName, repoURL, common.SyncStatusFailed)) - return err - } - - // Process 10 charts at a time - numWorkers := 10 - iconJobs := make(chan common.Chart, numWorkers) - chartFilesJobs := make(chan common.ImportChartFilesJob, numWorkers) - var wg sync.WaitGroup - - log.Debugf("starting %d workers", numWorkers) - for i := 0; i < numWorkers; i++ { - wg.Add(1) - go importWorker(db, &wg, iconJobs, chartFilesJobs) - } - - // Enqueue jobs to process chart icons - for _, c := range charts { - iconJobs <- c - } - // Close the iconJobs channel to signal the worker pools to move on to the - // chart files jobs - close(iconJobs) - - // Iterate through the list of charts and enqueue the latest chart version to - // be processed. Append the rest of the chart versions to a list to be - // enqueued later - var toEnqueue []common.ImportChartFilesJob - for _, c := range charts { - chartFilesJobs <- common.ImportChartFilesJob{Name: c.Name, Repo: c.Repo, ChartVersion: c.ChartVersions[0]} - for _, cv := range c.ChartVersions[1:] { - toEnqueue = append(toEnqueue, common.ImportChartFilesJob{Name: c.Name, Repo: c.Repo, ChartVersion: cv}) - } - } - - // Enqueue all the remaining chart versions - for _, cfj := range toEnqueue { - chartFilesJobs <- cfj - } - // Close the chartFilesJobs channel to signal the worker pools that there are - // no more jobs to process - close(chartFilesJobs) - - // Wait for the worker pools to finish processing - wg.Wait() - - // Update cache in the database - if err = updateLastCheck(db, repoName, repoChecksum, time.Now()); err != nil { - log.Errorf("Error updating last repo check timestamp: %v", err) - repoSyncStatus.Set(repoName, common.RepoSyncStatus{"repoName", repoURL, common.SyncStatusFailed}) - return err - } - log.WithFields(log.Fields{"Repository": repoName, "URL": repoURL}).Info("Synchronized successfully: ") - repoSyncStatus.Set(repoName, common.RepoSyncStatus{"repoName", repoURL, common.SyncStatusSynced}) - return nil -} - -func repoAlreadyProcessed(db Database, repoName string, checksum string) bool { - lastCheck := &common.RepoCheck{} - filter := bson.M{"_id": repoName} - err := db.Collection(repositoryCollection).FindOne(context.Background(), filter, lastCheck, options.FindOne()) - return err == nil && checksum == lastCheck.Checksum -} - -func updateLastCheck(db Database, repoName string, checksum string, now time.Time) error { - selector := bson.M{"_id": repoName} - update := bson.M{"$set": bson.M{"last_update": now, "checksum": checksum}} - _, err := db.Collection(repositoryCollection).UpdateOne(context.Background(), selector, update, options.Update()) - return err -} - -func DeleteRepo(dbClient Client, dbName, repoName string, clientKeepAlive bool) error { - db, closer := dbClient.Database(dbName) - - log.Infof("Keeping client alive: %v", clientKeepAlive) - if !clientKeepAlive { - defer closer() - } - - log.Debugf("Checking database connection and readiness...") - collection := db.Collection("numbers") - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - res, err := collection.InsertOne(ctx, bson.M{"name": "pi", "value": 3.14159}, options.InsertOne()) - if err != nil { - log.Fatalf("Database readiness/connection test failed: %v", err) - cancel() - return err - } - id := res.InsertedID - collection.DeleteMany(ctx, bson.M{"_id": id}, options.Delete()) - cancel() - log.Debugf("Database connection test successful.") - log.Debugf("Inserted a test document to test collection with ID: %v", id) - - collection = db.Collection(chartCollection) - filter := bson.M{ - "repo.name": repoName, - } - deleteResult, err := collection.DeleteMany(context.Background(), filter, options.Delete()) - if err != nil { - log.Debugf("Error occurred during delete repo (deleting charts from index). Err: %v, Result: %v", err, deleteResult) - return err - } - log.Debugf("Repo delete (delete charts from index) result: %v charts deleted", deleteResult.DeletedCount) - - collection = db.Collection(chartFilesCollection) - deleteResult, err = collection.DeleteMany(context.Background(), filter, options.Delete()) - if err != nil { - log.Debugf("Error occurred during delete repo (deleting chart files from index). Err: %v, Result: %v", err, deleteResult) - return err - } - log.Debugf("Repo delete (delete chart files from index) result: %v chart files deleted.", deleteResult.DeletedCount) - collection = db.Collection(repositoryCollection) - deleteResult, err = collection.DeleteMany(context.Background(), filter, options.Delete()) - if err != nil { - log.Debugf("Error occurred during delete repo (deleting repositories from index). Err: %v, Result: %v", err, deleteResult) - return err - } - log.Debugf("Repo delete (delete chart files from index) result: %v repositories deleted.", deleteResult.DeletedCount) - - return err -} - -func importCharts(db Database, dbName string, charts []common.Chart) error { - var operations []mongo.WriteModel - var chartIDs []string - for _, c := range charts { - operation := mongo.NewUpdateOneModel() - chartIDs = append(chartIDs, c.ID) - // charts to upsert - pair of filter, chart - operation.SetFilter(bson.M{ - "_id": c.ID, - }) - - chartBSON, err := bson.Marshal(&c) - var doc bson.M - bson.Unmarshal(chartBSON, &doc) - delete(doc, "_id") - - if err != nil { - log.Debugf("Error marshalling chart to BSON: %v. Skipping this chart.", err) - } else { - update := doc - operation.SetUpdate(update) - operation.SetUpsert(true) - operations = append(operations, operation) - } - log.Debugf("Adding chart insert operation for chart: %v", c.ID) - } - - //Must use bulk write for array of filters - collection := db.Collection(chartCollection) - updateResult, err := collection.BulkWrite( - context.Background(), - operations, - options.BulkWrite(), - ) - - //Set upsert flag and upsert the pairs here - //Updates our index for charts that we already have and inserts charts that are new - if err != nil { - log.Debugf("Error occurred during chart import (upsert many). Err: %v", err) - return err - } - log.Debugf("Upsert chart index success. %v documents inserted, %v documents upserted, %v documents modified", updateResult.InsertedCount, updateResult.UpsertedCount, updateResult.ModifiedCount) - - //Remove from our index, any charts that no longer exist - filter := bson.M{ - "_id": bson.M{ - "$nin": chartIDs, - }, - "repo.name": charts[0].Repo.Name, - } - deleteResult, err := collection.DeleteMany(context.Background(), filter, options.Delete()) - if err != nil { - log.Debugf("Error occurred during chart import (delete many). Err: %v", err) - return err - } - log.Debugf("Delete stale charts from index success. %v documents deleted.", deleteResult.DeletedCount) - - return err -} - -func importWorker(db Database, wg *sync.WaitGroup, icons <-chan common.Chart, chartFiles <-chan common.ImportChartFilesJob) { - defer wg.Done() - for c := range icons { - log.WithFields(log.Fields{"name": c.Name}).Debug("importing icon") - if err := fetchAndImportIcon(db, c); err != nil { - log.WithFields(log.Fields{"name": c.Name}).WithError(err).Warn("failed to import icon") - } - } - for j := range chartFiles { - log.WithFields(log.Fields{"name": j.Name, "version": j.ChartVersion.Version}).Debug("importing readme and values") - if err := fetchAndImportFiles(db, j.Name, j.Repo, j.ChartVersion); err != nil { - log.WithFields(log.Fields{"name": j.Name, "version": j.ChartVersion.Version}).WithError(err).Warn("failed to import files") - } - } -} - -func fetchAndImportIcon(db Database, c common.Chart) error { - if c.Icon == "" { - log.WithFields(log.Fields{"name": c.Name}).Debug("icon not found") - return nil - } - - req, err := http.NewRequest("GET", c.Icon, nil) - if err != nil { - return err - } - req.Header.Set("User-Agent", utils.UserAgent()) - if len(c.Repo.AuthorizationHeader) > 0 { - req.Header.Set("Authorization", c.Repo.AuthorizationHeader) - } - - res, err := netClient.Do(req) - if res != nil { - defer res.Body.Close() - } - if err != nil { - return err - } - - if res.StatusCode != http.StatusOK { - return fmt.Errorf("%d %s", res.StatusCode, c.Icon) - } - - b := []byte{} - contentType := "" - if strings.Contains(res.Header.Get("Content-Type"), "image/svg") { - // if the icon is a SVG file simply read it - b, err = ioutil.ReadAll(res.Body) - if err != nil { - return err - } - contentType = res.Header.Get("Content-Type") - } else { - // if the icon is in any other format try to convert it to PNG - orig, err := imaging.Decode(res.Body) - if err != nil { - log.WithFields(log.Fields{"name": c.Name}).WithError(err).Error("failed to decode icon") - return err - } - - // TODO: make this configurable? - icon := imaging.Fit(orig, 160, 160, imaging.Lanczos) - - var buf bytes.Buffer - imaging.Encode(&buf, icon, imaging.PNG) - b = buf.Bytes() - contentType = "image/png" - } - - collection := db.Collection(chartCollection) - //Update single icon - update := bson.M{"$set": bson.M{"raw_icon": b, "icon_content_type": contentType}} - filter := bson.M{"_id": c.ID} - updateResult, err := collection.UpdateOne(context.Background(), filter, update, options.Update()) - if err != nil { - log.Debugf("Error occurred during chart icon import (update one). Err: %v, Result: %v", err, updateResult) - return err - } - return err -} - -func fetchAndImportFiles(db Database, name string, r common.Repo, cv common.ChartVersion) error { - - chartFilesID := fmt.Sprintf("%s/%s-%s", r.Name, name, cv.Version) - //Check if we already have indexed files for this chart version and digest - collection := db.Collection(chartFilesCollection) - filter := bson.M{"_id": chartFilesID, "digest": cv.Digest} - findResult := collection.FindOne(context.Background(), filter, &common.ChartFiles{}, options.FindOne()) - if findResult != mongo.ErrNoDocuments { - log.WithFields(log.Fields{"name": name, "version": cv.Version}).Debug("skipping existing files") - return nil - } - log.WithFields(log.Fields{"name": name, "version": cv.Version}).Debug("fetching files") - - url := common.ChartTarballURL(r, cv) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return err - } - req.Header.Set("User-Agent", utils.UserAgent()) - if len(r.AuthorizationHeader) > 0 { - req.Header.Set("Authorization", r.AuthorizationHeader) - } - - res, err := netClient.Do(req) - if err != nil { - return err - } - defer res.Body.Close() - - // We read the whole chart into memory, this should be okay since the chart - // tarball needs to be small enough to fit into a GRPC call (Tiller - // requirement) - gzf, err := gzip.NewReader(res.Body) - if err != nil { - return err - } - defer gzf.Close() - - tarf := tar.NewReader(gzf) - - readmeFileName := name + "/README.md" - valuesFileName := name + "/values.yaml" - schemaFileName := name + "/values.schema.json" - filenames := []string{valuesFileName, readmeFileName, schemaFileName} - - files, err := common.ExtractFilesFromTarball(filenames, tarf) - if err != nil { - return err - } - - chartFiles := common.ChartFiles{ID: chartFilesID, Repo: r, Digest: cv.Digest} - if v, ok := files[readmeFileName]; ok { - chartFiles.Readme = v - } else { - log.WithFields(log.Fields{"name": name, "version": cv.Version}).Warn("README.md not found") - } - if v, ok := files[valuesFileName]; ok { - chartFiles.Values = v - } else { - log.WithFields(log.Fields{"name": name, "version": cv.Version}).Warn("values.yaml not found") - } - if v, ok := files[schemaFileName]; ok { - chartFiles.Schema = v - } else { - log.WithFields(log.Fields{"name": name, "version": cv.Version}).Warn("values.schema.json not found") - } - - // inserts the chart files if not already indexed, or updates the existing - // entry if digest has changed - log.Debugf("Inserting chart files %v to collection: %v....", chartFilesID, chartFilesCollection) - collection = db.Collection(chartFilesCollection) - filter = bson.M{"_id": chartFilesID} - chartBSON, err := bson.Marshal(&chartFiles) - var doc bson.M - bson.Unmarshal(chartBSON, &doc) - delete(doc, "_id") - update := bson.M{"$set": doc} - updateResult, err := collection.UpdateOne(context.Background(), filter, update, options.Update().SetUpsert(true)) - if err != nil { - log.Debugf("Error occurred during chart files import (update one). Chart files : %v doc: %v Err: %v", chartFiles, doc, err) - return err - } - log.Debugf("Chart files import success. (update one) Upserted: %v Updated: %v", updateResult.UpsertedCount, updateResult.ModifiedCount) - return nil -} - -func GetRepoSyncStatus(repo string) (common.RepoSyncStatus, error) { - status := repoSyncStatus.Get(repo) - if status == (common.RepoSyncStatus{}) { - return status, errors.New("Repo does not exist in database.") - } - return status, nil -} - -func GetRepoDeleteStatus(repo string) (common.RepoDeleteStatus, error) { - status := repoDeleteStatus.Get(repo) - if status == (common.RepoDeleteStatus{}) { - return status, errors.New("Repo does not exist in database.") - } - return status, nil -} - -func database(client *mongo.Client, dbName string) (*mongo.Database, func()) { - - db := client.Database(dbName) - return db, func() { - err := client.Disconnect(context.Background()) - - if err != nil { - log.Fatal(err) - } - fmt.Println("Connection to MongoDB closed.") - } -} diff --git a/src/jetstream/plugins/monocular/chart-repo/foundationdb/utils_test.go b/src/jetstream/plugins/monocular/chart-repo/foundationdb/utils_test.go deleted file mode 100644 index e435012458..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/foundationdb/utils_test.go +++ /dev/null @@ -1,696 +0,0 @@ -/* -Copyright (c) 2018 The Helm Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package foundationdb - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "crypto/rand" - "fmt" - "image" - "image/color" - "io" - "io/ioutil" - "net/http" - "net/http/httptest" - "os" - "path" - "strings" - "testing" - "time" - - "github.com/helm/monocular/chartrepo/common" - "github.com/helm/monocular/chartrepo/utils" - - "github.com/arschles/assert" - "github.com/disintegration/imaging" - log "github.com/sirupsen/logrus" - "github.com/stretchr/testify/mock" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var validRepoIndexYAMLBytes, _ = ioutil.ReadFile("../testdata/valid-index.yaml") -var validRepoIndexYAML = string(validRepoIndexYAMLBytes) - -var invalidRepoIndexYAML = "invalid" - -type badHTTPClient struct{} - -func (h *badHTTPClient) Do(req *http.Request) (*http.Response, error) { - w := httptest.NewRecorder() - w.WriteHeader(500) - return w.Result(), nil -} - -type goodHTTPClient struct{} - -func (h *goodHTTPClient) Do(req *http.Request) (*http.Response, error) { - w := httptest.NewRecorder() - // Don't accept trailing slashes - if strings.HasPrefix(req.URL.Path, "//") { - w.WriteHeader(500) - } - // If subpath repo URL test, check that index.yaml is correctly added to the - // subpath - if req.URL.Host == "subpath.test" && req.URL.Path != "/subpath/index.yaml" { - w.WriteHeader(500) - } - - w.Write([]byte(validRepoIndexYAML)) - return w.Result(), nil -} - -type badIconClient struct{} - -func (h *badIconClient) Do(req *http.Request) (*http.Response, error) { - w := httptest.NewRecorder() - w.Write([]byte("not-an-image")) - return w.Result(), nil -} - -type goodIconClient struct{} - -func iconBytes() []byte { - var b bytes.Buffer - img := imaging.New(1, 1, color.White) - imaging.Encode(&b, img, imaging.PNG) - return b.Bytes() -} - -func (h *goodIconClient) Do(req *http.Request) (*http.Response, error) { - w := httptest.NewRecorder() - w.Write(iconBytes()) - return w.Result(), nil -} - -type svgIconClient struct{} - -func (h *svgIconClient) Do(req *http.Request) (*http.Response, error) { - w := httptest.NewRecorder() - w.Write([]byte("foo")) - res := w.Result() - res.Header.Set("Content-Type", "image/svg") - return res, nil -} - -type goodTarballClient struct { - c common.Chart - skipReadme bool - skipValues bool - skipSchema bool -} - -var testChartReadme = "# readme for chart\n\nBest chart in town" -var testChartValues = "image: test" -var testChartSchema = `{"properties": {}}` - -func (h *goodTarballClient) Do(req *http.Request) (*http.Response, error) { - w := httptest.NewRecorder() - gzw := gzip.NewWriter(w) - files := []tarballFile{{h.c.Name + "/Chart.yaml", "should be a Chart.yaml here..."}} - if !h.skipValues { - files = append(files, tarballFile{h.c.Name + "/values.yaml", testChartValues}) - } - if !h.skipReadme { - files = append(files, tarballFile{h.c.Name + "/README.md", testChartReadme}) - } - if !h.skipSchema { - files = append(files, tarballFile{h.c.Name + "/values.schema.json", testChartSchema}) - } - createTestTarball(gzw, files) - gzw.Flush() - return w.Result(), nil -} - -type authenticatedTarballClient struct { - c common.Chart -} - -func (h *authenticatedTarballClient) Do(req *http.Request) (*http.Response, error) { - w := httptest.NewRecorder() - - // Ensure we're sending the right Authorization header - if !strings.Contains(req.Header.Get("Authorization"), "Bearer ThisSecretAccessTokenAuthenticatesTheClient") { - w.WriteHeader(500) - } else { - gzw := gzip.NewWriter(w) - files := []tarballFile{{h.c.Name + "/Chart.yaml", "should be a Chart.yaml here..."}} - files = append(files, tarballFile{h.c.Name + "/values.yaml", testChartValues}) - files = append(files, tarballFile{h.c.Name + "/README.md", testChartReadme}) - files = append(files, tarballFile{h.c.Name + "/values.schema.json", testChartSchema}) - createTestTarball(gzw, files) - gzw.Flush() - } - return w.Result(), nil -} - -func Test_syncURLInvalidity(t *testing.T) { - tests := []struct { - name string - repoURL string - }{ - {"invalid URL", "not-a-url"}, - {"invalid URL", "https//google.com"}, - } - m := mock.Mock{} - dbClient := NewMockClient(&m) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := SyncRepo(dbClient, "test", "test", tt.repoURL, "") - assert.ExistsErr(t, err, tt.name) - }) - } -} - -func Test_fetchRepoIndex(t *testing.T) { - tests := []struct { - name string - r common.Repo - }{ - {"valid HTTP URL", common.Repo{URL: "http://my.examplerepo.com"}}, - {"valid HTTPS URL", common.Repo{URL: "https://my.examplerepo.com"}}, - {"valid trailing URL", common.Repo{URL: "https://my.examplerepo.com/"}}, - {"valid subpath URL", common.Repo{URL: "https://subpath.test/subpath/"}}, - {"valid URL with trailing spaces", common.Repo{URL: "https://subpath.test/subpath/ "}}, - {"valid URL with leading spaces", common.Repo{URL: " https://subpath.test/subpath/"}}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - netClient = &goodHTTPClient{} - _, err := common.FetchRepoIndex(tt.r, netClient) - assert.NoErr(t, err) - }) - } - - t.Run("failed request", func(t *testing.T) { - netClient = &badHTTPClient{} - _, err := common.FetchRepoIndex(common.Repo{URL: "https://my.examplerepo.com"}, netClient) - assert.ExistsErr(t, err, "failed request") - }) -} - -func Test_fetchRepoIndexUserAgent(t *testing.T) { - tests := []struct { - name string - version string - userAgentComment string - expectedUserAgent string - }{ - {"default user agent", "", "", "chart-repo/devel"}, - {"custom version no app", "1.0", "", "chart-repo/1.0"}, - {"custom version and app", "1.0", "monocular/1.2", "chart-repo/1.0 (monocular/1.2)"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Override global variables used to generate the userAgent - if tt.version != "" { - utils.Version = tt.version - } - - if tt.userAgentComment != "" { - utils.UserAgentComment = tt.userAgentComment - } - - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - assert.Equal(t, tt.expectedUserAgent, req.Header.Get("User-Agent"), "expected user agent") - rw.Write([]byte(validRepoIndexYAML)) - })) - // Close the server when test finishes - defer server.Close() - - netClient = server.Client() - - _, err := common.FetchRepoIndex(common.Repo{URL: server.URL}, netClient) - assert.NoErr(t, err) - }) - } -} - -func Test_parseRepoIndex(t *testing.T) { - tests := []struct { - name string - repoYAML string - }{ - {"invalid", "invalid"}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - _, err := common.ParseRepoIndex([]byte(tt.repoYAML)) - assert.ExistsErr(t, err, tt.name) - }) - } - - t.Run("valid", func(t *testing.T) { - index, err := common.ParseRepoIndex([]byte(validRepoIndexYAML)) - assert.NoErr(t, err) - assert.Equal(t, len(index.Entries), 2, "number of charts") - assert.Equal(t, index.Entries["acs-engine-autoscaler"][0].GetName(), "acs-engine-autoscaler", "chart version populated") - }) -} - -func Test_chartsFromIndex(t *testing.T) { - r := common.Repo{Name: "test", URL: "http://testrepo.com"} - index, _ := common.ParseRepoIndex([]byte(validRepoIndexYAML)) - charts := common.ChartsFromIndex(index, r) - assert.Equal(t, len(charts), 2, "number of charts") - - indexWithDeprecated := validRepoIndexYAML + ` - deprecated-chart: - - name: deprecated-chart - deprecated: true` - index2, err := common.ParseRepoIndex([]byte(indexWithDeprecated)) - assert.NoErr(t, err) - charts = common.ChartsFromIndex(index2, r) - assert.Equal(t, len(charts), 2, "number of charts") -} - -func Test_newChart(t *testing.T) { - r := common.Repo{Name: "test", URL: "http://testrepo.com"} - index, _ := common.ParseRepoIndex([]byte(validRepoIndexYAML)) - c := common.NewChart(index.Entries["wordpress"], r) - assert.Equal(t, c.Name, "wordpress", "correctly built") - assert.Equal(t, len(c.ChartVersions), 2, "correctly built") - assert.Equal(t, c.Description, "new description!", "takes chart fields from latest entry") - assert.Equal(t, c.Repo, r, "repo set") - assert.Equal(t, c.ID, "test/wordpress", "id set") -} - -func Test_importCharts(t *testing.T) { - m := &mock.Mock{} - // Ensure Upsert func is called with some arguments - m.On("BulkWrite", mock.Anything, mock.Anything, mock.Anything).Return(&mongo.BulkWriteResult{}, nil) - m.On("DeleteMany", mock.Anything, mock.Anything, mock.Anything).Return(&mongo.DeleteResult{}, nil) - dbClient := NewMockClient(m) - db, _ := dbClient.Database("test") - index, _ := common.ParseRepoIndex([]byte(validRepoIndexYAML)) - charts := common.ChartsFromIndex(index, common.Repo{Name: "test", URL: "http://testrepo.com"}) - importCharts(db, "test", charts) - - m.AssertExpectations(t) - // The BulkWrite method takes an array of WriteModels. - // For x charts to upsert, there should be x elements. - // Each element has a selector (filter) and an "update" - the update document to apply - args := m.Calls[0].Arguments.Get(1).([]mongo.WriteModel) - assert.Equal(t, len(args), len(charts), "number of charts to upsert") - for i := 0; i < len(args); i++ { - updateModel := args[i].(*mongo.UpdateOneModel) - assert.Equal(t, updateModel.Filter, bson.M{"_id": "test/" + updateModel.Update.(bson.M)["name"].(string)}, "selector") - } -} - -func Test_DeleteRepo(t *testing.T) { - m := &mock.Mock{} - //Expect a few calls to test the DB readiness - m.On("InsertOne", mock.Anything, mock.Anything, mock.Anything).Return(&mongo.InsertOneResult{}, nil) - m.On("DeleteMany", mock.Anything, mock.Anything, mock.Anything).Return(&mongo.DeleteResult{}, nil) - - m.On("DeleteMany", mock.Anything, bson.M{ - "repo.name": "test", - }, mock.Anything).Return(&mongo.DeleteResult{}, nil) - dbClient := NewMockClient(m) - - err := DeleteRepo(dbClient, "test", "test") - if err != nil { - t.Errorf("failed to delete chart repo test: %v", err) - } - m.AssertExpectations(t) -} - -func Test_fetchAndImportIcon(t *testing.T) { - t.Run("no icon", func(t *testing.T) { - m := mock.Mock{} - dbClient := NewMockClient(&m) - db, _ := dbClient.Database("test") - c := common.Chart{ID: "test/acs-engine-autoscaler"} - assert.NoErr(t, fetchAndImportIcon(db, c)) - }) - - index, _ := common.ParseRepoIndex([]byte(validRepoIndexYAML)) - charts := common.ChartsFromIndex(index, common.Repo{Name: "test", URL: "http://testrepo.com"}) - - t.Run("failed download", func(t *testing.T) { - netClient = &badHTTPClient{} - c := charts[0] - m := mock.Mock{} - dbClient := NewMockClient(&m) - db, _ := dbClient.Database("test") - assert.Err(t, fmt.Errorf("500 %s", c.Icon), fetchAndImportIcon(db, c)) - }) - - t.Run("bad icon", func(t *testing.T) { - netClient = &badIconClient{} - c := charts[0] - m := mock.Mock{} - dbClient := NewMockClient(&m) - db, _ := dbClient.Database("test") - assert.Err(t, image.ErrFormat, fetchAndImportIcon(db, c)) - }) - - t.Run("valid icon", func(t *testing.T) { - netClient = &goodIconClient{} - c := charts[0] - m := mock.Mock{} - dbClient := NewMockClient(&m) - db, _ := dbClient.Database("test") - m.On("UpdateOne", mock.Anything, bson.M{"_id": c.ID}, bson.M{"$set": bson.M{"raw_icon": iconBytes(), "icon_content_type": "image/png"}}, mock.Anything).Return(&mongo.UpdateResult{}, nil) - assert.NoErr(t, fetchAndImportIcon(db, c)) - m.AssertExpectations(t) - }) - - t.Run("valid SVG icon", func(t *testing.T) { - netClient = &svgIconClient{} - c := common.Chart{ - ID: "foo", - Icon: "https://foo/bar/logo.svg", - Repo: common.Repo{}, - } - m := mock.Mock{} - dbClient := NewMockClient(&m) - db, _ := dbClient.Database("test") - m.On("UpdateOne", mock.Anything, bson.M{"_id": c.ID}, bson.M{"$set": bson.M{"raw_icon": []byte("foo"), "icon_content_type": "image/svg"}}, mock.Anything).Return(&mongo.UpdateResult{}, nil) - assert.NoErr(t, fetchAndImportIcon(db, c)) - m.AssertExpectations(t) - }) -} - -func Test_fetchAndImportFiles(t *testing.T) { - index, _ := common.ParseRepoIndex([]byte(validRepoIndexYAML)) - charts := common.ChartsFromIndex(index, common.Repo{Name: "test", URL: "http://testrepo.com", AuthorizationHeader: "Bearer ThisSecretAccessTokenAuthenticatesTheClient1s"}) - cv := charts[0].ChartVersions[0] - - t.Run("http error", func(t *testing.T) { - m := mock.Mock{} - m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(mongo.ErrNoDocuments) - dbClient := NewMockClient(&m) - db, _ := dbClient.Database("test") - netClient = &badHTTPClient{} - assert.Err(t, io.EOF, fetchAndImportFiles(db, charts[0].Name, charts[0].Repo, cv)) - }) - - t.Run("file not found", func(t *testing.T) { - netClient = &goodTarballClient{c: charts[0], skipValues: true, skipReadme: true, skipSchema: true} - m := mock.Mock{} - m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(mongo.ErrNoDocuments) - chartFilesID := fmt.Sprintf("%s/%s-%s", charts[0].Repo.Name, charts[0].Name, cv.Version) - chartFiles := common.ChartFiles{ID: chartFilesID, Readme: "", Values: "", Schema: "", Repo: charts[0].Repo, Digest: cv.Digest} - chartBSON, _ := bson.Marshal(&chartFiles) - var doc bson.M - bson.Unmarshal(chartBSON, &doc) - delete(doc, "_id") - update := bson.M{"$set": doc} - m.On("UpdateOne", mock.Anything, bson.M{"_id": chartFilesID}, update, mock.Anything).Return(&mongo.UpdateResult{}, nil) - dbClient := NewMockClient(&m) - db, _ := dbClient.Database("test") - err := fetchAndImportFiles(db, charts[0].Name, charts[0].Repo, cv) - assert.NoErr(t, err) - m.AssertExpectations(t) - }) - - t.Run("authenticated request", func(t *testing.T) { - netClient = &authenticatedTarballClient{c: charts[0]} - m := mock.Mock{} - m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(mongo.ErrNoDocuments) - chartFilesID := fmt.Sprintf("%s/%s-%s", charts[0].Repo.Name, charts[0].Name, cv.Version) - chartFiles := common.ChartFiles{ID: chartFilesID, Readme: testChartReadme, Values: testChartValues, Schema: testChartSchema, Repo: charts[0].Repo, Digest: cv.Digest} - chartBSON, _ := bson.Marshal(&chartFiles) - var doc bson.M - bson.Unmarshal(chartBSON, &doc) - delete(doc, "_id") - update := bson.M{"$set": doc} - m.On("UpdateOne", mock.Anything, bson.M{"_id": chartFilesID}, update, mock.Anything).Return(&mongo.UpdateResult{}, nil) - dbClient := NewMockClient(&m) - db, _ := dbClient.Database("test") - err := fetchAndImportFiles(db, charts[0].Name, charts[0].Repo, cv) - assert.NoErr(t, err) - m.AssertExpectations(t) - }) - - t.Run("valid tarball", func(t *testing.T) { - netClient = &goodTarballClient{c: charts[0]} - m := mock.Mock{} - m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(mongo.ErrNoDocuments) - chartFilesID := fmt.Sprintf("%s/%s-%s", charts[0].Repo.Name, charts[0].Name, cv.Version) - chartFiles := common.ChartFiles{ID: chartFilesID, Readme: testChartReadme, Values: testChartValues, Schema: testChartSchema, Repo: charts[0].Repo, Digest: cv.Digest} - chartBSON, _ := bson.Marshal(&chartFiles) - var doc bson.M - bson.Unmarshal(chartBSON, &doc) - delete(doc, "_id") - update := bson.M{"$set": doc} - m.On("UpdateOne", mock.Anything, bson.M{"_id": chartFilesID}, update, mock.Anything).Return(&mongo.UpdateResult{}, nil) - dbClient := NewMockClient(&m) - db, _ := dbClient.Database("test") - err := fetchAndImportFiles(db, charts[0].Name, charts[0].Repo, cv) - assert.NoErr(t, err) - m.AssertExpectations(t) - }) - - t.Run("file exists", func(t *testing.T) { - m := mock.Mock{} - // don't return an error when checking if files already exists - m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) - dbClient := NewMockClient(&m) - db, _ := dbClient.Database("test") - err := fetchAndImportFiles(db, charts[0].Name, charts[0].Repo, cv) - assert.NoErr(t, err) - m.AssertNotCalled(t, "UpdateOne", mock.Anything, mock.Anything, mock.Anything) - }) -} - -func Test_chartTarballURL(t *testing.T) { - r := common.Repo{Name: "test", URL: "http://testrepo.com"} - tests := []struct { - name string - cv common.ChartVersion - wanted string - }{ - {"absolute url", common.ChartVersion{URLs: []string{"http://testrepo.com/wordpress-0.1.0.tgz"}}, "http://testrepo.com/wordpress-0.1.0.tgz"}, - {"relative url", common.ChartVersion{URLs: []string{"wordpress-0.1.0.tgz"}}, "http://testrepo.com/wordpress-0.1.0.tgz"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, common.ChartTarballURL(r, tt.cv), tt.wanted, "url") - }) - } -} - -func Test_extractFilesFromTarball(t *testing.T) { - tests := []struct { - name string - files []tarballFile - filename string - want string - }{ - {"file", []tarballFile{{"file.txt", "best file ever"}}, "file.txt", "best file ever"}, - {"multiple file tarball", []tarballFile{{"file.txt", "best file ever"}, {"file2.txt", "worst file ever"}}, "file2.txt", "worst file ever"}, - {"file in dir", []tarballFile{{"file.txt", "best file ever"}, {"test/file2.txt", "worst file ever"}}, "test/file2.txt", "worst file ever"}, - {"filename ignore case", []tarballFile{{"Readme.md", "# readme for chart"}, {"values.yaml", "key: value"}}, "README.md", "# readme for chart"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var b bytes.Buffer - createTestTarball(&b, tt.files) - r := bytes.NewReader(b.Bytes()) - tarf := tar.NewReader(r) - files, err := common.ExtractFilesFromTarball([]string{tt.filename}, tarf) - assert.NoErr(t, err) - assert.Equal(t, files[tt.filename], tt.want, "file body") - }) - } - - t.Run("extract multiple files", func(t *testing.T) { - var b bytes.Buffer - tFiles := []tarballFile{{"file.txt", "best file ever"}, {"file2.txt", "worst file ever"}} - createTestTarball(&b, tFiles) - r := bytes.NewReader(b.Bytes()) - tarf := tar.NewReader(r) - files, err := common.ExtractFilesFromTarball([]string{tFiles[0].Name, tFiles[1].Name}, tarf) - assert.NoErr(t, err) - assert.Equal(t, len(files), 2, "matches") - for _, f := range tFiles { - assert.Equal(t, files[f.Name], f.Body, "file body") - } - }) - - t.Run("file not found", func(t *testing.T) { - var b bytes.Buffer - createTestTarball(&b, []tarballFile{{"file.txt", "best file ever"}}) - r := bytes.NewReader(b.Bytes()) - tarf := tar.NewReader(r) - name := "file2.txt" - files, err := common.ExtractFilesFromTarball([]string{name}, tarf) - assert.NoErr(t, err) - assert.Equal(t, files[name], "", "file body") - }) - - t.Run("not a tarball", func(t *testing.T) { - b := make([]byte, 4) - rand.Read(b) - r := bytes.NewReader(b) - tarf := tar.NewReader(r) - files, err := common.ExtractFilesFromTarball([]string{"file2.txt"}, tarf) - assert.Err(t, io.ErrUnexpectedEOF, err) - assert.Equal(t, len(files), 0, "file body") - }) -} - -type tarballFile struct { - Name, Body string -} - -func createTestTarball(w io.Writer, files []tarballFile) { - // Create a new tar archive. - tarw := tar.NewWriter(w) - - // Add files to the archive. - for _, file := range files { - hdr := &tar.Header{ - Name: file.Name, - Mode: 0600, - Size: int64(len(file.Body)), - } - if err := tarw.WriteHeader(hdr); err != nil { - log.Fatalln(err) - } - if _, err := tarw.Write([]byte(file.Body)); err != nil { - log.Fatalln(err) - } - } - // Make sure to check the error on Close. - if err := tarw.Close(); err != nil { - log.Fatal(err) - } -} - -func Test_initNetClient(t *testing.T) { - // Test env - otherDir, err := ioutil.TempDir("", "ca-registry") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(otherDir) - - // Create cert - caCert := `-----BEGIN CERTIFICATE----- -MIIC6jCCAdKgAwIBAgIUKVfzA7lfBgSYP8enCVhlm0ql5YwwDQYJKoZIhvcNAQEL -BQAwDTELMAkGA1UEAxMCQ0EwHhcNMTgxMjEyMTQxNzAwWhcNMjMxMjExMTQxNzAw -WjANMQswCQYDVQQDEwJDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -ALZU3fsAgvoUuLSHr24apslaYyuX84wGoZQmtFtQ+A3DF9KL/2nn3yZ6qJPkH0TF -sbObEQRNi+P6vQ3nI/dSNMX5PzMBP2CB6L7zEXzZQEHtAK0Bzva5CKEBGX7OfIKl -aBvs+dzKVJBdb+Oh0maacMwa4QbcD6ejzF90jUbaO65lpQpcL7KQdppKOGNclRaA -hQTV2VsxrV4hH7K9btaTTxso+8W6p8v6X9vf40Ywx72p+SKnGh+FCrOp1gYLBLwo -4SM0OUQHRvqUlj0XhZk5pW0dMRwHcoz1S2GmE5bj4edr4j+zGzGxa2wRGKvM0OCn -Do84AVszTFPmUf+mCl4pJNECAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1Ud -EwEB/wQFMAMBAf8wHQYDVR0OBBYEFI5l5k+MEhrbOQ29dOW1qJhI0yKaMA0GCSqG -SIb3DQEBCwUAA4IBAQByDebUOKzn6jfmXlW62vm09V+ipqId01wm21G9XMtMEqhc -xtun6YwQeTuGPtdepWG+NXuSsiX/HNAHeaumJaaljHhdKDisnMQ0CTnNsu8NPkAl -9iMEB3iXLWkb7+HgfPJAHZVGcMqMxNEMZYHB1Fh0G2Ne376X94+GYJ08qR2C8rUP -BShhMSktB578h4GtPIWSjPhDUWg1fGe7sewR+GPyuL9859hOD0wGm9tUixBKloCu -b90fhqZZ3FqZD7W1qJGKvz/8geqi0noip+uq/dokK1jarRkOVEJP+EvXkHo0tIuc -h251U/Daz6NiQBM9AxyAw6EHm8XAZBvCuebfzyrT ------END CERTIFICATE-----` - otherCA := path.Join(otherDir, "ca.crt") - err = ioutil.WriteFile(otherCA, []byte(caCert), 0644) - if err != nil { - t.Error(err) - } - - _, err = common.InitNetClient(otherCA, defaultTimeoutSeconds) - if err != nil { - t.Error(err) - } -} - -var emptyRepoIndexYAMLBytes, _ = ioutil.ReadFile("testdata/empty-repo-index.yaml") -var emptyRepoIndexYAML = string(emptyRepoIndexYAMLBytes) - -type emptyChartRepoHTTPClient struct{} - -func (h *emptyChartRepoHTTPClient) Do(req *http.Request) (*http.Response, error) { - w := httptest.NewRecorder() - w.Write([]byte(emptyRepoIndexYAML)) - return w.Result(), nil -} - -func Test_emptyChartRepo(t *testing.T) { - netClient = &emptyChartRepoHTTPClient{} - m := mock.Mock{} - dbClient := NewMockClient(&m) - //Expect a call to test the DB readiness - m.On("InsertOne", mock.Anything, mock.Anything, mock.Anything).Return(&mongo.InsertOneResult{}, nil) - m.On("DeleteMany", mock.Anything, mock.Anything, mock.Anything).Return(&mongo.DeleteResult{}, nil) - - m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) - err := SyncRepo(dbClient, "test", "testRepo", "https://my.examplerepo.com", "") - assert.ExistsErr(t, err, "Failed Request") -} - -func Test_getSha256(t *testing.T) { - sha, err := common.GetSha256([]byte("this is a test")) - assert.Equal(t, err, nil, "Unable to get sha") - assert.Equal(t, sha, "2e99758548972a8e8822ad47fa1017ff72f06f3ff6a016851f45c398732bc50c", "Unable to get sha") -} - -func Test_repoAlreadyProcessed(t *testing.T) { - tests := []struct { - name string - checksum string - mockedLastCheck common.RepoCheck - processed bool - }{ - {"not processed yet", "bar", common.RepoCheck{}, false}, - {"already processed", "bar", common.RepoCheck{Checksum: "bar"}, true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m := mock.Mock{} - repo := &common.RepoCheck{} - m.On("FindOne", mock.Anything, mock.Anything, repo, mock.Anything).Run(func(args mock.Arguments) { - *args.Get(2).(*common.RepoCheck) = tt.mockedLastCheck - }).Return(nil) - dbClient := NewMockClient(&m) - db, _ := dbClient.Database("test") - res := repoAlreadyProcessed(db, "", tt.checksum) - if res != tt.processed { - t.Errorf("Expected alreadyProcessed to be %v got %v", tt.processed, res) - } - }) - } -} - -func Test_updateLastCheck(t *testing.T) { - m := mock.Mock{} - repoName := "foo" - checksum := "bar" - now := time.Now() - selector := bson.M{"_id": repoName} - m.On("UpdateOne", mock.Anything, selector, bson.M{"$set": bson.M{"last_update": now, "checksum": checksum}}, mock.Anything).Return(&mongo.UpdateResult{}, nil) - dbClient := NewMockClient(&m) - db, _ := dbClient.Database("test") - err := updateLastCheck(db, repoName, checksum, now) - if err != nil { - t.Errorf("Unexpected error %v", err) - } - if len(m.Calls) != 1 { - t.Errorf("Expected one call got %d", len(m.Calls)) - } -} diff --git a/src/jetstream/plugins/monocular/chart-repo/go.mod b/src/jetstream/plugins/monocular/chart-repo/go.mod deleted file mode 100644 index 8ba1b1a9dc..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/go.mod +++ /dev/null @@ -1,37 +0,0 @@ -module github.com/helm/monocular/chartrepo - -go 1.12 - -require ( - github.com/Masterminds/semver v1.5.0 // indirect - github.com/arschles/assert v1.0.0 - github.com/cyphar/filepath-securejoin v0.2.2 // indirect - github.com/disintegration/imaging v1.6.2 - github.com/ghodss/yaml v1.0.0 - github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 // indirect - github.com/gobwas/glob v0.2.3 // indirect - github.com/golang/snappy v0.0.1 // indirect - github.com/google/uuid v1.1.1 - github.com/gorilla/mux v1.7.3 - github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40 - github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a - github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a - github.com/prometheus/client_golang v1.2.1 // indirect - github.com/sirupsen/logrus v1.4.2 - github.com/spf13/cobra v0.0.5 - github.com/stretchr/testify v1.4.0 - github.com/urfave/negroni v1.0.0 - github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect - github.com/xdg/stringprep v1.0.0 // indirect - go.mongodb.org/mongo-driver v1.1.3 - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect - k8s.io/apimachinery v0.0.0-20191203211716-adc6f4cd9e7d // indirect - k8s.io/client-go v11.0.0+incompatible // indirect - k8s.io/helm v2.16.1+incompatible -) - -replace ( - github.com/helm/monocular/chartrepo/common => ./common - github.com/helm/monocular/chartrepo/foundationdb => ./foundationdb - github.com/helm/monocular/chartrepo/utils => ./utils -) diff --git a/src/jetstream/plugins/monocular/chart-repo/go.sum b/src/jetstream/plugins/monocular/chart-repo/go.sum deleted file mode 100644 index b34c274aa1..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/go.sum +++ /dev/null @@ -1,230 +0,0 @@ -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/arschles/assert v1.0.0 h1:NofQbRhtxcLgP+XoKunA7J6UMJNTqX7xR/19tej8UsA= -github.com/arschles/assert v1.0.0/go.mod h1:m/u69zW43x0h8dTHcv3JJZljINyEYgBuf5fYJP6WikI= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA= -github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= -github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= -github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= -github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/helm/monocular v1.9.0 h1:jmCzg4mwHLLyL75/5Ekj3aqJs0Hr63fX3+n/hInwZ3U= -github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40 h1:GT4RsKmHh1uZyhmTkWJTDALRjSHYQp6FRKrotf0zhAs= -github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40/go.mod h1:NtmN9h8vrTveVQRLHcX2HQ5wIPBDCsZ351TGbZWgg38= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o= -github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= -github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a h1:VqeX/fehAB6FtBox0TVYcjOMXGE56INQIfbXegditX4= -github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a/go.mod h1:TsgmjeDpbftqhwPKInJ3v+l+xbHs4goiB6DFb2WqY9c= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI= -github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= -github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= -github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= -github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= -github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= -github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -go.mongodb.org/mongo-driver v1.1.3 h1:++7u8r9adKhGR+I79NfEtYrk2ktjenErXM99PSufIoI= -go.mongodb.org/mongo-driver v1.1.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= -golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= -golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -k8s.io/apimachinery v0.0.0-20191203211716-adc6f4cd9e7d h1:q+OZmYewHJeMCzwpHkXlNTtk5bvaUMPCikKvf77RBlo= -k8s.io/apimachinery v0.0.0-20191203211716-adc6f4cd9e7d/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= -k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o= -k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= -k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/helm v2.16.1+incompatible h1:L+k810plJlaGWEw1EszeT4deK8XVaKxac1oGcuB+WDc= -k8s.io/helm v2.16.1+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= -k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= -sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/src/jetstream/plugins/monocular/chart-repo/serve.go b/src/jetstream/plugins/monocular/chart-repo/serve.go deleted file mode 100644 index 22589b0524..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/serve.go +++ /dev/null @@ -1,67 +0,0 @@ -/* -Copyright (c) 2018 The Helm Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package main - -import ( - "os" - - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -//ServeCmd Start an HTTP server to allow on-demand trigger of sync or delete -var ServeCmd = &cobra.Command{ - Use: "serve", - Short: "Start an HTTP server for on-demand trigger of sync or delete", - Run: func(cmd *cobra.Command, args []string) { - fdbURL, err := cmd.Flags().GetString("doclayer-url") - if err != nil { - log.Fatal(err) - } - fDB, err := cmd.Flags().GetString("doclayer-database") - if err != nil { - log.Fatal(err) - } - cACert, err := cmd.Flags().GetString("cafile") - if err != nil { - log.Fatal(err) - } - key, err := cmd.Flags().GetString("keyfile") - if err != nil { - log.Fatal(err) - } - cert, err := cmd.Flags().GetString("certfile") - if err != nil { - log.Fatal(err) - } - - //TLS options must either be all set to enabled TLS, or none set to disable TLS - var tlsEnabled = cACert != "" && key != "" && cert != "" - if !(tlsEnabled || (cACert == "" && key == "" && cert == "")) { - cmd.Help() - log.Fatal("To enable TLS, all 3 TLS cert paths must be set.") - } - - debug, err := cmd.Flags().GetBool("debug") - if err != nil { - log.Fatal(err) - } - authorizationHeader := os.Getenv("AUTHORIZATION_HEADER") - - initOnDemandEndpoint(fdbURL, fDB, tlsEnabled, cACert, cert, key, authorizationHeader, debug) - }, -} diff --git a/src/jetstream/plugins/monocular/chart-repo/sync.go b/src/jetstream/plugins/monocular/chart-repo/sync.go deleted file mode 100644 index 5230e78ead..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/sync.go +++ /dev/null @@ -1,39 +0,0 @@ -/* -Copyright (c) 2018 The Helm Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package main - -import ( - "github.com/helm/monocular/chartrepo/foundationdb" - - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -//SyncCmd Sync a chart repository with Monocular -var SyncCmd = &cobra.Command{ - Use: "sync [REPO NAME] [REPO URL]", - Short: "add a new chart repository, and resync its charts periodically", - Run: func(cmd *cobra.Command, args []string) { - - if len(args) != 2 { - log.Info("Need exactly two arguments: [REPO NAME] [REPO URL]") - cmd.Help() - return - } - foundationdb.Sync(cmd, args) - }, -} diff --git a/src/jetstream/plugins/monocular/chart-repo/testdata/empty-repo-index.yaml b/src/jetstream/plugins/monocular/chart-repo/testdata/empty-repo-index.yaml deleted file mode 100644 index 9e608aa034..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/testdata/empty-repo-index.yaml +++ /dev/null @@ -1 +0,0 @@ -entries: \ No newline at end of file diff --git a/src/jetstream/plugins/monocular/chart-repo/testdata/valid-index.yaml b/src/jetstream/plugins/monocular/chart-repo/testdata/valid-index.yaml deleted file mode 100644 index d5a93537c1..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/testdata/valid-index.yaml +++ /dev/null @@ -1,69 +0,0 @@ -entries: - acs-engine-autoscaler: - - apiVersion: v1 - appVersion: 2.1.1 - created: 2017-12-06T18:48:59.568323124Z - description: Scales worker nodes within agent pools - digest: 39e66eb53c310529bd9dd19776f8ba662e063a4ebd51fc5ec9f2267e2e073e3e - icon: https://github.com/kubernetes/kubernetes/blob/master/logo/logo.png - maintainers: - - email: ritazh@microsoft.com - name: ritazh - - email: wibuch@microsoft.com - name: wbuchwalter - name: acs-engine-autoscaler - sources: - - https://github.com/wbuchwalter/Kubernetes-acs-engine-autoscaler - urls: - - https://kubernetes-charts.storage.googleapis.com/acs-engine-autoscaler-2.1.1.tgz - version: 2.1.1 - wordpress: - - appVersion: 4.9.1 - created: 2017-12-06T18:48:59.644981487Z - description: new description! - digest: 74889e60a35dcffa4686f88bb23de863fed2b6e63a69b1f4858dde37c301885c - engine: gotpl - home: http://www.wordpress.com/ - icon: https://bitnami.com/assets/stacks/wordpress/img/wordpress-stack-220x234.png - keywords: - - wordpress - - cms - - blog - - http - - web - - application - - php - maintainers: - - email: containers@bitnami.com - name: bitnami-bot - name: wordpress - sources: - - https://github.com/bitnami/bitnami-docker-wordpress - urls: - - https://kubernetes-charts.storage.googleapis.com/wordpress-0.7.5.tgz - version: 0.7.5 - - appVersion: 4.9.0 - created: 2017-12-01T11:49:00.136950565Z - description: Web publishing platform for building blogs and websites. - digest: a69139ef3008eeb11ca60261ec2ded61e84ce7db32bb3626056e84bcff7ec270 - engine: gotpl - home: http://www.wordpress.com/ - icon: https://bitnami.com/assets/stacks/wordpress/img/wordpress-stack-220x234.png - keywords: - - wordpress - - cms - - cms - - blog - - http - - web - - application - - php - maintainers: - - email: containers@bitnami.com - name: bitnami-bot - name: wordpress - sources: - - https://github.com/bitnami/bitnami-docker-wordpress - urls: - - https://kubernetes-charts.storage.googleapis.com/wordpress-0.7.4.tgz - version: 0.7.4 diff --git a/src/jetstream/plugins/monocular/chart-repo/utils/version.go b/src/jetstream/plugins/monocular/chart-repo/utils/version.go deleted file mode 100644 index e30f658e8c..0000000000 --- a/src/jetstream/plugins/monocular/chart-repo/utils/version.go +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright (c) 2018 The Helm Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package utils - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -var ( - Version = "devel" - UserAgentComment string -) - -// UserAgent returns the user agent to be used during calls to the chart repositories -// Examples: -// chart-repo/devel -// chart-repo/1.0 -// chart-repo/1.0 (monocular v1.0-beta4) -// More info here https://github.com/kubeapps/kubeapps/issues/767#issuecomment-436835938 -func UserAgent() string { - ua := "chart-repo/" + Version - if UserAgentComment != "" { - ua = fmt.Sprintf("%s (%s)", ua, UserAgentComment) - } - return ua -} - -//VersionCmd returns Monocular version information -var VersionCmd = &cobra.Command{ - Use: "version", - Short: "returns version information", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println(Version) - }, -} diff --git a/src/jetstream/plugins/monocular/chartsvc/models/chart.go b/src/jetstream/plugins/monocular/chart.go similarity index 57% rename from src/jetstream/plugins/monocular/chartsvc/models/chart.go rename to src/jetstream/plugins/monocular/chart.go index 7501f85c36..70e9c4a763 100644 --- a/src/jetstream/plugins/monocular/chartsvc/models/chart.go +++ b/src/jetstream/plugins/monocular/chart.go @@ -14,12 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -package models +package monocular import ( "time" - - "k8s.io/helm/pkg/proto/hapi/chart" ) // Repo holds the App repository details @@ -30,18 +28,18 @@ type Repo struct { // Chart is a higher-level representation of a chart package type Chart struct { - ID string `json:"-" bson:"_id"` - Name string `json:"name"` - Repo Repo `json:"repo"` - Description string `json:"description"` - Home string `json:"home"` - Keywords []string `json:"keywords"` - Maintainers []chart.Maintainer `json:"maintainers"` - Sources []string `json:"sources"` - Icon string `json:"icon"` - RawIcon []byte `json:"-" bson:"raw_icon"` - IconContentType string `json:"-" bson:"icon_content_type,omitempty"` - ChartVersions []ChartVersion `json:"-"` + ID string `json:"-"` + Name string `json:"name"` + Repo Repo `json:"repo"` + Description string `json:"description"` + Home string `json:"home"` + Keywords []string `json:"keywords"` + Maintainers []ChartMaintainer `json:"maintainers"` + Sources []string `json:"sources"` + Icon string `json:"icon"` + RawIcon []byte `json:"-"` + IconContentType string `json:"-"` + ChartVersions []ChartVersion `json:"-"` } // ChartVersion is a representation of a specific version of a chart @@ -51,17 +49,9 @@ type ChartVersion struct { Created time.Time `json:"created"` Digest string `json:"digest"` URLs []string `json:"urls"` - Readme string `json:"readme" bson:"-"` - Values string `json:"values" bson:"-"` - Schema string `json:"schema" bson:"-"` -} - -// ChartFiles holds the README and values for a given chart version -type ChartFiles struct { - ID string `bson:"_id"` - Readme string - Values string - Schema string + Readme string `json:"readme,omitempty"` + Values string `json:"values,omitempty"` + Schema string `json:"schema,omitempty"` } //RepoCheck describes the state of a repository in terms its current checksum and last update time. diff --git a/src/jetstream/plugins/monocular/chart_svc.go b/src/jetstream/plugins/monocular/chart_svc.go new file mode 100644 index 0000000000..770d0d69fc --- /dev/null +++ b/src/jetstream/plugins/monocular/chart_svc.go @@ -0,0 +1,283 @@ +package monocular + +import ( + "errors" + "fmt" + "net/http" + "os" + "path" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/monocular/store" + "github.com/labstack/echo" + log "github.com/sirupsen/logrus" +) + +// Functions to provide a Monocular compatible API with our chart store + +// List all helm Charts - gets the latest version for each Chart +func (m *Monocular) listCharts(c echo.Context) error { + log.Debug("List Charts called") + + // Check if this is a request for an external Monocular + if handled, err := m.processMonocularRequest(c); handled { + return err + } + + charts, err := m.ChartStore.GetLatestCharts() + if err != nil { + return err + } + + // Translate the list into an array of Charts + var list APIListResponse + for _, chart := range charts { + list = append(list, m.translateToChartAPIResponse(chart, nil)) + } + + meta := Meta{ + TotalPages: 1, + } + + body := BodyAPIListResponse{ + Data: &list, + Meta: meta, + } + + return c.JSON(200, body) +} + +// Get the latest version of a given chart +func (m *Monocular) getChart(c echo.Context) error { + log.Debug("Get Chart called") + + // Check if this is a request for an external Monocular + if handled, err := m.processMonocularRequest(c); handled { + return err + } + + repo := c.Param("repo") + chartName := c.Param("name") + + chart, err := m.ChartStore.GetChart(repo, chartName, "") + if err != nil { + return err + } + + chartYaml := m.getChartYaml(*chart) + body := BodyAPIResponse{ + Data: *m.translateToChartAPIResponse(chart, chartYaml), + } + return c.JSON(200, body) +} + +func (m *Monocular) getIcon(c echo.Context) error { + log.Debug("Get Icon called") + + // Check if this is a request for an external Monocular + if handled, err := m.processMonocularRequest(c); handled { + return err + } + + repo := c.Param("repo") + chartName := c.Param("chartName") + version := c.Param("version") + + if len(version) == 0 { + log.Debugf("Get icon for %s/%s", repo, chartName) + } else { + log.Debugf("Get icon for %s/%s-%s", repo, chartName, version) + } + + chart, err := m.ChartStore.GetChart(repo, chartName, version) + if err != nil { + log.Error("Can not find chart") + return errors.New("Error") + } + + // This will download and cache the icon if it is not already cached - it returns the local file path to the icon file + // or an empty string if no icon is available or could not be downloaded + iconFilePath, _ := m.cacheChartIcon(*chart) + if len(iconFilePath) == 0 { + // No icon or error downloading + http.Redirect(c.Response().Writer, c.Request(), "/core/assets/custom/placeholder.png", http.StatusTemporaryRedirect) + return nil + } + + c.File(iconFilePath) + return nil +} + +// /chartsvc/v1/charts/:repo/:name/versions/:version +// Get specific chart version +func (m *Monocular) getChartVersion(c echo.Context) error { + log.Debug("getChartAndVersion called") + + // Check if this is a request for an external Monocular + if handled, err := m.processMonocularRequest(c); handled { + return err + } + + repo := c.Param("repo") + chartName := c.Param("name") + version := c.Param("version") + + chart, err := m.ChartStore.GetChart(repo, chartName, version) + if err != nil { + return err + } + + chartYaml := m.getChartYaml(*chart) + if chartYaml == nil { + // Error - could not get chart yaml + return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Can not find Chart.yaml for %s/%s-%s", repo, chartName, version)) + } + + body := BodyAPIResponse{ + Data: *m.translateToChartVersionAPIResponse(chart, chartYaml), + } + return c.JSON(200, body) +} + +// /chartsvc/v1/charts/:repo/:name/versions +// Get all chart versions for a given chart +func (m *Monocular) getChartVersions(c echo.Context) error { + + // Check if this is a request for an external Monocular + if handled, err := m.processMonocularRequest(c); handled { + return err + } + + repo := c.Param("repo") + chartName := c.Param("name") + + // Get all versions for a given chart + charts, err := m.ChartStore.GetChartVersions(repo, chartName) + if err != nil { + return err + } + + // Translate the list into an array of Charts + var list APIListResponse + for _, chart := range charts { + list = append(list, m.translateToChartVersionAPIResponse(chart, nil)) + } + + body := BodyAPIListResponse{ + Data: &list, + } + + return c.JSON(200, body) +} + +// Get a file such as the README or valyes for a given chart version +func (m *Monocular) getChartAndVersionFile(c echo.Context) error { + log.Debug("Get Chart file called") + + // Check if this is a request for an external Monocular + if handled, err := m.processMonocularRequest(c); handled { + return err + } + + repo := c.Param("repo") + chartName := c.Param("name") + version := c.Param("version") + filename := c.Param("filename") + + log.Debugf("Get chart file: %s", filename) + + chart, err := m.ChartStore.GetChart(repo, chartName, version) + if err != nil { + return err + } + + if m.cacheChart(*chart) == nil { + return c.File(path.Join(m.getChartCacheFolder(*chart), filename)) + } + + return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Can not find file %s for the specified chart", filename)) +} + +// This is the simpler version that returns just enough data needed for the Charts list view +// This is a slight cheat - the response is not as complete as the Monocular API, but includes +// enough for the UI and saves us having to pull out all of the Chart.yaml files +func (m *Monocular) translateToChartAPIResponse(record *store.ChartStoreRecord, chartYaml *ChartMetadata) *APIResponse { + response := &APIResponse{ + ID: fmt.Sprintf("%s/%s", record.Repository, record.Name), + Type: "chart", + Relationships: make(map[string]Rel), + Attributes: m.translateToChart(record, chartYaml), + } + + response.Relationships["latestChartVersion"] = Rel{ + Data: m.translateToChartVersion(record, chartYaml), + } + return response +} + +func (m *Monocular) translateToChart(record *store.ChartStoreRecord, chartYaml *ChartMetadata) Chart { + chart := Chart{ + Name: record.Name, + Description: record.Description, + Repo: Repo{}, + Icon: fmt.Sprintf("/v1/assets/%s/%s/%s/logo", record.Repository, record.Name, record.Version), + Sources: record.Sources, + } + + chart.Repo.Name = record.Repository + + if chartYaml != nil { + chart.Keywords = chartYaml.Keywords + chart.Maintainers = make([]ChartMaintainer, len(chartYaml.Maintainers)) + for index, maintainer := range chartYaml.Maintainers { + chart.Maintainers[index] = *maintainer + } + + chart.Home = chartYaml.Home + } + + return chart +} + +func (m *Monocular) translateToChartVersion(record *store.ChartStoreRecord, chartYaml *ChartMetadata) ChartVersion { + chartVersion := ChartVersion{ + Version: record.Version, + AppVersion: record.AppVersion, + Digest: record.Digest, + Created: record.Created, + URLs: make([]string, 1), + } + chartVersion.URLs[0] = record.ChartURL + if chartYaml != nil { + // If we have the Chart yaml, then we already have the chart + // Add in the files that are available + cacheFolder := m.getChartCacheFolder(*record) + chartVersion.Readme = getFileAssetURL(record.Repository, record.Name, record.Version, cacheFolder, "README.md") + chartVersion.Schema = getFileAssetURL(record.Repository, record.Name, record.Version, cacheFolder, "values.schema.json") + chartVersion.Values = getFileAssetURL(record.Repository, record.Name, record.Version, cacheFolder, "values.yaml") + } + + return chartVersion +} + +func (m *Monocular) translateToChartVersionAPIResponse(record *store.ChartStoreRecord, chartYaml *ChartMetadata) *APIResponse { + response := &APIResponse{ + ID: fmt.Sprintf("%s/%s-%s", record.Repository, record.Name, record.Version), + Type: "chartVersion", + Relationships: make(map[string]Rel), + Attributes: m.translateToChartVersion(record, chartYaml), + } + + response.Relationships["chart"] = Rel{ + Data: m.translateToChart(record, chartYaml), + } + return response +} + +func getFileAssetURL(repo, name, version, folder, filename string) string { + cachePath := path.Join(folder, filename) + if _, err := os.Stat(cachePath); os.IsNotExist(err) { + return "" + } + + return fmt.Sprintf("/v1/assets/%s/%s/versions/%s/%s", repo, name, version, filename) +} diff --git a/src/jetstream/plugins/monocular/chartsvc/Dockerfile b/src/jetstream/plugins/monocular/chartsvc/Dockerfile deleted file mode 100644 index 9f4ce2c0bc..0000000000 --- a/src/jetstream/plugins/monocular/chartsvc/Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM golang:1.12 as builder -COPY . /go/src/github.com/helm/monocular/chartsvc -WORKDIR /go/src/github.com/helm/monocular/chartsvc -RUN GO111MODULE=on GOPROXY=https://gocenter.io CGO_ENABLED=0 go build -a -installsuffix cgo . -FROM scratch -COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ -COPY --from=builder /go/src/github.com/helm/monocular/chartsvc /chartsvc -EXPOSE 8080 -CMD ["/chartsvc"] diff --git a/src/jetstream/plugins/monocular/chartsvc/Makefile b/src/jetstream/plugins/monocular/chartsvc/Makefile deleted file mode 100644 index ef0b800ae2..0000000000 --- a/src/jetstream/plugins/monocular/chartsvc/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -IMAGE_REPO ?= docker.io/kreinecke/suse-fdb-chartsvc -IMAGE_TAG ?= latest - -docker-build: - # We use the context of the root dir - docker build --pull --rm -t ${IMAGE_REPO}:${IMAGE_TAG} -f Dockerfile . diff --git a/src/jetstream/plugins/monocular/chartsvc/README.md b/src/jetstream/plugins/monocular/chartsvc/README.md deleted file mode 100644 index 2cc0afc9d3..0000000000 --- a/src/jetstream/plugins/monocular/chartsvc/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Chartsvc - -Chartsvc is a service for Monocular that reads chart metadata from the database -and presents it in a RESTful API. It should be used with the -[chart-repo](https://github.com/helm/monocular/tree/master/cmd/chart-repo) to -populate chart metadata in the database. diff --git a/src/jetstream/plugins/monocular/chartsvc/datastore.go b/src/jetstream/plugins/monocular/chartsvc/datastore.go deleted file mode 100644 index 0e789c277c..0000000000 --- a/src/jetstream/plugins/monocular/chartsvc/datastore.go +++ /dev/null @@ -1,41 +0,0 @@ -package chartsvc - -import ( - "context" - "fmt" - - "github.com/helm/monocular/chartsvc/foundationdb" - "github.com/helm/monocular/chartsvc/foundationdb/datastore" - "github.com/helm/monocular/chartsvc/models" - log "github.com/sirupsen/logrus" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -const chartCollection = "charts" - -type ChartSvcDatastore struct { - dbClient datastore.Client - db datastore.Database - dbCloser func() -} - -func (m *ChartSvcDatastore) ListRepositories() ([]string, error) { - return foundationdb.ListRepositories() -} - -// GetChart returns the chart with the given ID -func (m *ChartSvcDatastore) GetChart(chartID string) (models.Chart, error) { - var chart models.Chart - - chartCollection := m.db.Collection(chartCollection) - filter := bson.M{"_id": chartID} - findResult := chartCollection.FindOne(context.Background(), filter, &chart, options.FindOne()) - if findResult == mongo.ErrNoDocuments { - log.WithError(findResult).Errorf("could not find chart with id %s", chartID) - return chart, fmt.Errorf("could not find chart with id %s", chartID) - } - - return chart, nil -} diff --git a/src/jetstream/plugins/monocular/chartsvc/foundationdb/datastore/datastore.go b/src/jetstream/plugins/monocular/chartsvc/foundationdb/datastore/datastore.go deleted file mode 100644 index 37ac06d008..0000000000 --- a/src/jetstream/plugins/monocular/chartsvc/foundationdb/datastore/datastore.go +++ /dev/null @@ -1,146 +0,0 @@ -/* -Copyright (c) 2019 - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package datastore implements an interface on top of the mgo mongo client -package datastore - -import ( - "context" - "fmt" - "reflect" - "time" - - log "github.com/sirupsen/logrus" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -const defaultTimeout = 30 * time.Second - -// Config configures the database connection -type Config struct { - URL string - Database string - Timeout time.Duration -} - -// Client is an interface for a MongoDB client -type Client interface { - Database(name string) (Database, func()) -} - -// Database is an interface for accessing a MongoDB database -type Database interface { - Collection(name string) Collection -} - -// Collection is an interface for accessing a MongoDB collection -type Collection interface { - BulkWrite(ctxt context.Context, operations []mongo.WriteModel, options *options.BulkWriteOptions) (*mongo.BulkWriteResult, error) - DeleteMany(ctxt context.Context, filter interface{}, options *options.DeleteOptions) (*mongo.DeleteResult, error) - FindOne(ctxt context.Context, filter interface{}, result interface{}, options *options.FindOneOptions) error - InsertOne(ctxt context.Context, document interface{}, options *options.InsertOneOptions) (*mongo.InsertOneResult, error) - UpdateOne(ctxt context.Context, filter interface{}, update interface{}, options *options.UpdateOptions) (*mongo.UpdateResult, error) - Find(ctxt context.Context, filter interface{}, result interface{}, options *options.FindOptions) error -} - -// mongoDatabase wraps a mongo.Database and implements Collection -type mongoDatabase struct { - Database *mongo.Database -} - -// mongoClient wraps a mongo.Client and implements Database -type mongoClient struct { - Client *mongo.Client -} - -// NewDocLayerClient creates a new MongoDB client for the FoundationDB document layer. -func NewDocLayerClient(ctx context.Context, options *options.ClientOptions) (Client, error) { - client, err := mongo.Connect(ctx, options) - return &mongoClient{client}, err -} - -func (c *mongoClient) Database(dbName string) (Database, func()) { - - db := &mongoDatabase{c.Client.Database(dbName)} - - return db, func() { - log.Infof("Closing db connection") - err := c.Client.Disconnect(context.Background()) - - if err != nil { - log.Fatal(err) - } - fmt.Println("Connection to MongoDB closed.") - } -} - -func (d *mongoDatabase) Collection(name string) Collection { - return &mongoCollection{d.Database.Collection(name)} -} - -// mgoCollection wraps an mgo.Collection and implements Collection -type mongoCollection struct { - Collection *mongo.Collection -} - -func (c *mongoCollection) BulkWrite(ctxt context.Context, operations []mongo.WriteModel, options *options.BulkWriteOptions) (*mongo.BulkWriteResult, error) { - res, err := c.Collection.BulkWrite(ctxt, operations, options) - return res, err -} - -func (c *mongoCollection) DeleteMany(ctxt context.Context, filter interface{}, options *options.DeleteOptions) (*mongo.DeleteResult, error) { - res, err := c.Collection.DeleteMany(ctxt, filter, options) - return res, err -} - -func (c *mongoCollection) FindOne(ctxt context.Context, filter interface{}, result interface{}, options *options.FindOneOptions) error { - res := c.Collection.FindOne(ctxt, filter, options) - return res.Decode(result) -} - -func (c *mongoCollection) InsertOne(ctxt context.Context, document interface{}, options *options.InsertOneOptions) (*mongo.InsertOneResult, error) { - res, err := c.Collection.InsertOne(ctxt, document, options) - return res, err -} - -func (c *mongoCollection) UpdateOne(ctxt context.Context, filter interface{}, document interface{}, options *options.UpdateOptions) (*mongo.UpdateResult, error) { - res, err := c.Collection.UpdateOne(ctxt, filter, document, options) - return res, err -} - -func (c *mongoCollection) Find(ctxt context.Context, filter interface{}, result interface{}, options *options.FindOptions) error { - resCursor, err := c.Collection.Find(ctxt, filter, options) - if err != nil { - log.WithError(err).Errorf( - "Error fetching query result.") - } else { - resultType := reflect.ValueOf(result) - if resultType.Kind() != reflect.Ptr { - return fmt.Errorf("Result arg must be a pointer type. Got: %v", resultType.Kind()) - } - resultSliceType := resultType.Elem() - if resultSliceType.Kind() != reflect.Slice { - return fmt.Errorf("Result arg must be a slice value. Got: %v", resultSliceType.Kind()) - } - err = resCursor.All(context.Background(), result) - if err != nil { - log.WithError(err).Errorf( - "Error decoding query result.") - } - } - return err -} diff --git a/src/jetstream/plugins/monocular/chartsvc/foundationdb/datastore/mockstore.go b/src/jetstream/plugins/monocular/chartsvc/foundationdb/datastore/mockstore.go deleted file mode 100644 index 053ffea382..0000000000 --- a/src/jetstream/plugins/monocular/chartsvc/foundationdb/datastore/mockstore.go +++ /dev/null @@ -1,87 +0,0 @@ -/* -Copyright (c) 2019 - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package datastore - -import ( - "context" - - "github.com/stretchr/testify/mock" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -// mockDatabase acts as a mock datastore.Database -type mockDatabase struct { - *mock.Mock -} - -type mockClient struct { - *mock.Mock -} - -//NewMockClient returns a mocked FDB Document-Layer client -func NewMockClient(m *mock.Mock) Client { - return mockClient{m} -} - -// DB returns a mocked datastore.Database and empty closer function -func (c mockClient) Database(dbName string) (Database, func()) { - - db := mockDatabase{c.Mock} - - return db, func() { - } -} - -func (d mockDatabase) Collection(name string) Collection { - return mockCollection{d.Mock} -} - -// mockCollection acts as a mock datastore.Collection -type mockCollection struct { - *mock.Mock -} - -func (c mockCollection) BulkWrite(ctxt context.Context, operations []mongo.WriteModel, options *options.BulkWriteOptions) (*mongo.BulkWriteResult, error) { - args := c.Called(ctxt, operations, options) - return args.Get(0).(*mongo.BulkWriteResult), args.Error(1) -} - -func (c mockCollection) DeleteMany(ctxt context.Context, filter interface{}, options *options.DeleteOptions) (*mongo.DeleteResult, error) { - args := c.Called(ctxt, filter, options) - return args.Get(0).(*mongo.DeleteResult), args.Error(1) -} - -func (c mockCollection) FindOne(ctxt context.Context, filter interface{}, result interface{}, options *options.FindOneOptions) error { - args := c.Called(ctxt, filter, result, options) - return args.Error(0) -} - -func (c mockCollection) InsertOne(ctxt context.Context, document interface{}, options *options.InsertOneOptions) (*mongo.InsertOneResult, error) { - args := c.Called(ctxt, document, options) - return args.Get(0).(*mongo.InsertOneResult), args.Error(1) -} - -func (c mockCollection) UpdateOne(ctxt context.Context, filter interface{}, document interface{}, options *options.UpdateOptions) (*mongo.UpdateResult, error) { - args := c.Called(ctxt, filter, document, options) - return args.Get(0).(*mongo.UpdateResult), args.Error(1) -} - -func (c mockCollection) Find(ctxt context.Context, filter interface{}, result interface{}, options *options.FindOptions) error { - args := c.Called(ctxt, filter, result, options) - return args.Error(0) -} diff --git a/src/jetstream/plugins/monocular/chartsvc/foundationdb/handler.go b/src/jetstream/plugins/monocular/chartsvc/foundationdb/handler.go deleted file mode 100644 index d937f43c06..0000000000 --- a/src/jetstream/plugins/monocular/chartsvc/foundationdb/handler.go +++ /dev/null @@ -1,541 +0,0 @@ -/* -Copyright (c) 2019 - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package foundationdb - -import ( - "context" - "fmt" - "math" - "net/http" - "sort" - "strconv" - "strings" - - "github.com/helm/monocular/chartsvc/foundationdb/datastore" - "github.com/helm/monocular/chartsvc/models" - "github.com/helm/monocular/chartsvc/utils" - - "github.com/gorilla/mux" - "github.com/kubeapps/common/response" - log "github.com/sirupsen/logrus" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -// Params a key-value map of path params -type Params map[string]string - -// WithParams can be used to wrap handlers to take an extra arg for path params -type WithParams func(http.ResponseWriter, *http.Request, Params) - -func (h WithParams) ServeHTTP(w http.ResponseWriter, req *http.Request) { - vars := mux.Vars(req) - h(w, req, vars) -} - -const chartCollection = "charts" -const filesCollection = "files" -const repoCollection = "repos" - -// count is used to parse the result of a $count operation in the database -type count struct { - Count int -} - -var dbClient datastore.Client -var db datastore.Database -var dbCloser func() - -//var db mongo.Database -var dbName string -var pathPrefix string - -//SetPathPrefix sets the URL prefix for the ChartSVC API endpoint -func SetPathPrefix(prefix string) { - pathPrefix = prefix -} - -//InitDBConfig sets FDB Document-Layer client and DB config for the ChartSVC API handler -func InitDBConfig(client datastore.Client, name string) { - dbClient = client - db, dbCloser = dbClient.Database(name) - dbName = name -} - -// getPageNumberAndSize extracts the page number and size of a request. Default (1, 0) if not set -func getPageNumberAndSize(req *http.Request) (int, int) { - page := req.FormValue("page") - size := req.FormValue("size") - pageInt, err := strconv.ParseUint(page, 10, 64) - if err != nil { - pageInt = 1 - } - // ParseUint will return 0 if size is a not positive integer - sizeInt, _ := strconv.ParseUint(size, 10, 64) - return int(pageInt), int(sizeInt) -} - -// showDuplicates returns if a request wants to retrieve charts. Default false -func showDuplicates(req *http.Request) bool { - return len(req.FormValue("showDuplicates")) > 0 -} - -// min returns the minimum of two integers. -// We are not using math.Min since that compares float64 -// and it's unnecessarily complex. -func min(a, b int) int { - if a < b { - return a - } - return b -} - -func uniqChartList(charts []*models.Chart) []*models.Chart { - // We will keep track of unique digest:chart to avoid duplicates - chartDigests := map[string]bool{} - res := []*models.Chart{} - for _, c := range charts { - digest := c.ChartVersions[0].Digest - // Filter out the chart if we've seen the same digest before - if _, ok := chartDigests[digest]; !ok { - chartDigests[digest] = true - res = append(res, c) - } - } - return res -} - -func getPaginatedChartList(repo string, pageNumber, pageSize int, showDuplicates bool) (utils.ApiListResponse, interface{}, error) { - log.Debugf("Request for paginated chart list for repo %v", repo) - - //Find all charts for repo name and sort by chart name - collection := db.Collection(chartCollection) - filter := bson.M{} - if repo != "" { - filter = bson.M{"repo.name": repo} - } - var charts []*models.Chart - err := collection.Find(context.Background(), filter, &charts, options.Find()) - if err != nil { - log.WithError(err).Errorf( - "Error fetching charts from DB for pagination %s", - repo, - ) - return newChartListResponse([]*models.Chart{}), utils.Meta{TotalPages: 0}, err - } - var tempChartMap map[string]*models.Chart = make(map[string]*models.Chart) - - chartsToSort := make([]*models.Chart, 0, len(tempChartMap)) - - if !showDuplicates { - for _, chart := range charts { - log.Debugf("Chart digest: %v.", chart.ChartVersions[0].Digest) - tempChartMap[chart.ChartVersions[0].Digest] = chart - } - log.Debugf("Charts in map: %v", len(tempChartMap)) - //Now just get all the values from our map - for _, v := range tempChartMap { - log.Debugf("Adding chart: %v to unique chart list.", *v) - chartsToSort = append(chartsToSort, v) - } - } else { - chartsToSort = charts - } - - //Sort the list of paginated charts by name - sort.Slice(chartsToSort, func(i, j int) bool { - return chartsToSort[i].Name < chartsToSort[j].Name - }) - - sortedCharts := chartsToSort - log.Debugf("Charts in sorted list: %v", len(sortedCharts)) - log.Debugf("Page size requested: %v", pageSize) - var paginatedCharts = sortedCharts - totalPages := 1 - if pageSize != 0 { - // If a pageSize is given, returns only the the specified number of charts and - // the number of pages - cc := count{} - cc.Count = len(sortedCharts) - totalPages = int(math.Ceil(float64(cc.Count) / float64(pageSize))) - - // If the page number is out of range, return the last one - if pageNumber > totalPages { - pageNumber = totalPages - } - paginatedCharts = sortedCharts[(pageNumber-1)*pageSize : pageNumber*pageSize] - } - - log.Debugf("Returning %v charts, Done.", len(paginatedCharts)) - return newChartListResponse(paginatedCharts), utils.Meta{TotalPages: totalPages}, nil -} - -//ListRepositories returns a list of names of all stored repositories -func ListRepositories() ([]string, error) { - var repoNames []string - //Find all repo names - collection := db.Collection(repoCollection) - filter := bson.M{} - var lastChecks []*models.RepoCheck - err := collection.Find(context.Background(), filter, &lastChecks, options.Find()) - if err != nil { - log.WithError(err).Error("could not fetch repositories") - return repoNames, err - } - for i := range lastChecks { - repoNames = append(repoNames, lastChecks[i].ID) - } - return repoNames, nil -} - -// ListCharts returns a list of charts -func ListCharts(w http.ResponseWriter, req *http.Request) { - log.Debug("Request for charts..") - pageNumber, pageSize := getPageNumberAndSize(req) - cl, meta, err := getPaginatedChartList("", pageNumber, pageSize, showDuplicates(req)) - if err != nil { - log.WithError(err).Error("could not fetch charts") - response.NewErrorResponse(http.StatusInternalServerError, "could not fetch all charts").Write(w) - return - } - response.NewDataResponseWithMeta(cl, meta).Write(w) - log.Debug("Done.") -} - -// ListRepoCharts returns a list of charts in the given repo -func ListRepoCharts(w http.ResponseWriter, req *http.Request, params Params) { - log.Debug("Request for charts..") - pageNumber, pageSize := getPageNumberAndSize(req) - cl, meta, err := getPaginatedChartList(params["repo"], pageNumber, pageSize, showDuplicates(req)) - if err != nil { - log.WithError(err).Error("could not fetch charts") - response.NewErrorResponse(http.StatusInternalServerError, "could not fetch all charts").Write(w) - return - } - response.NewDataResponseWithMeta(cl, meta).Write(w) - log.Debug("Done.") -} - -// GetChart returns the chart from the given repo -func GetChart(w http.ResponseWriter, req *http.Request, params Params) { - var chart models.Chart - chartID := fmt.Sprintf("%s/%s", params["repo"], params["chartName"]) - - chartCollection := db.Collection(chartCollection) - filter := bson.M{"_id": chartID} - findResult := chartCollection.FindOne(context.Background(), filter, &chart, options.FindOne()) - if findResult == mongo.ErrNoDocuments { - log.WithError(findResult).Errorf("could not find chart with id %s", chartID) - response.NewErrorResponse(http.StatusNotFound, "could not find chart").Write(w) - return - } - - cr := newChartResponse(&chart) - response.NewDataResponse(cr).Write(w) -} - -// ListChartVersions returns a list of chart versions for the given chart -func ListChartVersions(w http.ResponseWriter, req *http.Request, params Params) { - var chart models.Chart - chartID := fmt.Sprintf("%s/%s", params["repo"], params["chartName"]) - - chartCollection := db.Collection(chartCollection) - filter := bson.M{"_id": chartID} - findResult := chartCollection.FindOne(context.Background(), filter, &chart, options.FindOne()) - if findResult == mongo.ErrNoDocuments { - log.WithError(findResult).Errorf("could not find chart with id %s", chartID) - response.NewErrorResponse(http.StatusNotFound, "could not find chart").Write(w) - return - } - - cvl := newChartVersionListResponse(&chart) - response.NewDataResponse(cvl).Write(w) -} - -// GetChartVersion returns the given chart version -func GetChartVersion(w http.ResponseWriter, req *http.Request, params Params) { - var chart models.Chart - chartID := fmt.Sprintf("%s/%s", params["repo"], params["chartName"]) - - chartCollection := db.Collection(chartCollection) - filter := bson.M{ - "_id": chartID, - "chartversions": bson.M{"$elemMatch": bson.M{"version": params["version"]}}, - } - projection := bson.M{ - "name": 1, "repo": 1, "description": 1, "home": 1, "keywords": 1, "maintainers": 1, "sources": 1, - "chartversions": 1, - } - findResult := chartCollection.FindOne(context.Background(), filter, &chart, options.FindOne().SetProjection(projection)) - if findResult == mongo.ErrNoDocuments { - log.WithError(findResult).Errorf("could not find chart with id %s", chartID) - response.NewErrorResponse(http.StatusNotFound, "could not find chart").Write(w) - return - } - - for i := range chart.ChartVersions { - if chart.ChartVersions[i].Version == params["version"] { - chart.ChartVersions = chart.ChartVersions[i : i+1] - break - } - } - // Cut the versions slice down to just one element - cvr := newChartVersionResponse(&chart, chart.ChartVersions[0]) - response.NewDataResponse(cvr).Write(w) -} - -// GetChartIcon returns the icon for a given chart -func GetChartIcon(w http.ResponseWriter, req *http.Request, params Params) { - var chart models.Chart - chartID := fmt.Sprintf("%s/%s", params["repo"], params["chartName"]) - - chartCollection := db.Collection(chartCollection) - filter := bson.M{"_id": chartID} - findResult := chartCollection.FindOne(context.Background(), filter, &chart, options.FindOne()) - if findResult == mongo.ErrNoDocuments { - log.WithError(findResult).Errorf("could not find chart with id %s", chartID) - http.NotFound(w, req) - return - } - if chart.RawIcon == nil { - http.NotFound(w, req) - return - } - - if chart.IconContentType != "" { - // Force the Content-Type header because the autogenerated type does not work for - // image/svg+xml. It is detected as plain text - w.Header().Set("Content-Type", chart.IconContentType) - } - - w.Write(chart.RawIcon) -} - -// GetChartVersionReadme returns the README for a given chart -func GetChartVersionReadme(w http.ResponseWriter, req *http.Request, params Params) { - - var files models.ChartFiles - fileID := fmt.Sprintf("%s/%s-%s", params["repo"], params["chartName"], params["version"]) - - filesCollection := db.Collection(filesCollection) - filter := bson.M{"_id": fileID} - findResult := filesCollection.FindOne(context.Background(), filter, &files, options.FindOne()) - if findResult == mongo.ErrNoDocuments { - log.WithError(findResult).Errorf("could not find files with id %s", fileID) - http.NotFound(w, req) - return - } - readme := []byte(files.Readme) - if len(readme) == 0 { - log.Errorf("could not find a README for id %s", fileID) - http.NotFound(w, req) - return - } - w.Write(readme) -} - -// GetChartVersionValues returns the values.yaml for a given chart -func GetChartVersionValues(w http.ResponseWriter, req *http.Request, params Params) { - var files models.ChartFiles - - fileID := fmt.Sprintf("%s/%s-%s", params["repo"], params["chartName"], params["version"]) - filesCollection := db.Collection(filesCollection) - filter := bson.M{"_id": fileID} - findResult := filesCollection.FindOne(context.Background(), filter, &files, options.FindOne()) - if findResult == mongo.ErrNoDocuments { - log.WithError(findResult).Errorf("could not find values.yaml with id %s", fileID) - http.NotFound(w, req) - return - } - - w.Write([]byte(files.Values)) -} - -// GetChartVersionSchema returns the values.schema.json for a given chart -func GetChartVersionSchema(w http.ResponseWriter, req *http.Request, params Params) { - - var files models.ChartFiles - - fileID := fmt.Sprintf("%s/%s-%s", params["repo"], params["chartName"], params["version"]) - filter := bson.M{"_id": fileID} - filesCollection := db.Collection(filesCollection) - findResult := filesCollection.FindOne(context.Background(), filter, &files, options.FindOne()) - if findResult == mongo.ErrNoDocuments { - log.WithError(findResult).Errorf("could not find values.schema.json with id %s", fileID) - http.NotFound(w, req) - return - } - - w.Write([]byte(files.Schema)) -} - -// ListChartsWithFilters returns the list of repos that contains the given chart and the latest version found -func ListChartsWithFilters(w http.ResponseWriter, req *http.Request, params Params) { - - var charts []*models.Chart - - chartCollection := db.Collection(chartCollection) - filter := bson.M{ - "name": params["chartName"], - "chartversions": bson.M{ - "$elemMatch": bson.M{"version": req.FormValue("version"), "appversion": req.FormValue("appversion")}, - }} - projection := bson.M{ - "name": 1, "repo": 1, - "chartversions": bson.M{"$slice": 1}, - } - err := chartCollection.Find(context.Background(), filter, &charts, options.Find().SetProjection(projection)) - if err != nil { - log.WithError(err).Errorf( - "Error finding charts with the given name %s, version %s and appversion %s", - params["chartName"], req.FormValue("version"), req.FormValue("appversion"), - ) - // continue to return empty list - } - - chartResponse := charts - if !showDuplicates(req) { - chartResponse = uniqChartList(charts) - } - - cl := newChartListResponse(chartResponse) - response.NewDataResponse(cl).Write(w) -} - -// SearchCharts returns the list of charts that matches the query param in any of these fields: -// - name -// - description -// - repository name -// - any keyword -// - any source -// - any maintainer name -func SearchCharts(w http.ResponseWriter, req *http.Request, params Params) { - - query := req.FormValue("q") - var charts []*models.Chart - - chartCollection := db.Collection(chartCollection) - filter := bson.M{ - "$or": []bson.M{ - {"name": bson.M{"$regex": query}}, - {"description": bson.M{"$regex": query}}, - {"repo.name": bson.M{"$regex": query}}, - {"keywords": bson.M{"$elemMatch": bson.M{"$regex": query}}}, - {"sources": bson.M{"$elemMatch": bson.M{"$regex": query}}}, - {"maintainers": bson.M{"$elemMatch": bson.M{"name": bson.M{"$regex": query}}}}, - }, - } - if params["repo"] != "" { - filter["repo.name"] = params["repo"] - } - err := chartCollection.Find(context.Background(), filter, &charts, options.Find()) - if err != nil { - log.WithError(err).Errorf( - "Error finding charts with the given name %s, version %s and appversion %s", - params["chartName"], req.FormValue("version"), req.FormValue("appversion"), - ) - // continue to return empty list - } - - chartResponse := charts - if !showDuplicates(req) { - chartResponse = uniqChartList(charts) - } - - cl := newChartListResponse(uniqChartList(chartResponse)) - response.NewDataResponse(cl).Write(w) -} - -func newChartResponse(c *models.Chart) *utils.ApiResponse { - - // NWM: This was: latestCV := c.ChartVersions[0] - but this includes development charts - // FIX: This ignores development versions - latestCV := findFirstNonDevelopmentVersion(c) - - return &utils.ApiResponse{ - Type: "chart", - ID: c.ID, - Attributes: chartAttributes(*c), - Links: utils.SelfLink{Self: pathPrefix + "/charts/" + c.ID}, - Relationships: utils.RelMap{ - "latestChartVersion": utils.Rel{ - Data: chartVersionAttributes(c.ID, latestCV), - Links: utils.SelfLink{Self: pathPrefix + "/charts/" + c.ID + "/versions/" + latestCV.Version}, - }, - }, - } -} - -func findFirstNonDevelopmentVersion(c *models.Chart) models.ChartVersion { - for _, chartVersion := range c.ChartVersions { - if strings.Index(chartVersion.Version, "-") == -1 { - return chartVersion - } - } - return c.ChartVersions[0] -} - -func newChartListResponse(charts []*models.Chart) utils.ApiListResponse { - cl := utils.ApiListResponse{} - for _, c := range charts { - cl = append(cl, newChartResponse(c)) - } - return cl -} - -func chartVersionAttributes(cid string, cv models.ChartVersion) models.ChartVersion { - cv.Readme = pathPrefix + "/assets/" + cid + "/versions/" + cv.Version + "/README.md" - cv.Values = pathPrefix + "/assets/" + cid + "/versions/" + cv.Version + "/values.yaml" - cv.Schema = pathPrefix + "/assets/" + cid + "/versions/" + cv.Version + "/values.schema.json" - - return cv -} - -func chartAttributes(c models.Chart) models.Chart { - if c.RawIcon != nil { - c.Icon = pathPrefix + "/assets/" + c.ID + "/logo" - } else { - // If the icon wasn't processed, it is either not set or invalid - c.Icon = "" - } - return c -} - -func newChartVersionResponse(c *models.Chart, cv models.ChartVersion) *utils.ApiResponse { - return &utils.ApiResponse{ - Type: "chartVersion", - ID: fmt.Sprintf("%s-%s", c.ID, cv.Version), - Attributes: chartVersionAttributes(c.ID, cv), - Links: utils.SelfLink{Self: pathPrefix + "/charts/" + c.ID + "/versions/" + cv.Version}, - Relationships: utils.RelMap{ - "chart": utils.Rel{ - Data: chartAttributes(*c), - Links: utils.SelfLink{Self: pathPrefix + "/charts/" + c.ID}, - }, - }, - } -} - -func newChartVersionListResponse(c *models.Chart) utils.ApiListResponse { - var cvl utils.ApiListResponse - for _, cv := range c.ChartVersions { - cvl = append(cvl, newChartVersionResponse(c, cv)) - } - - return cvl -} diff --git a/src/jetstream/plugins/monocular/chartsvc/foundationdb/handler_test.go b/src/jetstream/plugins/monocular/chartsvc/foundationdb/handler_test.go deleted file mode 100644 index 2496a16069..0000000000 --- a/src/jetstream/plugins/monocular/chartsvc/foundationdb/handler_test.go +++ /dev/null @@ -1,867 +0,0 @@ -/* -Copyright (c) 2019 - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package foundationdb - -import ( - "bytes" - "encoding/json" - "image/color" - "net/http" - "net/http/httptest" - "strconv" - "strings" - "testing" - - "github.com/helm/monocular/chartsvc/foundationdb/datastore" - "github.com/helm/monocular/chartsvc/models" - "github.com/helm/monocular/chartsvc/utils" - - "github.com/disintegration/imaging" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -var cc count - -const testChartReadme = "# Quickstart\n\n```bash\nhelm install my-repo/my-chart\n```" -const testChartValues = "image:\n registry: docker.io\n repository: my-repo/my-chart\n tag: 0.1.0" - -const fdbURL = "mongodb://fdb-service/27016" -const fDB = "charts" - -func iconBytes() []byte { - var b bytes.Buffer - img := imaging.New(1, 1, color.White) - imaging.Encode(&b, img, imaging.PNG) - return b.Bytes() -} - -func Test_chartAttributes(t *testing.T) { - tests := []struct { - name string - chart models.Chart - }{ - {"chart has no icon", models.Chart{ - ID: "stable/wordpress", - }}, - {"chart has a icon", models.Chart{ - ID: "repo/mychart", RawIcon: iconBytes(), IconContentType: "image/svg", - }}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := chartAttributes(tt.chart) - assert.Equal(t, tt.chart.ID, c.ID) - assert.Equal(t, tt.chart.RawIcon, c.RawIcon) - if len(tt.chart.RawIcon) == 0 { - assert.Equal(t, len(c.Icon), 0, "icon url should be undefined") - } else { - assert.Equal(t, c.Icon, pathPrefix+"/assets/"+tt.chart.ID+"/logo", "the icon url should be the same") - assert.Equal(t, c.IconContentType, tt.chart.IconContentType, "the icon content type should be the same") - } - }) - } -} - -func Test_chartVersionAttributes(t *testing.T) { - tests := []struct { - name string - chart models.Chart - }{ - {"my-chart", models.Chart{ - ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}}, - }}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cv := chartVersionAttributes(tt.chart.ID, tt.chart.ChartVersions[0]) - assert.Equal(t, cv.Version, tt.chart.ChartVersions[0].Version, "version string should be the same") - assert.Equal(t, cv.Readme, pathPrefix+"/assets/"+tt.chart.ID+"/versions/"+tt.chart.ChartVersions[0].Version+"/README.md", "README.md resource path should be the same") - assert.Equal(t, cv.Values, pathPrefix+"/assets/"+tt.chart.ID+"/versions/"+tt.chart.ChartVersions[0].Version+"/values.yaml", "values.yaml resource path should be the same") - }) - } -} - -func Test_newChartResponse(t *testing.T) { - tests := []struct { - name string - chart models.Chart - }{ - {"chart has only one version", models.Chart{ - ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "1.2.3"}}}, - }, - {"chart has many versions", models.Chart{ - ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.2"}, {Version: "0.1.0"}}, - }}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cResponse := newChartResponse(&tt.chart) - assert.Equal(t, cResponse.Type, "chart", "response type is chart") - assert.Equal(t, cResponse.ID, tt.chart.ID, "chart ID should be the same") - assert.Equal(t, cResponse.Relationships["latestChartVersion"].Data.(models.ChartVersion).Version, tt.chart.ChartVersions[0].Version, "latestChartVersion should match version at index 0") - assert.Equal(t, cResponse.Links.(utils.SelfLink).Self, pathPrefix+"/charts/"+tt.chart.ID, "self link should be the same") - assert.Equal(t, len(cResponse.Attributes.(models.Chart).ChartVersions), len(tt.chart.ChartVersions), "number of chart versions in the response should be the same") - }) - } -} - -func Test_newChartListResponse(t *testing.T) { - tests := []struct { - name string - input []*models.Chart - result []*models.Chart - }{ - {"no charts", []*models.Chart{}, []*models.Chart{}}, - {"has one chart", []*models.Chart{ - {ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}}, - }, []*models.Chart{ - {ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}}, - }}, - {"has two charts", []*models.Chart{ - {ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}}, - {ID: "stable/wordpress", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "1234"}, {Version: "1.2.2", Digest: "12345"}}}, - }, []*models.Chart{ - {ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}}, - {ID: "stable/wordpress", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "1234"}, {Version: "1.2.2", Digest: "12345"}}}, - }}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - clResponse := newChartListResponse(tt.input) - assert.Equal(t, len(clResponse), len(tt.result), "number of charts in response should be the same") - for i := range tt.result { - assert.Equal(t, clResponse[i].Type, "chart", "response type is chart") - assert.Equal(t, clResponse[i].ID, tt.result[i].ID, "chart ID should be the same") - assert.Equal(t, clResponse[i].Relationships["latestChartVersion"].Data.(models.ChartVersion).Version, tt.result[i].ChartVersions[0].Version, "latestChartVersion should match version at index 0") - assert.Equal(t, clResponse[i].Links.(utils.SelfLink).Self, pathPrefix+"/charts/"+tt.result[i].ID, "self link should be the same") - assert.Equal(t, len(clResponse[i].Attributes.(models.Chart).ChartVersions), len(tt.result[i].ChartVersions), "number of chart versions in the response should be the same") - } - }) - } -} - -func Test_newChartVersionResponse(t *testing.T) { - tests := []struct { - name string - chart models.Chart - }{ - {"my-chart", models.Chart{ - ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}, {Version: "0.2.3"}}, - }}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - for i := range tt.chart.ChartVersions { - cvResponse := newChartVersionResponse(&tt.chart, tt.chart.ChartVersions[i]) - assert.Equal(t, cvResponse.Type, "chartVersion", "response type is chartVersion") - assert.Equal(t, cvResponse.ID, tt.chart.ID+"-"+tt.chart.ChartVersions[i].Version, "reponse id should have chart version suffix") - assert.Equal(t, cvResponse.Links.(interface{}).(utils.SelfLink).Self, pathPrefix+"/charts/"+tt.chart.ID+"/versions/"+tt.chart.ChartVersions[i].Version, "self link should be the same") - assert.Equal(t, cvResponse.Attributes.(models.ChartVersion).Version, tt.chart.ChartVersions[i].Version, "chart version in the response should be the same") - assert.Equal(t, cvResponse.Relationships["chart"].Data.(interface{}).(models.Chart), tt.chart, "chart in relatioship matches") - } - }) - } -} - -func Test_newChartVersionListResponse(t *testing.T) { - tests := []struct { - name string - chart models.Chart - }{ - {"chart has no versions", models.Chart{ - ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{}, - }}, - {"chart has one version", models.Chart{ - ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1"}}, - }}, - {"chart has many versions", models.Chart{ - ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1"}, {Version: "0.0.2"}}, - }}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cvListResponse := newChartVersionListResponse(&tt.chart) - assert.Equal(t, len(cvListResponse), len(tt.chart.ChartVersions), "number of chart versions in response should be the same") - for i := range tt.chart.ChartVersions { - assert.Equal(t, cvListResponse[i].Type, "chartVersion", "response type is chartVersion") - assert.Equal(t, cvListResponse[i].ID, tt.chart.ID+"-"+tt.chart.ChartVersions[i].Version, "reponse id should have chart version suffix") - assert.Equal(t, cvListResponse[i].Links.(interface{}).(utils.SelfLink).Self, pathPrefix+"/charts/"+tt.chart.ID+"/versions/"+tt.chart.ChartVersions[i].Version, "self link should be the same") - assert.Equal(t, cvListResponse[i].Attributes.(models.ChartVersion).Version, tt.chart.ChartVersions[i].Version, "chart version in the response should be the same") - } - }) - } -} - -func Test_listCharts(t *testing.T) { - pageSize := 2 - tests := []struct { - name string - query string - dbQueryResult []*models.Chart - chartListResult []*models.Chart - meta utils.Meta - }{ - {"no charts", "", []*models.Chart{}, []*models.Chart{}, utils.Meta{TotalPages: 1}}, - {"one chart", "", []*models.Chart{ - {ID: "my-repo/my-chart", Name: "my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}}, - }, []*models.Chart{ - {ID: "my-repo/my-chart", Name: "my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}}, - }, utils.Meta{TotalPages: 1}}, - {"two charts", "", []*models.Chart{ - {ID: "my-repo/my-chart", Name: "my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}}, - {ID: "stable/dokuwiki", Name: "dokuwiki", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "1234"}, {Version: "1.2.2", Digest: "12345"}}}, - }, []*models.Chart{ - {ID: "stable/dokuwiki", Name: "dokuwiki", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "1234"}, {Version: "1.2.2", Digest: "12345"}}}, - {ID: "my-repo/my-chart", Name: "my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}}, - }, utils.Meta{TotalPages: 1}}, - // Pagination tests - {"four charts with pagination", "?size=" + strconv.Itoa(pageSize), []*models.Chart{ - {ID: "my-repo/my-chart", Name: "my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}}, - {ID: "stable/dokuwiki", Name: "dokuwiki", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "1234"}}}, - {ID: "stable/drupal", Name: "drupal", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "12345"}}}, - {ID: "stable/wordpress", Name: "wordpress", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "123456"}}}, - }, []*models.Chart{ - {ID: "stable/dokuwiki", Name: "dokuwiki", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "1234"}}}, - {ID: "stable/drupal", Name: "drupal", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "12345"}}}, - }, utils.Meta{TotalPages: 2}}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var m mock.Mock - dbClient = datastore.NewMockClient(&m) - db, _ = dbClient.Database("test") - var chartsList []*models.Chart - - m.On("Find", mock.Anything, mock.Anything, &chartsList, mock.Anything).Return(nil).Run(func(args mock.Arguments) { - *args.Get(2).(*[]*models.Chart) = tt.dbQueryResult - }) - - w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "/charts"+tt.query, nil) - ListCharts(w, req) - - m.AssertExpectations(t) - assert.Equal(t, http.StatusOK, w.Code) - - var b utils.BodyAPIListResponse - json.NewDecoder(w.Body).Decode(&b) - if b.Data == nil { - t.Fatal("chart list shouldn't be null") - } - data := *b.Data - - assert.Len(t, data, len(tt.chartListResult)) - - for i, resp := range data { - assert.Equal(t, resp.ID, tt.chartListResult[i].ID, "chart id in the response should be the same") - assert.Equal(t, resp.Type, "chart", "response type is chart") - assert.Equal(t, resp.Links.(map[string]interface{})["self"], pathPrefix+"/charts/"+tt.chartListResult[i].ID, "self link should be the same") - assert.Equal(t, resp.Relationships["latestChartVersion"].Data.(map[string]interface{})["version"], tt.chartListResult[i].ChartVersions[0].Version, "latestChartVersion should match version at index 0") - } - assert.Equal(t, b.Meta, tt.meta, "response meta should be the same") - }) - } -} - -func Test_listRepoCharts(t *testing.T) { - pageSize := 2 - tests := []struct { - name string - repo string - query string - dbQueryResult []*models.Chart - chartListResult []*models.Chart - meta utils.Meta - }{ - {"repo has no charts", "my-repo", "", []*models.Chart{}, []*models.Chart{}, utils.Meta{TotalPages: 1}}, - {"repo has one chart", "my-repo", "", []*models.Chart{ - {ID: "my-repo/my-chart", Name: "my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}}, - }, []*models.Chart{ - {ID: "my-repo/my-chart", Name: "my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}}, - }, utils.Meta{TotalPages: 1}}, - {"repo has many charts", "my-repo", "", []*models.Chart{ - {ID: "my-repo/my-chart", Name: "my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}}, - {ID: "my-repo/dokuwiki", Name: "dokuwiki", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "1234"}, {Version: "1.2.2", Digest: "12345"}}}, - }, []*models.Chart{ - {ID: "my-repo/dokuwiki", Name: "dokuwiki", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "1234"}, {Version: "1.2.2", Digest: "12345"}}}, - {ID: "my-repo/my-chart", Name: "my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}}, - }, utils.Meta{TotalPages: 1}}, - {"repo has many charts with pagination", "my-repo", "?size=" + strconv.Itoa(pageSize), []*models.Chart{ - {ID: "my-repo/my-chart3", Name: "my-chart3", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}}, - {ID: "my-repo/my-chart1", Name: "my-chart1", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "1234"}}}, - {ID: "my-repo/my-chart2", Name: "my-chart2", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "12345"}}}, - }, []*models.Chart{ - {ID: "my-repo/my-chart1", Name: "my-chart1", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "1234"}}}, - {ID: "my-repo/my-chart2", Name: "my-chart2", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "12345"}}}, - }, utils.Meta{TotalPages: 2}}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var m mock.Mock - dbClient = datastore.NewMockClient(&m) - db, _ = dbClient.Database("test") - var chartsList []*models.Chart - m.On("Find", mock.Anything, bson.M{"repo.name": "my-repo"}, &chartsList, mock.Anything).Return(nil).Run(func(args mock.Arguments) { - *args.Get(2).(*[]*models.Chart) = tt.dbQueryResult - }) - - w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "/charts/"+tt.repo+tt.query, nil) - params := Params{ - "repo": "my-repo", - } - - ListRepoCharts(w, req, params) - - m.AssertExpectations(t) - assert.Equal(t, http.StatusOK, w.Code) - - var b utils.BodyAPIListResponse - json.NewDecoder(w.Body).Decode(&b) - data := *b.Data - assert.Len(t, data, len(tt.chartListResult)) - for i, resp := range data { - assert.Equal(t, resp.ID, tt.chartListResult[i].ID, "chart id in the response should be the same") - assert.Equal(t, resp.Type, "chart", "response type is chart") - assert.Equal(t, resp.Relationships["latestChartVersion"].Data.(map[string]interface{})["version"], tt.chartListResult[i].ChartVersions[0].Version, "latestChartVersion should match version at index 0") - } - assert.Equal(t, b.Meta, tt.meta, "response meta should be the same") - }) - } -} - -func Test_getChart(t *testing.T) { - tests := []struct { - name string - err error - chart models.Chart - wantCode int - }{ - { - "chart does not exist", - mongo.ErrNoDocuments, - models.Chart{ID: "my-repo/my-chart"}, - http.StatusNotFound, - }, - { - "chart exists", - nil, - models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}}}, - http.StatusOK, - }, - { - "chart has multiple versions", - nil, - models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}, {Version: "0.0.1"}}}, - http.StatusOK, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var m mock.Mock - dbClient = datastore.NewMockClient(&m) - db, _ = dbClient.Database("test") - - if tt.err != nil { - m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tt.err) - } else { - m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) { - *args.Get(2).(*models.Chart) = tt.chart - }) - } - - w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "/charts/"+tt.chart.ID, nil) - parts := strings.Split(tt.chart.ID, "/") - params := Params{ - "repo": parts[0], - "chartName": parts[1], - } - - GetChart(w, req, params) - - m.AssertExpectations(t) - assert.Equal(t, tt.wantCode, w.Code) - if tt.wantCode == http.StatusOK { - var b utils.BodyAPIResponse - json.NewDecoder(w.Body).Decode(&b) - assert.Equal(t, b.Data.ID, tt.chart.ID, "chart id in the response should be the same") - assert.Equal(t, b.Data.Type, "chart", "response type is chart") - assert.Equal(t, b.Data.Links.(map[string]interface{})["self"], pathPrefix+"/charts/"+tt.chart.ID, "self link should be the same") - assert.Equal(t, b.Data.Relationships["latestChartVersion"].Data.(map[string]interface{})["version"], tt.chart.ChartVersions[0].Version, "latestChartVersion should match version at index 0") - } - }) - } -} - -func Test_listChartVersions(t *testing.T) { - tests := []struct { - name string - err error - chart models.Chart - wantCode int - }{ - { - "chart does not exist", - mongo.ErrNoDocuments, - models.Chart{ID: "my-repo/my-chart"}, - http.StatusNotFound, - }, - { - "chart exists", - nil, - models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}}}, - http.StatusOK, - }, - { - "chart has multiple versions", - nil, - models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}, {Version: "0.0.1"}}}, - http.StatusOK, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var m mock.Mock - dbClient = datastore.NewMockClient(&m) - db, _ = dbClient.Database("test") - - if tt.err != nil { - m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tt.err) - } else { - m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) { - *args.Get(2).(*models.Chart) = tt.chart - }) - } - - w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "/charts/"+tt.chart.ID+"/versions", nil) - parts := strings.Split(tt.chart.ID, "/") - params := Params{ - "repo": parts[0], - "chartName": parts[1], - } - - ListChartVersions(w, req, params) - - m.AssertExpectations(t) - assert.Equal(t, tt.wantCode, w.Code) - if tt.wantCode == http.StatusOK { - var b utils.BodyAPIListResponse - json.NewDecoder(w.Body).Decode(&b) - data := *b.Data - for i, resp := range data { - assert.Equal(t, resp.ID, tt.chart.ID+"-"+tt.chart.ChartVersions[i].Version, "chart id in the response should be the same") - assert.Equal(t, resp.Type, "chartVersion", "response type is chartVersion") - assert.Equal(t, resp.Attributes.(map[string]interface{})["version"], tt.chart.ChartVersions[i].Version, "chart version should match") - } - } - }) - } -} - -func Test_getChartVersion(t *testing.T) { - tests := []struct { - name string - err error - chart models.Chart - wantCode int - }{ - { - "chart does not exist", - mongo.ErrNoDocuments, - models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}}}, - http.StatusNotFound, - }, - { - "chart exists", - nil, - models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}}}, - http.StatusOK, - }, - { - "chart has multiple versions", - nil, - models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}, {Version: "0.0.1"}}}, - http.StatusOK, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var m mock.Mock - dbClient = datastore.NewMockClient(&m) - db, _ = dbClient.Database("test") - - if tt.err != nil { - m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tt.err) - } else { - m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) { - *args.Get(2).(*models.Chart) = tt.chart - }) - } - - w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "/charts/"+tt.chart.ID+"/versions/"+tt.chart.ChartVersions[0].Version, nil) - parts := strings.Split(tt.chart.ID, "/") - params := Params{ - "repo": parts[0], - "chartName": parts[1], - "version": tt.chart.ChartVersions[0].Version, - } - - GetChartVersion(w, req, params) - - m.AssertExpectations(t) - assert.Equal(t, tt.wantCode, w.Code) - if tt.wantCode == http.StatusOK { - var b utils.BodyAPIResponse - json.NewDecoder(w.Body).Decode(&b) - assert.Equal(t, b.Data.ID, tt.chart.ID+"-"+tt.chart.ChartVersions[0].Version, "chart id in the response should be the same") - assert.Equal(t, b.Data.Type, "chartVersion", "response type is chartVersion") - assert.Equal(t, b.Data.Attributes.(map[string]interface{})["version"], tt.chart.ChartVersions[0].Version, "chart version should match") - } - }) - } -} - -func Test_getChartIcon(t *testing.T) { - tests := []struct { - name string - err error - chart models.Chart - wantCode int - }{ - { - "chart does not exist", - mongo.ErrNoDocuments, - models.Chart{ID: "my-repo/my-chart"}, - http.StatusNotFound, - }, - { - "chart has icon", - nil, - models.Chart{ID: "my-repo/my-chart", RawIcon: iconBytes(), IconContentType: "image/png"}, - http.StatusOK, - }, - { - "chart does not have a icon", - nil, - models.Chart{ID: "my-repo/my-chart"}, - http.StatusNotFound, - }, - { - "chart has icon with custom type", - nil, - models.Chart{ID: "my-repo/my-chart", RawIcon: iconBytes(), IconContentType: "image/svg"}, - http.StatusOK, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var m mock.Mock - dbClient = datastore.NewMockClient(&m) - db, _ = dbClient.Database("test") - - if tt.err != nil { - m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tt.err) - } else { - m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) { - *args.Get(2).(*models.Chart) = tt.chart - }) - } - - w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "/assets/"+tt.chart.ID+"/logo", nil) - parts := strings.Split(tt.chart.ID, "/") - params := Params{ - "repo": parts[0], - "chartName": parts[1], - } - - GetChartIcon(w, req, params) - - m.AssertExpectations(t) - assert.Equal(t, tt.wantCode, w.Code, "http status code should match") - if tt.wantCode == http.StatusOK { - assert.Equal(t, w.Body.Bytes(), tt.chart.RawIcon, "raw icon data should match") - assert.Equal(t, w.Header().Get("Content-Type"), tt.chart.IconContentType, "icon content type should match") - } - }) - } -} - -func Test_getChartVersionReadme(t *testing.T) { - tests := []struct { - name string - version string - err error - files models.ChartFiles - wantCode int - }{ - { - "chart does not exist", - "0.1.0", - mongo.ErrNoDocuments, - models.ChartFiles{ID: "my-repo/my-chart"}, - http.StatusNotFound, - }, - { - "chart exists", - "1.2.3", - nil, - models.ChartFiles{ID: "my-repo/my-chart", Readme: testChartReadme}, - http.StatusOK, - }, - { - "chart does not have a readme", - "1.1.1", - nil, - models.ChartFiles{ID: "my-repo/my-chart"}, - http.StatusNotFound, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var m mock.Mock - dbClient = datastore.NewMockClient(&m) - db, _ = dbClient.Database("test") - - if tt.err != nil { - m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tt.err) - } else { - m.On("FindOne", mock.Anything, mock.Anything, &models.ChartFiles{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) { - *args.Get(2).(*models.ChartFiles) = tt.files - }) - } - - w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "/assets/"+tt.files.ID+"/versions/"+tt.version+"/README.md", nil) - parts := strings.Split(tt.files.ID, "/") - params := Params{ - "repo": parts[0], - "chartName": parts[1], - "version": "0.1.0", - } - - GetChartVersionReadme(w, req, params) - - m.AssertExpectations(t) - assert.Equal(t, tt.wantCode, w.Code, "http status code should match") - if tt.wantCode == http.StatusOK { - assert.Equal(t, string(w.Body.Bytes()), tt.files.Readme, "content of the readme should match") - } - }) - } -} - -func Test_getChartVersionValues(t *testing.T) { - tests := []struct { - name string - version string - err error - files models.ChartFiles - wantCode int - }{ - { - "chart does not exist", - "0.1.0", - mongo.ErrNoDocuments, - models.ChartFiles{ID: "my-repo/my-chart"}, - http.StatusNotFound, - }, - { - "chart exists", - "3.2.1", - nil, - models.ChartFiles{ID: "my-repo/my-chart", Values: testChartValues}, - http.StatusOK, - }, - { - "chart does not have values.yaml", - "2.2.2", - nil, - models.ChartFiles{ID: "my-repo/my-chart"}, - http.StatusOK, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var m mock.Mock - dbClient = datastore.NewMockClient(&m) - db, _ = dbClient.Database("test") - - if tt.err != nil { - m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tt.err) - } else { - m.On("FindOne", mock.Anything, mock.Anything, &models.ChartFiles{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) { - *args.Get(2).(*models.ChartFiles) = tt.files - }) - } - - w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "/assets/"+tt.files.ID+"/versions/"+tt.version+"/values.yaml", nil) - parts := strings.Split(tt.files.ID, "/") - params := Params{ - "repo": parts[0], - "chartName": parts[1], - "version": "0.1.0", - } - - GetChartVersionValues(w, req, params) - - m.AssertExpectations(t) - assert.Equal(t, tt.wantCode, w.Code, "http status code should match") - if tt.wantCode == http.StatusOK { - assert.Equal(t, string(w.Body.Bytes()), tt.files.Values, "content of values.yaml should match") - } - }) - } -} - -func Test_findLatestChart(t *testing.T) { - t.Run("returns mocked chart", func(t *testing.T) { - chart := &models.Chart{ - Name: "foo", - ID: "foo", - Repo: models.Repo{Name: "bar"}, - ChartVersions: []models.ChartVersion{ - models.ChartVersion{Version: "1.0.0", AppVersion: "0.1.0"}, - models.ChartVersion{Version: "0.0.1", AppVersion: "0.1.0"}, - }, - } - charts := []*models.Chart{chart} - reqVersion := "1.0.0" - reqAppVersion := "0.1.0" - - var m mock.Mock - dbClient = datastore.NewMockClient(&m) - db, _ = dbClient.Database("test") - var chartsList []*models.Chart - m.On("Find", mock.Anything, mock.Anything, &chartsList, mock.Anything).Run(func(args mock.Arguments) { - *args.Get(2).(*[]*models.Chart) = charts - }).Return(nil) - - w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "/charts?name="+chart.Name+"&version="+reqVersion+"&appversion="+reqAppVersion, nil) - params := Params{ - "name": chart.Name, - "version": reqVersion, - "appversion": reqAppVersion, - } - - ListChartsWithFilters(w, req, params) - - var b utils.BodyAPIListResponse - json.NewDecoder(w.Body).Decode(&b) - if b.Data == nil { - t.Fatal("chart list shouldn't be null") - } - data := *b.Data - - if data[0].ID != chart.ID { - t.Errorf("Expecting %v, received %v", chart, data[0].ID) - } - }) - t.Run("ignores duplicated chart", func(t *testing.T) { - charts := []*models.Chart{ - {Name: "foo", ID: "stable/foo", Repo: models.Repo{Name: "bar"}, ChartVersions: []models.ChartVersion{models.ChartVersion{Version: "1.0.0", AppVersion: "0.1.0", Digest: "123"}}}, - {Name: "foo", ID: "bitnami/foo", Repo: models.Repo{Name: "bar"}, ChartVersions: []models.ChartVersion{models.ChartVersion{Version: "1.0.0", AppVersion: "0.1.0", Digest: "123"}}}, - } - reqVersion := "1.0.0" - reqAppVersion := "0.1.0" - - var m mock.Mock - dbClient = datastore.NewMockClient(&m) - db, _ = dbClient.Database("test") - var chartsList []*models.Chart - m.On("Find", mock.Anything, mock.Anything, &chartsList, mock.Anything).Run(func(args mock.Arguments) { - *args.Get(2).(*[]*models.Chart) = charts - }).Return(nil) - - w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "/charts?name="+charts[0].Name+"&version="+reqVersion+"&appversion="+reqAppVersion, nil) - params := Params{ - "name": charts[0].Name, - "version": reqVersion, - "appversion": reqAppVersion, - } - - ListChartsWithFilters(w, req, params) - - var b utils.BodyAPIListResponse - json.NewDecoder(w.Body).Decode(&b) - if b.Data == nil { - t.Fatal("chart list shouldn't be null") - } - data := *b.Data - - assert.Equal(t, len(data), 1, "it should return a single chart") - if data[0].ID != charts[0].ID { - t.Errorf("Expecting %v, received %v", charts[0], data[0].ID) - } - }) - t.Run("includes duplicated charts when showDuplicates param set", func(t *testing.T) { - charts := []*models.Chart{ - {Name: "foo", ID: "stable/foo", Repo: models.Repo{Name: "bar"}, ChartVersions: []models.ChartVersion{models.ChartVersion{Version: "1.0.0", AppVersion: "0.1.0", Digest: "123"}}}, - {Name: "foo", ID: "bitnami/foo", Repo: models.Repo{Name: "bar"}, ChartVersions: []models.ChartVersion{models.ChartVersion{Version: "1.0.0", AppVersion: "0.1.0", Digest: "123"}}}, - } - reqVersion := "1.0.0" - reqAppVersion := "0.1.0" - - var m mock.Mock - dbClient = datastore.NewMockClient(&m) - db, _ = dbClient.Database("test") - var chartsList []*models.Chart - m.On("Find", mock.Anything, mock.Anything, &chartsList, mock.Anything).Run(func(args mock.Arguments) { - *args.Get(2).(*[]*models.Chart) = charts - }).Return(nil) - - w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "/charts?showDuplicates=true&name="+charts[0].Name+"&version="+reqVersion+"&appversion="+reqAppVersion, nil) - params := Params{ - "name": charts[0].Name, - "version": reqVersion, - "appversion": reqAppVersion, - } - - ListChartsWithFilters(w, req, params) - - var b utils.BodyAPIListResponse - json.NewDecoder(w.Body).Decode(&b) - if b.Data == nil { - t.Fatal("chart list shouldn't be null") - } - data := *b.Data - - assert.Equal(t, 2, len(data), "it should return both charts") - }) -} diff --git a/src/jetstream/plugins/monocular/chartsvc/go.mod b/src/jetstream/plugins/monocular/chartsvc/go.mod deleted file mode 100644 index 3fea97e947..0000000000 --- a/src/jetstream/plugins/monocular/chartsvc/go.mod +++ /dev/null @@ -1,31 +0,0 @@ -module github.com/helm/monocular/chartsvc - -go 1.12 - -require ( - github.com/disintegration/imaging v1.6.2 - github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 - github.com/golang/snappy v0.0.1 // indirect - github.com/gorilla/mux v1.7.3 - github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40 - github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a - github.com/labstack/echo v3.3.10+incompatible // indirect - github.com/labstack/gommon v0.3.0 // indirect - github.com/prometheus/client_golang v1.2.1 // indirect - github.com/sirupsen/logrus v1.4.2 - github.com/stretchr/testify v1.4.0 - github.com/unrolled/render v1.0.1 // indirect - github.com/urfave/negroni v1.0.0 - github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect - github.com/xdg/stringprep v1.0.0 // indirect - go.mongodb.org/mongo-driver v1.1.3 - golang.org/x/crypto v0.0.0-20191205161847-0a08dada0ff9 // indirect - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect - k8s.io/helm v2.16.1+incompatible -) - -replace ( - github.com/helm/monocular/chartsvc/foundationdb => ./foundationdb - github.com/helm/monocular/chartsvc/models => ./models - github.com/helm/monocular/chartsvc/utils => ./utils -) diff --git a/src/jetstream/plugins/monocular/chartsvc/go.sum b/src/jetstream/plugins/monocular/chartsvc/go.sum deleted file mode 100644 index dbf10aba67..0000000000 --- a/src/jetstream/plugins/monocular/chartsvc/go.sum +++ /dev/null @@ -1,139 +0,0 @@ -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA= -github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= -github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= -github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40 h1:GT4RsKmHh1uZyhmTkWJTDALRjSHYQp6FRKrotf0zhAs= -github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40/go.mod h1:NtmN9h8vrTveVQRLHcX2HQ5wIPBDCsZ351TGbZWgg38= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a h1:VqeX/fehAB6FtBox0TVYcjOMXGE56INQIfbXegditX4= -github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a/go.mod h1:TsgmjeDpbftqhwPKInJ3v+l+xbHs4goiB6DFb2WqY9c= -github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= -github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= -github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= -github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI= -github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= -github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/unrolled/render v1.0.1 h1:VDDnQQVfBMsOsp3VaCJszSO0nkBIVEYoPWeRThk9spY= -github.com/unrolled/render v1.0.1/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0obPf6NBdM= -github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= -github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= -github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= -github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= -github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= -go.mongodb.org/mongo-driver v1.1.3 h1:++7u8r9adKhGR+I79NfEtYrk2ktjenErXM99PSufIoI= -go.mongodb.org/mongo-driver v1.1.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.2.1 h1:ANAlYXXM5XmOdW/Nc38jOr+wS5nlk7YihT24U1imiWM= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191205161847-0a08dada0ff9 h1:abxekknhS/Drh3uoQDk5Hc7BgeiyI39Crb7vhf/1j5s= -golang.org/x/crypto v0.0.0-20191205161847-0a08dada0ff9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= -golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= -golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -k8s.io/helm v2.16.1+incompatible h1:L+k810plJlaGWEw1EszeT4deK8XVaKxac1oGcuB+WDc= -k8s.io/helm v2.16.1+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= diff --git a/src/jetstream/plugins/monocular/chartsvc/main.go b/src/jetstream/plugins/monocular/chartsvc/main.go deleted file mode 100644 index 05226e6bc5..0000000000 --- a/src/jetstream/plugins/monocular/chartsvc/main.go +++ /dev/null @@ -1,168 +0,0 @@ -/* -Copyright (c) 2017 The Helm Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartsvc - -import ( - "context" - "crypto/tls" - "crypto/x509" - "flag" - "io/ioutil" - "net/http" - "os" - - "github.com/gorilla/mux" - "github.com/heptiolabs/healthcheck" - mongoDatastore "github.com/kubeapps/common/datastore" - log "github.com/sirupsen/logrus" - "github.com/urfave/negroni" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" - - fdb "github.com/helm/monocular/chartsvc/foundationdb" - fdbDatastore "github.com/helm/monocular/chartsvc/foundationdb/datastore" -) - -const pathPrefix = "/v1" - -var client *mongo.Client -var dbSession mongoDatastore.Session - -func SetupRoutes() http.Handler { - r := mux.NewRouter() - - // Healthcheck - health := healthcheck.NewHandler() - r.Handle("/live", health) - r.Handle("/ready", health) - - // Routes - apiv1 := r.PathPrefix(pathPrefix).Subrouter() - apiv1.Methods("GET").Path("/charts").Queries("name", "{chartName}", "version", "{version}", "appversion", "{appversion}").Handler(fdb.WithParams(fdb.ListChartsWithFilters)) - apiv1.Methods("GET").Path("/charts").Queries("name", "{chartName}", "version", "{version}", "appversion", "{appversion}", "showDuplicates", "{showDuplicates}").Handler(fdb.WithParams(fdb.ListChartsWithFilters)) - apiv1.Methods("GET").Path("/charts").HandlerFunc(fdb.ListCharts) - apiv1.Methods("GET").Path("/charts").Queries("showDuplicates", "{showDuplicates}").HandlerFunc(fdb.ListCharts) - apiv1.Methods("GET").Path("/charts/search").Queries("q", "{query}").Handler(fdb.WithParams(fdb.SearchCharts)) - apiv1.Methods("GET").Path("/charts/search").Queries("q", "{query}", "showDuplicates", "{showDuplicates}").Handler(fdb.WithParams(fdb.SearchCharts)) - apiv1.Methods("GET").Path("/charts/{repo}").Handler(fdb.WithParams(fdb.ListRepoCharts)) - apiv1.Methods("GET").Path("/charts/{repo}/search").Queries("q", "{query}").Handler(fdb.WithParams(fdb.SearchCharts)) - apiv1.Methods("GET").Path("/charts/{repo}/search").Queries("q", "{query}", "showDuplicates", "{showDuplicates}").Handler(fdb.WithParams(fdb.SearchCharts)) - apiv1.Methods("GET").Path("/charts/{repo}/{chartName}").Handler(fdb.WithParams(fdb.GetChart)) - apiv1.Methods("GET").Path("/charts/{repo}/{chartName}/versions").Handler(fdb.WithParams(fdb.ListChartVersions)) - apiv1.Methods("GET").Path("/charts/{repo}/{chartName}/versions/{version}").Handler(fdb.WithParams(fdb.GetChartVersion)) - apiv1.Methods("GET").Path("/assets/{repo}/{chartName}/logo").Handler(fdb.WithParams(fdb.GetChartIcon)) - // Maintain the logo-160x160-fit.png endpoint for backward compatibility /assets/{repo}/{chartName}/logo should be used instead - apiv1.Methods("GET").Path("/assets/{repo}/{chartName}/logo-160x160-fit.png").Handler(fdb.WithParams(fdb.GetChartIcon)) - apiv1.Methods("GET").Path("/assets/{repo}/{chartName}/versions/{version}/README.md").Handler(fdb.WithParams(fdb.GetChartVersionReadme)) - apiv1.Methods("GET").Path("/assets/{repo}/{chartName}/versions/{version}/values.yaml").Handler(fdb.WithParams(fdb.GetChartVersionValues)) - apiv1.Methods("GET").Path("/assets/{repo}/{chartName}/versions/{version}/values.schema.json").Handler(fdb.WithParams(fdb.GetChartVersionSchema)) - - n := negroni.Classic() - n.UseHandler(r) - return n -} - -func main() { - - debug := flag.Bool("debug", false, "Debug Logging") - - //Flags for optional FoundationDB + Document Layer backend - fdbURL := flag.String("doclayer-url", "mongodb://fdb-service/27016", "FoundationDB Document Layer URL") - fDB := flag.String("doclayer-database", "monocular-plugin", "FoundationDB Document-Layer database") - - //Flags for Serve-Mode TLS - cACertFile := flag.String("cafile", "", "Path to CA certificate to use for client verification.") - certFile := flag.String("certfile", "", "Path to TLS certificate.") - keyFile := flag.String("keyfile", "", "Path to TLS key.") - - //TLS options must either be all set to enabled TLS, or none set to disable TLS - var tlsEnabled = *cACertFile != "" && *keyFile != "" && *certFile != "" - if !(tlsEnabled || (*cACertFile == "" && *keyFile == "" && *certFile == "")) { - log.Fatal("To enable TLS, all 3 TLS cert paths must be set.") - } - - flag.Parse() - - if *debug { - log.SetLevel(log.DebugLevel) - } - - InitFDBDocLayerConnection(fdbURL, fDB, &tlsEnabled, *cACertFile, *certFile, *keyFile, debug) - - n := SetupRoutes() - - port := os.Getenv("PORT") - if port == "" { - port = "8080" - } - addr := ":" + port - log.WithFields(log.Fields{"addr": addr}).Info("Started chartsvc") - http.ListenAndServe(addr, n) -} - -func InitFDBDocLayerConnection(fdbURL *string, fDB *string, tlsEnabled *bool, CAFile string, certFile string, keyFile string, debug *bool) *ChartSvcDatastore { - - log.Debugf("Attempting to connect to FDB: %v, %v, debug: %v", *fdbURL, *fDB, *debug) - - var tlsConfig *tls.Config - - if *tlsEnabled { - //Load CA Cert from file here - CA, err := ioutil.ReadFile(CAFile) // just pass the file name - if err != nil { - log.Fatalf("Cannot load CA certificate from file: %v.", err) - return nil - } - CACert := x509.NewCertPool() - ok := CACert.AppendCertsFromPEM([]byte(CA)) - if !ok { - log.Fatalf("Cannot append CA certificate to certificate pool.") - return nil - } - //Now load the key pair and create tls options struct - clientKeyPair, err := tls.LoadX509KeyPair(certFile, keyFile) - if err != nil { - log.Fatalf("Cannot load server keypair: %v", err) - return nil - } - - tlsConfig = &tls.Config{RootCAs: CACert, Certificates: []tls.Certificate{clientKeyPair}} - } - - clientOptions := options.Client().ApplyURI(*fdbURL) - if *tlsEnabled { - clientOptions.SetTLSConfig(tlsConfig) - } - client, err := fdbDatastore.NewDocLayerClient(context.Background(), clientOptions) - if err != nil { - log.Fatalf("Can't create client for FoundationDB document layer: %v. URL provided was: %v.", err, *fdbURL) - return nil - } - log.Debugf("FDB Document Layer client created.") - - fdb.InitDBConfig(client, *fDB) - fdb.SetPathPrefix(pathPrefix) - - db, dbCloser := client.Database(*fDB) - datastore := &ChartSvcDatastore{ - dbClient: client, - db: db, - dbCloser: dbCloser, - } - - return datastore -} diff --git a/src/jetstream/plugins/monocular/chartsvc/main_test.go b/src/jetstream/plugins/monocular/chartsvc/main_test.go deleted file mode 100644 index 8dbc7b3e11..0000000000 --- a/src/jetstream/plugins/monocular/chartsvc/main_test.go +++ /dev/null @@ -1,544 +0,0 @@ -/* -Copyright (c) 2017 The Helm Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartsvc - -import ( - "encoding/json" - "errors" - "net/http" - "net/http/httptest" - "testing" - - fdb "github.com/helm/monocular/chartsvc/foundationdb" - datastore "github.com/helm/monocular/chartsvc/foundationdb/datastore" - "github.com/helm/monocular/chartsvc/models" - "github.com/helm/monocular/chartsvc/utils" - "go.mongodb.org/mongo-driver/mongo" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var dbType string = "mongodb" - -const testChartSchema = `{"properties": {"type": "object"}}` - -// tests the GET /live endpoint -func Test_GetLive(t *testing.T) { - var m mock.Mock - dbClient := datastore.NewMockClient(&m) - fdb.InitDBConfig(dbClient, "test") - - ts := httptest.NewServer(SetupRoutes()) - defer ts.Close() - - res, err := http.Get(ts.URL + "/live") - assert.NoError(t, err, "should not return an error") - defer res.Body.Close() - assert.Equal(t, res.StatusCode, http.StatusOK, "http status code should match") -} - -// tests the GET /ready endpoint -func Test_GetReady(t *testing.T) { - var m mock.Mock - dbClient := datastore.NewMockClient(&m) - fdb.InitDBConfig(dbClient, "test") - - ts := httptest.NewServer(SetupRoutes()) - defer ts.Close() - - res, err := http.Get(ts.URL + "/ready") - assert.NoError(t, err, "should not return an error") - defer res.Body.Close() - assert.Equal(t, res.StatusCode, http.StatusOK, "http status code should match") -} - -// tests the GET /{apiVersion}/charts endpoint -func Test_GetCharts(t *testing.T) { - ts := httptest.NewServer(SetupRoutes()) - defer ts.Close() - - tests := []struct { - name string - charts []*models.Chart - }{ - {"no charts", []*models.Chart{}}, - {"one chart", []*models.Chart{ - {ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1"}}}}, - }, - {"two charts", []*models.Chart{ - {ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}}, - {ID: "my-repo/dokuwiki", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "1234"}, {Version: "1.2.2", Digest: "12345"}}}, - }}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var m mock.Mock - dbClient := datastore.NewMockClient(&m) - fdb.InitDBConfig(dbClient, "test") - - m.On("Find", mock.Anything, mock.Anything, &utils.ChartsList, mock.Anything).Return(nil).Run(func(args mock.Arguments) { - *args.Get(2).(*[]*models.Chart) = tt.charts - }) - - res, err := http.Get(ts.URL + pathPrefix + "/charts") - assert.NoError(t, err) - defer res.Body.Close() - - m.AssertExpectations(t) - assert.Equal(t, res.StatusCode, http.StatusOK, "http status code should match") - - var b utils.BodyAPIListResponse - json.NewDecoder(res.Body).Decode(&b) - assert.Len(t, *b.Data, len(tt.charts)) - }) - } -} - -// tests the GET /{apiVersion}/charts/{repo} endpoint -func Test_GetChartsInRepo(t *testing.T) { - ts := httptest.NewServer(SetupRoutes()) - defer ts.Close() - - tests := []struct { - name string - repo string - charts []*models.Chart - }{ - {"repo has no charts", "my-repo", []*models.Chart{}}, - {"repo has one chart", "my-repo", []*models.Chart{ - {ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}}, - }}, - {"repo has many charts", "my-repo", []*models.Chart{ - {ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}}, - {ID: "my-repo/dokuwiki", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "1234"}, {Version: "1.2.2", Digest: "12345"}}}, - }}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var m mock.Mock - dbClient := datastore.NewMockClient(&m) - fdb.InitDBConfig(dbClient, "test") - - m.On("Find", mock.Anything, mock.Anything, &utils.ChartsList, mock.Anything).Return(nil).Run(func(args mock.Arguments) { - *args.Get(2).(*[]*models.Chart) = tt.charts - }) - res, err := http.Get(ts.URL + pathPrefix + "/charts/" + tt.repo) - assert.NoError(t, err) - defer res.Body.Close() - - m.AssertExpectations(t) - assert.Equal(t, res.StatusCode, http.StatusOK, "http status code should match") - - var b utils.BodyAPIListResponse - json.NewDecoder(res.Body).Decode(&b) - assert.Len(t, *b.Data, len(tt.charts)) - }) - } -} - -// tests the GET /{apiVersion}/charts/{repo}/{chartName} endpoint -func Test_GetChartInRepo(t *testing.T) { - ts := httptest.NewServer(SetupRoutes()) - defer ts.Close() - - tests := []struct { - name string - err error - chart models.Chart - wantCode int - }{ - { - "chart does not exist", - errors.New("return an error when checking if chart exists"), - models.Chart{ID: "my-repo/my-chart"}, - http.StatusNotFound, - }, - { - "chart exists", - nil, - models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}}}, - http.StatusOK, - }, - { - "chart has multiple versions", - nil, - models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}, {Version: "0.0.1"}}}, - http.StatusOK, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var m mock.Mock - dbClient := datastore.NewMockClient(&m) - fdb.InitDBConfig(dbClient, "test") - if tt.err != nil { - m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(mongo.ErrNoDocuments) - } else { - m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) { - *args.Get(2).(*models.Chart) = tt.chart - }) - } - - res, err := http.Get(ts.URL + pathPrefix + "/charts/" + tt.chart.ID) - assert.NoError(t, err) - defer res.Body.Close() - - m.AssertExpectations(t) - assert.Equal(t, tt.wantCode, res.StatusCode, "http status code should match") - }) - } -} - -// tests the GET /{apiVersion}/charts/{repo}/{chartName}/versions endpoint -func Test_ListChartVersions(t *testing.T) { - ts := httptest.NewServer(SetupRoutes()) - defer ts.Close() - - tests := []struct { - name string - err error - chart models.Chart - wantCode int - }{ - { - "chart does not exist", - errors.New("return an error when checking if chart exists"), - models.Chart{ID: "my-repo/my-chart"}, - http.StatusNotFound, - }, - { - "chart exists", - nil, - models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}}}, - http.StatusOK, - }, - { - "chart has multiple versions", - nil, - models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}, {Version: "0.0.1"}}}, - http.StatusOK, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var m mock.Mock - dbClient := datastore.NewMockClient(&m) - fdb.InitDBConfig(dbClient, "test") - if tt.err != nil { - m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(mongo.ErrNoDocuments) - } else { - m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) { - *args.Get(2).(*models.Chart) = tt.chart - }) - } - - res, err := http.Get(ts.URL + pathPrefix + "/charts/" + tt.chart.ID + "/versions") - assert.NoError(t, err) - defer res.Body.Close() - - m.AssertExpectations(t) - assert.Equal(t, tt.wantCode, res.StatusCode, "http status code should match") - }) - } -} - -// tests the GET /{apiVersion}/charts/{repo}/{chartName}/versions/{:version} endpoint -func Test_GetChartVersion(t *testing.T) { - ts := httptest.NewServer(SetupRoutes()) - defer ts.Close() - - tests := []struct { - name string - err error - chart models.Chart - wantCode int - }{ - { - "chart does not exist", - errors.New("return an error when checking if chart exists"), - models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}}}, - http.StatusNotFound, - }, - { - "chart exists", - nil, - models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}}}, - http.StatusOK, - }, - { - "chart has multiple versions", - nil, - models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}, {Version: "0.0.1"}}}, - http.StatusOK, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var m mock.Mock - dbClient := datastore.NewMockClient(&m) - fdb.InitDBConfig(dbClient, "test") - if tt.err != nil { - m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(mongo.ErrNoDocuments) - } else { - m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) { - *args.Get(2).(*models.Chart) = tt.chart - }) - } - - res, err := http.Get(ts.URL + pathPrefix + "/charts/" + tt.chart.ID + "/versions/" + tt.chart.ChartVersions[0].Version) - assert.NoError(t, err) - defer res.Body.Close() - - m.AssertExpectations(t) - assert.Equal(t, res.StatusCode, tt.wantCode, "http status code should match") - }) - } -} - -// tests the GET /{apiVersion}/assets/{repo}/{chartName}/logo-160x160-fit.png endpoint -func Test_GetChartIcon(t *testing.T) { - ts := httptest.NewServer(SetupRoutes()) - defer ts.Close() - - tests := []struct { - name string - err error - chart models.Chart - wantCode int - }{ - { - "chart does not exist", - errors.New("return an error when checking if chart exists"), - models.Chart{ID: "my-repo/my-chart"}, - http.StatusNotFound, - }, - { - "chart has icon", - nil, - models.Chart{ID: "my-repo/my-chart", RawIcon: utils.IconBytes()}, - http.StatusOK, - }, - { - "chart does not have a icon", - nil, - models.Chart{ID: "my-repo/my-chart"}, - http.StatusNotFound, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var m mock.Mock - dbClient := datastore.NewMockClient(&m) - fdb.InitDBConfig(dbClient, "test") - if tt.err != nil { - m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(mongo.ErrNoDocuments) - } else { - m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) { - *args.Get(2).(*models.Chart) = tt.chart - }) - } - - res, err := http.Get(ts.URL + pathPrefix + "/assets/" + tt.chart.ID + "/logo") - assert.NoError(t, err) - defer res.Body.Close() - - m.AssertExpectations(t) - assert.Equal(t, tt.wantCode, res.StatusCode, "http status code should match") - }) - } -} - -// tests the GET /{apiVersion}/assets/{repo}/{chartName}/versions/{version}/README.md endpoint -func Test_GetChartReadme(t *testing.T) { - ts := httptest.NewServer(SetupRoutes()) - defer ts.Close() - - tests := []struct { - name string - version string - err error - files models.ChartFiles - wantCode int - }{ - { - "chart does not exist", - "0.1.0", - errors.New("return an error when checking if chart exists"), - models.ChartFiles{ID: "my-repo/my-chart"}, - http.StatusNotFound, - }, - { - "chart exists", - "1.2.3", - nil, - models.ChartFiles{ID: "my-repo/my-chart", Readme: utils.TestChartReadme}, - http.StatusOK, - }, - { - "chart does not have a readme", - "1.1.1", - nil, - models.ChartFiles{ID: "my-repo/my-chart"}, - http.StatusNotFound, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var m mock.Mock - dbClient := datastore.NewMockClient(&m) - fdb.InitDBConfig(dbClient, "test") - if tt.err != nil { - m.On("FindOne", mock.Anything, mock.Anything, &models.ChartFiles{}, mock.Anything).Return(mongo.ErrNoDocuments) - } else { - m.On("FindOne", mock.Anything, mock.Anything, &models.ChartFiles{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) { - *args.Get(2).(*models.ChartFiles) = tt.files - }) - } - - res, err := http.Get(ts.URL + pathPrefix + "/assets/" + tt.files.ID + "/versions/" + tt.version + "/README.md") - assert.NoError(t, err) - defer res.Body.Close() - - m.AssertExpectations(t) - assert.Equal(t, tt.wantCode, res.StatusCode, "http status code should match") - }) - } -} - -// tests the GET /{apiVersion}/assets/{repo}/{chartName}/versions/{version}/values.yaml endpoint -func Test_GetChartValues(t *testing.T) { - ts := httptest.NewServer(SetupRoutes()) - defer ts.Close() - - tests := []struct { - name string - version string - err error - files models.ChartFiles - wantCode int - }{ - { - "chart does not exist", - "0.1.0", - errors.New("return an error when checking if chart exists"), - models.ChartFiles{ID: "my-repo/my-chart"}, - http.StatusNotFound, - }, - { - "chart exists", - "3.2.1", - nil, - models.ChartFiles{ID: "my-repo/my-chart", Values: utils.TestChartValues}, - http.StatusOK, - }, - { - "chart does not have values.yaml", - "2.2.2", - nil, - models.ChartFiles{ID: "my-repo/my-chart"}, - http.StatusOK, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var m mock.Mock - dbClient := datastore.NewMockClient(&m) - fdb.InitDBConfig(dbClient, "test") - if tt.err != nil { - m.On("FindOne", mock.Anything, mock.Anything, &models.ChartFiles{}, mock.Anything).Return(mongo.ErrNoDocuments) - } else { - m.On("FindOne", mock.Anything, mock.Anything, &models.ChartFiles{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) { - *args.Get(2).(*models.ChartFiles) = tt.files - }) - } - - res, err := http.Get(ts.URL + pathPrefix + "/assets/" + tt.files.ID + "/versions/" + tt.version + "/values.yaml") - assert.NoError(t, err) - defer res.Body.Close() - - m.AssertExpectations(t) - assert.Equal(t, res.StatusCode, tt.wantCode, "http status code should match") - }) - } -} - -// tests the GET /{apiVersion}/assets/{repo}/{chartName}/versions/{version}/values/schema.json endpoint -func Test_GetChartSchema(t *testing.T) { - ts := httptest.NewServer(SetupRoutes()) - defer ts.Close() - - tests := []struct { - name string - version string - err error - files models.ChartFiles - wantCode int - }{ - { - "chart does not exist", - "0.1.0", - errors.New("return an error when checking if chart exists"), - models.ChartFiles{ID: "my-repo/my-chart"}, - http.StatusNotFound, - }, - { - "chart exists", - "3.2.1", - nil, - models.ChartFiles{ID: "my-repo/my-chart", Schema: testChartSchema}, - http.StatusOK, - }, - { - "chart does not have values.schema.json", - "2.2.2", - nil, - models.ChartFiles{ID: "my-repo/my-chart"}, - http.StatusOK, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var m mock.Mock - dbClient := datastore.NewMockClient(&m) - fdb.InitDBConfig(dbClient, "test") - if tt.err != nil { - m.On("FindOne", mock.Anything, mock.Anything, &models.ChartFiles{}, mock.Anything).Return(mongo.ErrNoDocuments) - } else { - m.On("FindOne", mock.Anything, mock.Anything, &models.ChartFiles{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) { - *args.Get(2).(*models.ChartFiles) = tt.files - }) - } - - res, err := http.Get(ts.URL + pathPrefix + "/assets/" + tt.files.ID + "/versions/" + tt.version + "/values.schema.json") - assert.NoError(t, err) - defer res.Body.Close() - - m.AssertExpectations(t) - assert.Equal(t, res.StatusCode, tt.wantCode, "http status code should match") - }) - } -} diff --git a/src/jetstream/plugins/monocular/chartsvc/utils/testutils.go b/src/jetstream/plugins/monocular/chartsvc/utils/testutils.go deleted file mode 100644 index 4014887a8f..0000000000 --- a/src/jetstream/plugins/monocular/chartsvc/utils/testutils.go +++ /dev/null @@ -1,41 +0,0 @@ -/* -Copyright (c) 2019 The Helm Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package utils - -import ( - "bytes" - "image/color" - - "github.com/helm/monocular/chartsvc/models" - - "github.com/disintegration/imaging" -) - -//ChartsList a list of charts used in unit tests -var ChartsList []*models.Chart - -//IconBytes the bytes of a chart icon image used in unit tests -func IconBytes() []byte { - var b bytes.Buffer - img := imaging.New(1, 1, color.White) - imaging.Encode(&b, img, imaging.PNG) - return b.Bytes() -} - -const TestChartReadme = "# Quickstart\n\n```bash\nhelm install my-repo/my-chart\n```" -const TestChartValues = "image:\n registry: docker.io\n repository: my-repo/my-chart\n tag: 0.1.0" -const TestChartSchema = `{"properties": {"type": "object"}}` diff --git a/src/jetstream/plugins/monocular/chartsvc_main.txt b/src/jetstream/plugins/monocular/chartsvc_main.txt deleted file mode 100644 index 33c73651be..0000000000 --- a/src/jetstream/plugins/monocular/chartsvc_main.txt +++ /dev/null @@ -1,15 +0,0 @@ - -// Stratos addition to chartsvc main.go - -// SetMongoConfig allosws Stratos plugin to configure MongoDB -func SetMongoConfig(dbURL, dbName, dbUsername *string, dbPassword string) (datastore.Session, error) { - var err error - mongoConfig := datastore.Config{URL: *dbURL, Database: *dbName, Username: *dbUsername, Password: dbPassword} - dbSession, err = datastore.NewSession(mongoConfig) - return dbSession, err -} - -// GetRoutes exposes the HTTP routes for the Chart Service API -func GetRoutes() http.Handler { - return setupRoutes() -} diff --git a/src/jetstream/plugins/monocular/git-merge-subpath.sh b/src/jetstream/plugins/monocular/git-merge-subpath.sh deleted file mode 100755 index da5ecf1b8b..0000000000 --- a/src/jetstream/plugins/monocular/git-merge-subpath.sh +++ /dev/null @@ -1,65 +0,0 @@ -#! /bin/bash - -#================================================ -# Attribution: -# https://stackoverflow.com/questions/23937436/add-subdirectory-of-remote-repo-with-git-subtree/30386041#30386041 -#================================================================ - -git-merge-subpath() { - local SQUASH - if [[ $1 == "--squash" ]]; then - SQUASH=1 - shift - fi - if (( $# != 3 )); then - local PARAMS="[--squash] SOURCE_COMMIT SOURCE_PREFIX DEST_PREFIX" - echo "USAGE: ${FUNCNAME[0]} $PARAMS" - return 1 - fi - - # Friendly parameter names; strip any trailing slashes from prefixes. - local SOURCE_COMMIT="$1" SOURCE_PREFIX="${2%/}" DEST_PREFIX="${3%/}" - - local SOURCE_SHA1 - SOURCE_SHA1=$(git rev-parse --verify "$SOURCE_COMMIT^{commit}") - - local OLD_SHA1 - local GIT_ROOT=$(git rev-parse --show-toplevel) - if [[ -n "$(ls -A "$GIT_ROOT/$DEST_PREFIX" 2> /dev/null)" ]]; then - # OLD_SHA1 will remain empty if there is no match. - local RE="^${FUNCNAME[0]}: [0-9a-f]{40} $SOURCE_PREFIX $DEST_PREFIX\$" - OLD_SHA1=$(git log -1 --format=%b -E --grep="$RE" \ - | grep --color=never -E "$RE" | tail -1 | awk '{print $2}') - fi - - local OLD_TREEISH - if [[ -n $OLD_SHA1 ]]; then - OLD_TREEISH="$OLD_SHA1:$SOURCE_PREFIX" - else - # This is the first time git-merge-subpath is run, so diff against the - # empty commit instead of the last commit created by git-merge-subpath. - OLD_TREEISH=$(git hash-object -t tree /dev/null) - fi && - - if [[ -z $SQUASH ]]; then - git merge -s ours --no-commit "$SOURCE_COMMIT" || return 1 - fi && - - printf "Calculating diff of changes from source and applying to destination..." - git diff --color=never "$OLD_TREEISH" "$SOURCE_COMMIT:$SOURCE_PREFIX" \ - | git apply -3 --directory="$DEST_PREFIX" || git mergetool - echo "done." - if (( $? == 1 )); then - echo "Uh-oh! Try cleaning up with |git reset --merge|." - echo "You may be able to finish the merge manually then run the following (do not modify the commit message):" - echo "git commit -em \"Merge $SOURCE_COMMIT:$SOURCE_PREFIX/ to $DEST_PREFIX/ ${FUNCNAME[0]}: $SOURCE_SHA1 $SOURCE_PREFIX $DEST_PREFIX\"" - return 1 - else - git commit -em "Merge $SOURCE_COMMIT:$SOURCE_PREFIX/ to $DEST_PREFIX/ - -# Feel free to edit the title and body above, but make sure to keep the -# ${FUNCNAME[0]}: line below intact, so ${FUNCNAME[0]} can find it -# again when grepping git log. -${FUNCNAME[0]}: $SOURCE_SHA1 $SOURCE_PREFIX $DEST_PREFIX" - fi -} diff --git a/src/jetstream/plugins/monocular/git-pull-downstream.sh b/src/jetstream/plugins/monocular/git-pull-downstream.sh deleted file mode 100755 index 462b3bdc0b..0000000000 --- a/src/jetstream/plugins/monocular/git-pull-downstream.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/bin/bash - -echo "=== Pull Monocular Code to Stratos Repo ===" >&1 -echo "Pulls the subtree under cmd from monocular repo into the src/jetstream/plugins/monocular subtree in stratos" >&1 -usage() { - echo "Usage: git-pull-downstream.sh -f <MONOCULAR_FORK> [ -b <MONOCULAR_BRANCH> | -r <MONOCULAR_REF> ]" >&1 - echo " options:" >&1 - printf " -f MONOCULAR_FORK The upstream fork/repo to pull from\n" >&1 - printf " ONE OF EITHER -b or -r must be set\n" >&1 - echo " -b MONOCULAR_BRANCH The upstream branch to pull from" >&1 - echo " -r MONOCULAR_REF The upsream ref to pull from (e.g. tag or SHA1)" >&1 - echo " -h usage" >&1 - exit 0 -} - -while getopts f:b:r:h arg -do - case ${arg} in - f) MONOCULAR_FORK=${OPTARG};; - b) MONOCULAR_BRANCH=${OPTARG};; - r) MONOCULAR_REF=${OPTARG};; - h) usage;; - \?) usage;; - esac -done - -if [ -z "$MONOCULAR_FORK" ]; then - echo "Must specify a monocular fork to pull from" >&2 - usage -fi - -if [ -n "$MONOCULAR_BRANCH" ] && [ -n "$MONOCULAR_REF" ] || [ -z "$MONOCULAR_BRANCH" ] && [ -z "$MONOCULAR_REF" ]; then - echo "Must specify either a monocular branch or ref to pull from" >&2 - usage -fi - -echo "Monocular fork: $MONOCULAR_FORK" >&1 -echo "Monocular branch: $MONOCULAR_BRANCH" >&1 -echo "Monocular ref: $MONOCULAR_REF" >&1 - -DIRPATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -echo "${DIRPATH}" - -pushd "${DIRPATH}" >& /dev/null || exit - -source ./git-merge-subpath.sh - -echo "Fetching $MONOCULAR_FORK" - -# Fetch and merge the monocular subtree into the Stratos repo and attempt squash commit -git fetch "$MONOCULAR_FORK" - -# Set our pull ref to either "fork/branch" or [tag|commit] -if [ -n "$MONOCULAR_REF" ]; then - PULL_REF="$MONOCULAR_REF" -else - PULL_REF="$MONOCULAR_FORK/$MONOCULAR_BRANCH" -fi - -echo "Reading subtree index from $MONOCULAR_FORK:cmd. Merging in changes from $PULL_REF into src/jetstream/plugins/monocular" >&1 -# Move to root of repo -repo_root=$(git rev-parse --show-toplevel) -echo "Moving to repo top level: $repo_root" >&1 -pushd "$repo_root" >&/dev/null || exit - -# Read in tree -# Git diff apply from last commit (or none) to the specified commit between the given subtrees -git-merge-subpath --squash "$PULL_REF" cmd src/jetstream/plugins/monocular -merge_retval=$? - -popd >& /dev/null || exit -popd >& /dev/null || exit - -if [ $merge_retval -eq 0 ]; then - echo "Success" >&1 -fi - diff --git a/src/jetstream/plugins/monocular/git-push-upstream.sh b/src/jetstream/plugins/monocular/git-push-upstream.sh deleted file mode 100755 index c4e54aaed7..0000000000 --- a/src/jetstream/plugins/monocular/git-push-upstream.sh +++ /dev/null @@ -1,82 +0,0 @@ -#!/bin/bash - -echo "== Push Monocular Changes Upstream ==" >&1 - -usage() { - echo "Usage: git-push-upstream.sh -f <MONOCULAR_FORK> -t <MONOCULAR_BRANCH> -s <STRATOS_SOURCE_BRANCH> ]" >&1 - echo " options:" >&1 - echo " -f MONOCULAR_FORK The upstream fork/repo to push to" >&1 - echo " -t MONOCULAR_BRANCH The target upstream branch to push to" >&1 - echo " -s STRATOS_SOURCE_BRANCH The Stratos source branch containing the changes" >&1 - echo " -h usage" -} - -while getopts f:t:s:h arg; do - case ${arg} in - f) MONOCULAR_FORK=${OPTARG};; - t) MONOCULAR_BRANCH=${OPTARG};; - s) STRATOS_BRANCH=${OPTARG};; - h) usage;; - \?) usage;; - esac -done - - -if [[ -z "$MONOCULAR_FORK" || -z "$MONOCULAR_BRANCH" || -z "$STRATOS_BRANCH" ]]; then - usage - exit 1 -fi - -echo "Monocular fork: $MONOCULAR_FORK" >&1 -echo "Monocular branch: $MONOCULAR_BRANCH" >&1 -echo "Stratos source branch: $STRATOS_BRANCH" >&1 - -DIRPATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -echo "${DIRPATH}" - -pushd "${DIRPATH}" >& /dev/null || exit - -source ./git-merge-subpath.sh - -echo "Checking out monocular target branch: $MONOCULAR_BRANCH from $MONOCULAR_FORK into temporary merge branch: temp-merge-branch" >&1 - -##Change to top level of repo -repo_root=$(git rev-parse --show-toplevel) -echo "Moving to repo top level: $repo_root" >&1 -pushd "$repo_root" >&/dev/null || exit - -##Checkout the monocular feature branch onto a temporary merge branch -git fetch "$MONOCULAR_FORK" -git checkout -b temp-merge-branch "$MONOCULAR_FORK"/"$MONOCULAR_BRANCH" || exit - -##Merge changes from our monocular subtree in Stratos v2-master to our temp-merge-branch - -echo "Merging Stratos branch $STRATOS_BRANCH at src/jetstream/plugins/monocular into temp-merge-branch" >&1 - -git-merge-subpath --squash origin/"$STRATOS_BRANCH" src/jetstream/plugins/monocular cmd -if [ $? -ne 0 ]; then - echo "Unsuccessful." >&2 - #Move back to repo root - important in case of bail-out here. - popd >& /dev/null || exit - exit 1 -else - - echo "Pushing changes to target branch: $MONOCULAR_BRANCH on $MONOCULAR_FORK" >&1 - - ##Push the temporary merge branch back to the monocular feature branch - git push "$MONOCULAR_FORK" HEAD:"$MONOCULAR_BRANCH" - if [ $? -ne 0 ]; then - echo "Failed to push to branch: $MONOCULAR_BRANCH on $MONOCULAR_FORK." >&2 - else - echo "Cleaning up temporary branches" >&1 - ##Cleanup: remove our temporary merge branch - git checkout "$STRATOS_BRANCH" && git branch -d temp-merge-branch - fi -fi - -popd >& /dev/null || exit -popd >& /dev/null || exit - -if [ $? -eq 0 ]; then - echo "Success" >&1 -fi diff --git a/src/jetstream/plugins/monocular/go.mod b/src/jetstream/plugins/monocular/go.mod index d099ac958b..a1f6697738 100644 --- a/src/jetstream/plugins/monocular/go.mod +++ b/src/jetstream/plugins/monocular/go.mod @@ -4,22 +4,19 @@ go 1.12 require ( bitbucket.org/liamstask/goose v0.0.0-20150115234039-8488cc47d90c + github.com/Masterminds/semver/v3 v3.1.0 // indirect github.com/go-sql-driver/mysql v1.4.1 // indirect - github.com/helm/monocular v1.4.0 - github.com/helm/monocular/chartrepo v0.0.0-00010101000000-000000000000 - github.com/helm/monocular/chartsvc v0.0.0-00010101000000-000000000000 github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28 // indirect github.com/labstack/echo v3.3.10+incompatible + github.com/labstack/gommon v0.3.0 // indirect github.com/lib/pq v1.0.0 // indirect github.com/mattn/go-sqlite3 v1.10.0 // indirect github.com/satori/go.uuid v1.2.0 github.com/sirupsen/logrus v1.4.2 github.com/ziutek/mymysql v1.5.4 // indirect + golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect + gopkg.in/yaml.v2 v2.3.0 // indirect ) -replace ( - github.com/cloudfoundry-incubator/stratos/src/jetstream => ../.. - github.com/helm/monocular/chartrepo => ./chart-repo - github.com/helm/monocular/chartsvc => ./chartsvc -) +replace github.com/cloudfoundry-incubator/stratos/src/jetstream => ../.. diff --git a/src/jetstream/plugins/monocular/go.sum b/src/jetstream/plugins/monocular/go.sum index 1dc960fbfd..c4d22047ed 100644 --- a/src/jetstream/plugins/monocular/go.sum +++ b/src/jetstream/plugins/monocular/go.sum @@ -6,6 +6,8 @@ github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITg github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk= +github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= @@ -254,6 +256,8 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191205161847-0a08dada0ff9 h1:abxekknhS/Drh3uoQDk5Hc7BgeiyI39Crb7vhf/1j5s= golang.org/x/crypto v0.0.0-20191205161847-0a08dada0ff9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= @@ -262,6 +266,7 @@ golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI= @@ -280,12 +285,15 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -303,6 +311,8 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= k8s.io/apimachinery v0.0.0-20190221093215-450d01ad5771 h1:XMECjVNpkFVT9uY40z07scw8Xtn2mUnkxx8BC3gjwoE= k8s.io/apimachinery v0.0.0-20190221093215-450d01ad5771/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= k8s.io/apimachinery v0.0.0-20191203211716-adc6f4cd9e7d h1:q+OZmYewHJeMCzwpHkXlNTtk5bvaUMPCikKvf77RBlo= diff --git a/src/jetstream/plugins/monocular/main.go b/src/jetstream/plugins/monocular/main.go index 01f12dc905..c2746a13bf 100644 --- a/src/jetstream/plugins/monocular/main.go +++ b/src/jetstream/plugins/monocular/main.go @@ -1,26 +1,18 @@ package monocular import ( - "encoding/json" "errors" - "fmt" "io/ioutil" - "math/rand" "net/http" "os" - "os/exec" "path/filepath" "strings" "time" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/monocular/store" "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/labstack/echo" log "github.com/sirupsen/logrus" - - "github.com/helm/monocular/chartsvc" - "github.com/helm/monocular/chartsvc/foundationdb" - "github.com/helm/monocular/chartsvc/models" - "github.com/helm/monocular/chartsvc/utils" ) const ( @@ -28,30 +20,25 @@ const ( helmHubEndpointType = "hub" helmRepoEndpointType = "repo" stratosPrefix = "/pp/v1/" - prefix = "/pp/v1/chartsvc/" kubeReleaseNameEnvVar = "STRATOS_HELM_RELEASE" - foundationDBURLEnvVar = "FDB_URL" - syncServerURLEnvVar = "SYNC_SERVER_URL" - caCertEnvVar = "MONOCULAR_CA_CRT_PATH" - tlsKeyEnvVar = "MONOCULAR_KEY_PATH" - tLSCertEnvVar = "MONOCULAR_CRT_PATH" - localDevEnvVar = "FDB_LOCAL_DEV" - chartSyncBasePort = 45000 + cacheFolderEnvVar = "HELM_CACHE_FOLDER" + defaultCacheFolder = "./.helm-cache" ) // Monocular is a plugin for Monocular type Monocular struct { portalProxy interfaces.PortalProxy chartSvcRoutes http.Handler - RepoQueryStore *chartsvc.ChartSvcDatastore + ChartStore store.ChartStore FoundationDBURL string SyncServiceURL string devSyncPID int + CacheFolder string } type HelmHubChart struct { - utils.ApiResponse - Attributes *models.ChartVersion `json:"attributes"` + APIResponse + Attributes *ChartVersion `json:"attributes"` } type HelmHubChartResponse struct { @@ -60,96 +47,51 @@ type HelmHubChartResponse struct { // Init creates a new Monocular func Init(portalProxy interfaces.PortalProxy) (interfaces.StratosPlugin, error) { + store.InitRepositoryProvider(portalProxy.GetConfig().DatabaseProviderName) return &Monocular{portalProxy: portalProxy}, nil } // Init performs plugin initialization func (m *Monocular) Init() error { log.Debug("Monocular init .... ") - if err := m.configure(); err != nil { + + m.CacheFolder = m.portalProxy.Env().String(cacheFolderEnvVar, defaultCacheFolder) + folder, err := filepath.Abs(m.CacheFolder) + if err != nil { return err } - - fdbURL := m.FoundationDBURL - fDB := "monocular-plugin" - debug := false - caCertPath, _ := m.portalProxy.Env().Lookup(caCertEnvVar) - TLSCertPath, _ := m.portalProxy.Env().Lookup(tLSCertEnvVar) - tlsKeyPath, _ := m.portalProxy.Env().Lookup(tlsKeyEnvVar) - m.ConfigureChartSVC(&fdbURL, &fDB, caCertPath, TLSCertPath, tlsKeyPath, &debug) - m.chartSvcRoutes = chartsvc.SetupRoutes() - m.InitSync() - m.syncOnStartup() - return nil -} - -// Destroy does any cleanup for the plugin on exit -func (m *Monocular) Destroy() { - log.Debug("Monocular plugin .. destroy") - if m.devSyncPID != 0 { - log.Info("... Stopping chart sync tool") - if p, err := os.FindProcess(m.devSyncPID); err == nil { - p.Kill() - } else { - log.Error("Could not find process for the chart sync tool") + m.CacheFolder = folder + log.Infof("Using Cache folder: %s", m.CacheFolder) + + // Check that the folder exists - try to make it, if not + if _, err := os.Stat(m.CacheFolder); os.IsNotExist(err) { + log.Info("Helm Cache folder does not exist - creating") + if err := os.MkdirAll(m.CacheFolder, os.ModePerm); err != nil { + log.Warn("Could not create folder for Helm Cache") + return err } } -} - -func (m *Monocular) configure() error { - - // Env var lookup for Monocular services - m.FoundationDBURL = m.portalProxy.Env().String(foundationDBURLEnvVar, "") - m.SyncServiceURL = m.portalProxy.Env().String(syncServerURLEnvVar, "") - - if fdbPort, isLocal := m.portalProxy.Env().Lookup(localDevEnvVar); isLocal { - // Create a random port to use for the chart sync service - devSyncPort := chartSyncBasePort + rand.Intn(5000) - m.FoundationDBURL = fmt.Sprintf("mongodb://127.0.0.1:%s", fdbPort) - m.SyncServiceURL = fmt.Sprintf("http://127.0.0.1:%d", devSyncPort) - // Run the chartrepo tool - dir, err := filepath.Abs(filepath.Dir(os.Args[0])) - if err != nil { - log.Error("Can not get folder of current process") - } - chartSyncTool := filepath.Join(dir, "plugins", "monocular", "chart-repo", "chartrepo") - cmd := exec.Command(chartSyncTool, "serve", fmt.Sprintf("--doclayer-url=%s", m.FoundationDBURL)) - cmd.Env = make([]string, 1) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Env[0] = fmt.Sprintf("PORT=%d", devSyncPort) - if err = cmd.Start(); err != nil { - log.Fatalf("Error starting chart sync tool: %+v", err) - } else { - m.devSyncPID = cmd.Process.Pid - } + store, err := store.NewHelmChartDBStore(m.portalProxy.GetDatabaseConnection()) + if err != nil { + log.Errorf("Can not get Helm Chart store: %s", err) + return err } - log.Debugf("Foundation DB : %s", m.FoundationDBURL) - log.Debugf("Sync Server : %s", m.SyncServiceURL) - - if len(m.FoundationDBURL) == 0 || len(m.SyncServiceURL) == 0 { - return errors.New("Helm Monocular DB and/or Sync server are not configured") - } + m.ChartStore = store + m.InitSync() + m.syncOnStartup() return nil } -func getReleaseNameEnvVarPrefix(name string) string { - prefix := strings.ToUpper(name) - prefix = strings.ReplaceAll(prefix, "-", "_") - return prefix +// Destroy does any cleanup for the plugin on exit +func (m *Monocular) Destroy() { + log.Debug("Monocular plugin .. destroy") } func (m *Monocular) syncOnStartup() { - - // Get the repositories that we currently have - repos, err := foundationdb.ListRepositories() - if err != nil { - log.Errorf("Chart Repository Startup: Unable to sync repositories: %v+", err) - return - } + // Always sync all repositories on startup // Get all of the helm endpoints endpoints, err := m.portalProxy.ListEndpoints() @@ -158,17 +100,13 @@ func (m *Monocular) syncOnStartup() { return } - helmRepos := make([]string, 0) + helmRepos := make(map[string]bool) for _, ep := range endpoints { if ep.CNSIType == helmEndpointType { if ep.SubType == helmRepoEndpointType { - helmRepos = append(helmRepos, ep.Name) - - // Is this an endpoint that we don't have charts for ? - if !arrayContainsString(repos, ep.Name) { - log.Infof("Syncing helm repository to chart store: %s", ep.Name) - m.Sync(interfaces.EndpointRegisterAction, ep) - } + helmRepos[ep.Name] = true + log.Infof("Syncing helm repository to chart store: %s", ep.Name) + m.Sync(interfaces.EndpointRegisterAction, ep) } else { metadata := "{}" m.portalProxy.UpdateEndpointMetadata(ep.GUID, metadata) @@ -176,16 +114,15 @@ func (m *Monocular) syncOnStartup() { } } - // Now delete any repositories that are no longer registered as endpoints - for _, repo := range repos { - if !arrayContainsString(helmRepos, repo) { - log.Infof("Removing helm repository from chart store: %s", repo) - endpoint := &interfaces.CNSIRecord{ - GUID: repo, - Name: repo, - CNSIType: helmEndpointType, + // Delete any endpoints left in the chart store that are no longer registered + // Get all of the endpoints that we have in the Database Chart Store + existing, err := m.ChartStore.GetEndpointIDs() + if err == nil { + for _, id := range existing { + if _, ok := helmRepos[id]; !ok { + log.Warnf("Endpoint ID %s exists in the Chart Store but does not exist as an endpoint - deleting", id) + m.deleteChartStoreForEndpoint(id) } - m.Sync(interfaces.EndpointUnregisterAction, endpoint) } } } @@ -200,17 +137,7 @@ func arrayContainsString(a []string, x string) bool { return false } -func (m *Monocular) ConfigureChartSVC(fdbURL *string, fDB *string, cACertFile string, certFile string, keyFile string, debug *bool) error { - //TLS options must either be all set to enabled TLS, or none set to disable TLS - var tlsEnabled = cACertFile != "" && keyFile != "" && certFile != "" - if !(tlsEnabled || (cACertFile == "" && keyFile == "" && certFile == "")) { - return errors.New("To enable TLS, all 3 TLS cert paths must be set.") - } - m.RepoQueryStore = chartsvc.InitFDBDocLayerConnection(fdbURL, fDB, &tlsEnabled, cACertFile, certFile, keyFile, debug) - - return nil -} - +// OnEndpointNotification handles notification that endpoint has been remoevd func (m *Monocular) OnEndpointNotification(action interfaces.EndpointAction, endpoint *interfaces.CNSIRecord) { if endpoint.CNSIType == helmEndpointType && endpoint.SubType == helmRepoEndpointType { m.Sync(action, endpoint) @@ -239,16 +166,57 @@ func (m *Monocular) AddAdminGroupRoutes(echoGroup *echo.Group) { // AddSessionGroupRoutes adds the session routes for this plugin to the Echo server func (m *Monocular) AddSessionGroupRoutes(echoGroup *echo.Group) { - // Requests to Monocular Instances - echoGroup.Any("/chartsvc/*", m.handleAPI) + + // API for Helm Chart Repositories - sync and sync status // Reach out to a monocular instance other than Stratos (like helm hub). This is usually done via `x-cap-cnsi-list` // however cannot be done for things like img src echoGroup.Any("/monocular/:guid/chartsvc/*", m.handleMonocularInstance) - // API for Helm Chart Repositories - echoGroup.GET("/chartrepos", m.ListRepos) - echoGroup.POST("/chartrepos/status", m.GetRepoStatuses) - echoGroup.POST("/chartrepos/:guid", m.SyncRepo) + echoGroup.POST("/chartrepos/:guid", m.syncRepo) + echoGroup.POST("/chartrepos/status", m.getRepoStatuses) + + // Routes for Chart Store + chartSvcGroup := echoGroup.Group("/chartsvc") + + // Routes for the internal chart store + + // Get specific chart version file (used for values.yaml) + chartSvcGroup.GET("/v1/assets/:repo/:name/versions/:version/:filename", m.getChartAndVersionFile) + + // Get specific chart version file + chartSvcGroup.GET("/v1/charts/:repo/:name/versions/:version/files/:filename", m.getChartAndVersionFile) + + // Get specific chart version of a chart + chartSvcGroup.GET("/v1/charts/:repo/:name/versions/:version", m.getChartVersion) + + // Get chart versions + chartSvcGroup.GET("/v1/charts/:repo/:name/versions", m.getChartVersions) + + // Get a chart + chartSvcGroup.GET("/v1/charts/:repo/:name", m.getChart) + + // // Get list of charts + chartSvcGroup.GET("/v1/charts", m.listCharts) + + // Get the chart icon for a specific version + chartSvcGroup.GET("/v1/assets/:repo/:chartName/:version/logo", m.getIcon) + + // Get the chart icon + chartSvcGroup.GET("/v1/assets/:repo/:chartName/logo", m.getIcon) +} + +// Check if the request if for an external Monocular instance and handle it if so +func (m *Monocular) processMonocularRequest(c echo.Context) (bool, error) { + externalMonocularEndpoint, err := m.isExternalMonocularRequest(c) + if err != nil { + return true, echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + + // If this request is associated with an external monocular instance forward the request on to it + if externalMonocularEndpoint != nil { + return true, m.baseHandleMonocularInstance(c, externalMonocularEndpoint) + } + return false, nil } // isExternalMonocularRequest .. Should this request go out to an external monocular instance? IF so returns external monocular endpoint @@ -278,30 +246,6 @@ func (m *Monocular) validateExternalMonocularEndpoint(cnsi string) (*interfaces. return nil, nil } -// Forward requests to the Chart Service API -func (m *Monocular) handleAPI(c echo.Context) error { - externalMonocularEndpoint, err := m.isExternalMonocularRequest(c) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, err.Error()) - } - - // If this request is associated with an external monocular instance forward the request on to it - if externalMonocularEndpoint != nil { - return m.baseHandleMonocularInstance(c, externalMonocularEndpoint) - } - - // Modify the path to remove our prefix for the Chart Service API - path := c.Request().URL.Path - log.Debugf("URL to chartsvc requested: %v", path) - if strings.Index(path, prefix) == 0 { - path = path[len(prefix)-1:] - c.Request().URL.Path = path - } - log.Debugf("URL to chartsvc requested after modification: %v", path) - m.chartSvcRoutes.ServeHTTP(c.Response().Writer, c.Request()) - return nil -} - func (m *Monocular) handleMonocularInstance(c echo.Context) error { log.Debug("handleMonocularInstance") guid := c.Param("guid") @@ -392,59 +336,3 @@ func (m *Monocular) baseHandleMonocularInstance(c echo.Context, monocularEndpoin return nil } - -// GetChartDownloadUrl ... Get the download url for the bits required to install the given chart -func (m *Monocular) GetChartDownloadUrl(monocularEndpoint, chartID, version string) (string, error) { - if len(monocularEndpoint) > 0 { - // Fetch the monocular endpoint for the url - endpoint, err := m.validateExternalMonocularEndpoint(monocularEndpoint) - if err != nil { - return "", err - } - url := endpoint.APIEndpoint - - // Fetch the chart, this will give us the url to download the bits - url.Path += "/chartsvc/v1/charts/" + chartID + "/versions/" + version - req, err := http.NewRequest(http.MethodGet, url.String(), nil) - req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) - client := &http.Client{Timeout: 30 * time.Second} - res, err := client.Do(req) - if err != nil { - return "", err - } else if res.StatusCode >= 400 { - return "", fmt.Errorf("Couldn't download monocular chart (%+v) from '%+v'", res.StatusCode, req.URL) - } else if res.Body != nil { - body, _ := ioutil.ReadAll(res.Body) - defer res.Body.Close() - - // Reach into the chart response for the download URL - chartVersionResponse := &HelmHubChartResponse{} - err := json.Unmarshal(body, chartVersionResponse) - if err != nil { - return "", err - } - if len(chartVersionResponse.Data.Attributes.URLs) < 1 { - return "", errors.New("Response contained no chart package urls") - } - return chartVersionResponse.Data.Attributes.URLs[0], err - } else { - return "", errors.New("No body in response to chart request") - } - } else { - store := m.RepoQueryStore - chart, err := store.GetChart(chartID) - if err != nil { - return "", errors.New("Could not find Chart") - } - - // Find the download URL for the version - for _, chartVersion := range chart.ChartVersions { - if chartVersion.Version == version { - if len(chartVersion.URLs) == 1 { - return chartVersion.URLs[0], nil - } - } - } - return "", errors.New("Could not find Chart Version") - } -} diff --git a/src/jetstream/plugins/monocular/repository.go b/src/jetstream/plugins/monocular/repository.go index 18b1f3e600..eeeffa298d 100644 --- a/src/jetstream/plugins/monocular/repository.go +++ b/src/jetstream/plugins/monocular/repository.go @@ -2,7 +2,6 @@ package monocular import ( "encoding/json" - "errors" "io/ioutil" "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" @@ -10,45 +9,11 @@ import ( log "github.com/sirupsen/logrus" ) -type HelmRepoInfo struct { - ID string `json:"id"` - Type string `json:"type"` - Attributes struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"attributes"` -} - type helmStatusInfo map[string]bool -func (m *Monocular) ListRepos(c echo.Context) error { - log.Debug("ListRepos") - - endpoints, err := m.portalProxy.ListEndpoints() - if err != nil { - return errors.New("Could not get endpoints") - } - - repos := make([]HelmRepoInfo, 0) - for _, ep := range endpoints { - if ep.CNSIType == helmEndpointType { - // Helm endpoint - repo := HelmRepoInfo{ - ID: ep.Name, - Type: "repository", - } - repo.Attributes.Name = ep.Name - repo.Attributes.URL = ep.APIEndpoint.String() - repos = append(repos, repo) - } - } - - return c.JSON(200, repos) -} - -// GetRepoStatuses will get the status of the Helm Endpoints requested -func (m *Monocular) GetRepoStatuses(c echo.Context) error { - log.Debug("GetRepoStatuses") +// getRepoStatuses will get the status of the Helm Endpoints requested +func (m *Monocular) getRepoStatuses(c echo.Context) error { + log.Debug("getRepoStatuses") // Get the list of endpoints we are looking at // Need to extract the parameters from the request body diff --git a/src/jetstream/plugins/monocular/chartsvc/utils/responses.go b/src/jetstream/plugins/monocular/responses.go similarity index 85% rename from src/jetstream/plugins/monocular/chartsvc/utils/responses.go rename to src/jetstream/plugins/monocular/responses.go index 00d6c105f2..036c9aa658 100644 --- a/src/jetstream/plugins/monocular/chartsvc/utils/responses.go +++ b/src/jetstream/plugins/monocular/responses.go @@ -14,21 +14,21 @@ See the License for the specific language governing permissions and limitations under the License. */ -package utils +package monocular //BodyAPIListResponse is an API body response in list format including the number of results pages type BodyAPIListResponse struct { - Data *ApiListResponse `json:"data"` + Data *APIListResponse `json:"data"` Meta Meta `json:"meta,omitempty"` } //BodyAPIResponse is an API body response in non-list format type BodyAPIResponse struct { - Data ApiResponse `json:"data"` + Data APIResponse `json:"data"` } -//ApiResponse is an API response in non-list format -type ApiResponse struct { +//APIResponse is an API response in non-list format +type APIResponse struct { ID string `json:"id"` Type string `json:"type"` Attributes interface{} `json:"attributes"` @@ -36,8 +36,8 @@ type ApiResponse struct { Relationships RelMap `json:"relationships"` } -//ApiListResponse is an API response in list format -type ApiListResponse []*ApiResponse +//APIListResponse is an API response in list format +type APIListResponse []*APIResponse //SelfLink the self-referencing URL to a chart in a response type SelfLink struct { diff --git a/src/jetstream/plugins/monocular/store/chart_store_db.go b/src/jetstream/plugins/monocular/store/chart_store_db.go new file mode 100644 index 0000000000..c21a7d5787 --- /dev/null +++ b/src/jetstream/plugins/monocular/store/chart_store_db.go @@ -0,0 +1,211 @@ +package store + +import ( + "database/sql" + "errors" + "fmt" + "sort" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/datastore" +) + +var ( + saveChartVersion = `INSERT INTO helm_charts (endpoint, name, repo_name, version, created, app_version, description, icon_url, chart_url, source_url, digest, is_latest, update_batch) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)` + deleteChartVersion = `DELETE FROM helm_charts WHERE endpoint = $1 AND name = $2 and version = $3` + deleteForEndpoint = `DELETE FROM helm_charts WHERE endpoint = $1` + deleteForBatch = `DELETE FROM helm_charts WHERE endpoint = $1 AND name = $2 and update_batch != $3` + renameEndpoint = `UPDATE helm_charts SET repo_name=$1 WHERE endpoint=$2` + getLatestCharts = `SELECT endpoint, name, repo_name, version, created, app_version, description, icon_url, chart_url, source_url, digest, is_latest FROM helm_charts WHERE is_latest = true` + getLatestChart = `SELECT endpoint, name, repo_name, version, created, app_version, description, icon_url, chart_url, source_url, digest, is_latest FROM helm_charts WHERE repo_name = $1 AND name = $2 AND is_latest = true` + getChartVersion = `SELECT endpoint, name, repo_name, version, created, app_version, description, icon_url, chart_url, source_url, digest, is_latest FROM helm_charts WHERE repo_name = $1 AND name = $2 AND version = $3` + getChartVersions = `SELECT endpoint, name, repo_name, version, created, app_version, description, icon_url, chart_url, source_url, digest, is_latest FROM helm_charts WHERE repo_name = $1 AND name = $2` + getEndpointIDs = `SELECT DISTINCT endpoint FROM helm_charts` +) + +// InitRepositoryProvider - One time init for the given DB Provider +func InitRepositoryProvider(databaseProvider string) { + saveChartVersion = datastore.ModifySQLStatement(saveChartVersion, databaseProvider) + deleteChartVersion = datastore.ModifySQLStatement(deleteChartVersion, databaseProvider) + deleteForEndpoint = datastore.ModifySQLStatement(deleteForEndpoint, databaseProvider) + deleteForBatch = datastore.ModifySQLStatement(deleteForBatch, databaseProvider) + renameEndpoint = datastore.ModifySQLStatement(renameEndpoint, databaseProvider) + getLatestCharts = datastore.ModifySQLStatement(getLatestCharts, databaseProvider) + getLatestChart = datastore.ModifySQLStatement(getLatestChart, databaseProvider) + getChartVersion = datastore.ModifySQLStatement(getChartVersion, databaseProvider) + getChartVersions = datastore.ModifySQLStatement(getChartVersions, databaseProvider) + getEndpointIDs = datastore.ModifySQLStatement(getEndpointIDs, databaseProvider) +} + +// HelmChartDBStore is a DB-backed Helm Chart repository +type HelmChartDBStore struct { + db *sql.DB +} + +// NewHelmChartDBStore will create a new instance of the AnalysisDBStore +func NewHelmChartDBStore(dcp *sql.DB) (ChartStore, error) { + return &HelmChartDBStore{db: dcp}, nil +} + +// Save a Helm Chart to the database +func (p *HelmChartDBStore) Save(chart ChartStoreRecord, batchID string) error { + + // Delete the existing record + p.db.Exec(deleteChartVersion, chart.EndpointID, chart.Name, chart.Version) + + sourceURL := "" + if len(chart.Sources) > 0 { + sourceURL = chart.Sources[0] + } + + if _, err := p.db.Exec(saveChartVersion, chart.EndpointID, chart.Name, chart.Repository, chart.Version, chart.Created, chart.AppVersion, chart.Description, chart.IconURL, chart.ChartURL, sourceURL, chart.Digest, chart.IsLatest, batchID); err != nil { + return fmt.Errorf("Unable to save Helm Chart Version: %v", err) + } + return nil +} + +// DeleteBatch will remove all chart versions not with the given batch id +func (p *HelmChartDBStore) DeleteBatch(endpointID, chart, batchID string) error { + if _, err := p.db.Exec(deleteForBatch, endpointID, chart, batchID); err != nil { + return fmt.Errorf("Unable to delete Helm Chart Versions for batch ID: %s %v", batchID, err) + } + return nil +} + +// DeleteForEndpoint will remove all Helm Charts for a given endpoint guid +func (p *HelmChartDBStore) DeleteForEndpoint(endpointID string) error { + if _, err := p.db.Exec(deleteForEndpoint, endpointID); err != nil { + return fmt.Errorf("Unable to delete Helm Charts for endpoint: %s %v", endpointID, err) + } + return nil +} + +// RenameEndpoint will update all charts for a given endpoint to have the new repository name +func (p *HelmChartDBStore) RenameEndpoint(endpointID, name string) error { + if _, err := p.db.Exec(renameEndpoint, name, endpointID); err != nil { + return fmt.Errorf("Unable to rename Helm Chart repository for endpoint: %s %v", endpointID, err) + } + return nil +} + +// GetLatestCharts will get only the info for the latest version of each chart +func (p *HelmChartDBStore) GetLatestCharts() ([]*ChartStoreRecord, error) { + + rows, err := p.db.Query(getLatestCharts) + if err != nil { + return nil, fmt.Errorf("Unable to retrieve Helm Charts: %v", err) + } + defer rows.Close() + + var chartList []*ChartStoreRecord + + for rows.Next() { + chart := new(ChartStoreRecord) + sourceURL := "" + err := rows.Scan(&chart.EndpointID, &chart.Name, &chart.Repository, &chart.Version, &chart.Created, &chart.AppVersion, &chart.Description, &chart.IconURL, &chart.ChartURL, &sourceURL, &chart.Digest, &chart.IsLatest) + if err != nil { + return nil, fmt.Errorf("Unable to scan Helm Chart records: %v", err) + } + chart.SemVer = NewSemanticVersion(chart.Version) + addSources(chart, sourceURL) + chartList = append(chartList, chart) + } + + if err = rows.Err(); err != nil { + return nil, fmt.Errorf("Unable to list Helm Chart records: %v", err) + } + + return chartList, nil +} + +// GetChart gets a single helm chart +func (p *HelmChartDBStore) GetChart(repo, name, version string) (*ChartStoreRecord, error) { + + var row *sql.Row + chart := new(ChartStoreRecord) + + if len(version) == 0 { + row = p.db.QueryRow(getLatestChart, repo, name) + } else { + row = p.db.QueryRow(getChartVersion, repo, name, version) + } + + sourceURL := "" + err := row.Scan(&chart.EndpointID, &chart.Name, &chart.Repository, &chart.Version, &chart.Created, &chart.AppVersion, &chart.Description, &chart.IconURL, &chart.ChartURL, &sourceURL, &chart.Digest, &chart.IsLatest) + switch { + case err == sql.ErrNoRows: + return chart, errors.New("No match for that chart") + case err != nil: + return chart, fmt.Errorf("Error trying to find chart record: %v", err) + default: + // do nothing + } + + chart.SemVer = NewSemanticVersion(chart.Version) + addSources(chart, sourceURL) + + return chart, nil +} + +// GetChartVersions will get all of the versions for a given chart +func (p *HelmChartDBStore) GetChartVersions(repo, name string) ([]*ChartStoreRecord, error) { + rows, err := p.db.Query(getChartVersions, repo, name) + if err != nil { + return nil, fmt.Errorf("Unable to retrieve Helm Charts: %v", err) + } + defer rows.Close() + + var chartList ChartStoreRecordList + + for rows.Next() { + chart := new(ChartStoreRecord) + sourceURL := "" + err := rows.Scan(&chart.EndpointID, &chart.Name, &chart.Repository, &chart.Version, &chart.Created, &chart.AppVersion, &chart.Description, &chart.IconURL, &chart.ChartURL, &sourceURL, &chart.Digest, &chart.IsLatest) + if err != nil { + return nil, fmt.Errorf("Unable to scan Helm Chart records: %v", err) + } + chart.SemVer = NewSemanticVersion(chart.Version) + addSources(chart, sourceURL) + chartList = append(chartList, chart) + } + + if err = rows.Err(); err != nil { + return nil, fmt.Errorf("Unable to list Helm Chart records: %v", err) + } + + // Sort list by version + sort.Sort(chartList) + return chartList, nil +} + +// GetEndpointIDs will get all unique endpoint IDs from the database +func (p *HelmChartDBStore) GetEndpointIDs() ([]string, error) { + rows, err := p.db.Query(getEndpointIDs) + if err != nil { + return nil, fmt.Errorf("Unable to retrieve Endpoint IDs: %v", err) + } + defer rows.Close() + + list := make([]string, 0) + + for rows.Next() { + var endpoint string + err := rows.Scan(&endpoint) + if err != nil { + return nil, fmt.Errorf("Unable to scan Helm Chart records for endpoints: %v", err) + } + list = append(list, endpoint) + } + + if err = rows.Err(); err != nil { + return nil, fmt.Errorf("Unable to list Helm Chart endpoints: %v", err) + } + + return list, nil +} + +func addSources(record *ChartStoreRecord, sourceURL string) { + record.Sources = make([]string, 0) + if len(sourceURL) > 0 { + record.Sources = append(record.Sources, sourceURL) + } +} diff --git a/src/jetstream/plugins/monocular/store/main.go b/src/jetstream/plugins/monocular/store/main.go new file mode 100644 index 0000000000..68501e5f0f --- /dev/null +++ b/src/jetstream/plugins/monocular/store/main.go @@ -0,0 +1,28 @@ +package store + +// ChartStore is the Helm Chart Store repository +type ChartStore interface { + // This will add or update the given chart + Save(chart ChartStoreRecord, batchID string) error + + // Delete chart versions for a given batch + DeleteBatch(endpoint, chart, batchID string) error + + // Delete all charts for the given endpoint + DeleteForEndpoint(endpoint string) error + + // RenameEndpoint renames an endpoint (==renames helm repository) + RenameEndpoint(endpointID, name string) error + + // GetLatestCharts gets all of the latest charts + GetLatestCharts() ([]*ChartStoreRecord, error) + + // Version is optional - empty means get latest + GetChart(repo, name, version string) (*ChartStoreRecord, error) + + // Get Chart Versions + GetChartVersions(repo, name string) ([]*ChartStoreRecord, error) + + // Get Endopoint IDs stored in the chart store + GetEndpointIDs() ([]string, error) +} diff --git a/src/jetstream/plugins/monocular/store/types.go b/src/jetstream/plugins/monocular/store/types.go new file mode 100644 index 0000000000..65e5881d50 --- /dev/null +++ b/src/jetstream/plugins/monocular/store/types.go @@ -0,0 +1,38 @@ +package store + +import ( + "time" +) + +// ChartStoreRecord represents a Helm Chart Version record +type ChartStoreRecord struct { + EndpointID string `json:"endpoint"` + Name string `json:"name"` + Repository string `json:"repo_name"` + Version string `json:"version"` + AppVersion string `json:"app_version"` + Description string `json:"description"` + IconURL string `json:"icon_url"` + ChartURL string `json:"chart_url"` + Sources []string `json:"sources"` + Created time.Time `json:"created"` + Digest string `json:"digest"` + IsLatest bool `json:"is_latest"` + SemVer SemanticVersion `json:"-"` +} + +type ChartStoreRecordList []*ChartStoreRecord + +func (r ChartStoreRecordList) Len() int { + return len(r) +} + +func (r ChartStoreRecordList) Swap(i, j int) { + r[i], r[j] = r[j], r[i] +} +func (r ChartStoreRecordList) Less(i, j int) bool { + ci := r[i].SemVer + cj := r[j].SemVer + + return ci.LessThan(&cj) +} diff --git a/src/jetstream/plugins/monocular/store/version.go b/src/jetstream/plugins/monocular/store/version.go new file mode 100644 index 0000000000..76a08f5f19 --- /dev/null +++ b/src/jetstream/plugins/monocular/store/version.go @@ -0,0 +1,60 @@ +package store + +import ( + semver "github.com/Masterminds/semver/v3" +) + +// SemanticVersion is a semver with support for a plain text version +// Uses the semver library - which errors if the version can not be parsed +// This wrapper ensures that if a version can not be parsed as a semver +// it is treated as a string + +type SemanticVersion struct { + Version *semver.Version + Text string + Valid bool +} + +// NewSemanticVersion parses and returns a Semantic Version +func NewSemanticVersion(version string) SemanticVersion { + + v := SemanticVersion{ + Text: version, + } + + sv, err := semver.NewVersion(version) + v.Version = sv + v.Valid = err == nil + + return v +} + +func (s *SemanticVersion) LessThan(d *SemanticVersion) bool { + if d == nil { + return true + } + if s.Valid && d.Valid { + return !s.Version.LessThan(d.Version) + } else if s.Valid && !d.Valid { + return true + } else if !s.Valid && d.Valid { + return false + } + + return s.Text < d.Text +} + +func (s *SemanticVersion) LessThanReleaseVersions(d *SemanticVersion) bool { + if d == nil { + return true + } + if s.Valid && d.Valid { + // Check release versions + if len(d.Version.Prerelease()) > 0 { + return true + } + return !s.Version.LessThan(d.Version) + } + + return s.LessThan(d) +} diff --git a/src/jetstream/plugins/monocular/sync-source.sh b/src/jetstream/plugins/monocular/sync-source.sh deleted file mode 100755 index c9f34721f6..0000000000 --- a/src/jetstream/plugins/monocular/sync-source.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -echo "== Sync Monocular backend code ==" - -DIRPATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -echo ${DIRPATH} - -pushd ${DIRPATH} >& /dev/null -rm -rf tmp -mkdir tmp -git clone https://github.com/helm/monocular.git ./tmp/monocular - -# Copy the golang backend code -cp -R ./tmp/monocular/cmd/* . - -# Add to the chartsvc API -cat ./chartsvc_main.txt >> ./chartsvc/main.go - -# Update packages -sed -i.bak -e 's/package main/package chartsvc/g' ./chartsvc/*.go -sed -i.bak -e 's/package main/package chartrepo/g' ./chart-repo/*.go - -# Remove .bak files -find . -name "*.bak" -type f -delete - -# Remove tmp folder -rm -rf ./tmp - -# Initialize go modules -cd ./chartsvc -go mod init github.com/helm/monocular/chartsvc - -cd ../chart-repo -go mod init github.com/helm/monocular/chartrepo - -popd >& /dev/null - -echo "All done" diff --git a/src/jetstream/plugins/monocular/sync.go b/src/jetstream/plugins/monocular/sync.go index ab1e6c3460..ff1795fa9a 100644 --- a/src/jetstream/plugins/monocular/sync.go +++ b/src/jetstream/plugins/monocular/sync.go @@ -2,17 +2,10 @@ package monocular import ( "encoding/json" - "fmt" - "io" - "net/http" - "strings" - "time" "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" - "github.com/helm/monocular/chartrepo/common" "github.com/labstack/echo" log "github.com/sirupsen/logrus" - "k8s.io/apimachinery/pkg/util/wait" ) type SyncJob struct { @@ -25,14 +18,6 @@ type SyncMetadata struct { Busy bool `json:"busy"` } -const ( - chartRepoPathPrefix = "/v1" - statusPollInterval = 30 - statusPollTimeout = 320 - syncServiceTimeoutBoundary = 10 - syncServiceReadyPollInterval = 5 -) - // Sync Channel var syncChan = make(chan SyncJob, 100) @@ -41,9 +26,9 @@ func (m *Monocular) InitSync() { go m.processSyncRequests() } -// SyncRepos is endpoint to force a re-sync of a given Helm Repository -func (m *Monocular) SyncRepo(c echo.Context) error { - log.Debug("SyncRepos") +// syncRepo is endpoint to force a re-sync of a given Helm Repository +func (m *Monocular) syncRepo(c echo.Context) error { + log.Debug("syncRepo") // Lookup repository by GUID var p = m.portalProxy @@ -61,170 +46,71 @@ func (m *Monocular) SyncRepo(c echo.Context) error { // Sync schedules a sync action for the given endpoint func (m *Monocular) Sync(action interfaces.EndpointAction, endpoint *interfaces.CNSIRecord) { + // Delete and Update are Synchronously handled + // Add (Sync) is handled Asynchronously via a SyncJob + if action == 0 { + // If the sync job is busy, it won't update the status of this new job until it completes the previous one + // Set the status to indicate it is pending + metadata := SyncMetadata{ + Status: "Pending", + Busy: true, + } + m.portalProxy.UpdateEndpointMetadata(endpoint.GUID, marshalSyncMetadata(metadata)) - // If the sync job is busy, it won't update the status of this new job until it completes the previou sone - // Set the status to indicate it is pending - metadata := SyncMetadata{ - Status: "Pending", - Busy: true, - } - m.portalProxy.UpdateEndpointMetadata(endpoint.GUID, marshalSyncMetadata(metadata)) + // Add the job to the queue to be processed + job := SyncJob{ + Action: action, + Endpoint: endpoint, + } - // Add the job to the queue to be processed - job := SyncJob{ - Action: action, - Endpoint: endpoint, + // Schedula a sync job + syncChan <- job + } else if action == 1 { + log.Debugf("Deleting Helm Repository: %s", endpoint.Name) + m.deleteChartStoreForEndpoint(endpoint.GUID) + } else if action == 2 { + log.Debugf("Helm Repository has been updated - renaming the Helm repository field in the associated charts") + if err := m.ChartStore.RenameEndpoint(endpoint.GUID, endpoint.Name); err != nil { + log.Errorf("An error occurred renameing the Helm Repository for endpoint %s to %s - %+v", endpoint.GUID, endpoint.Name, err) + } } - - syncChan <- job } -func waitForSyncService(syncServiceURL string) error { - // Ensure that the chart repo sync service is responsive - for { - // establish an outer timeout boundary - timeout := time.Now().Add(time.Minute * syncServiceTimeoutBoundary) - - // Make a dummy status request to the chart repo - if it is up we should get a 404 - statusURL := fmt.Sprintf("%s%s/status/%s", syncServiceURL, chartRepoPathPrefix, "none") - resp, err := http.Get(statusURL) - if resp != nil { - defer resp.Body.Close() - } - if err == nil { - log.Info("Sync service is reachable and ready.") - break - } else { - log.Debugf("Result of chart repo request: %v", err) - log.Info("Sync service not yet ready. Waiting for sync service to be available...") - } - - // If our timeout boundary has been exceeded, bail out - if timeout.Sub(time.Now()) < 0 { - return fmt.Errorf("timeout boundary of %d minutes has been exceeded", syncServiceTimeoutBoundary) - } +func (m *Monocular) deleteChartStoreForEndpoint(id string) { + // Delete the records from the database + if err := m.ChartStore.DeleteForEndpoint(id); err != nil { + log.Warnf("Unable to delete Helm Charts for endpoint %s - %+v", id, err) + } - // Circle back and try again - time.Sleep(time.Second * syncServiceReadyPollInterval) + // Delete files from the cache + if err := m.deleteCacheForEndpoint(id); err != nil { + log.Warnf("Unable to delete Helm Chart Cache for endpoint %s - %+v", err) } - return nil } func (m *Monocular) processSyncRequests() { log.Info("Helm Repository Sync init") for job := range syncChan { - err := waitForSyncService(m.SyncServiceURL) - if err != nil { - log.Errorf("Unable to process sync request for %v. Chart Repo not available after %v minutes. %v", job.Endpoint.Name, syncServiceTimeoutBoundary, err) - continue - } log.Debugf("Processing Helm Repository Sync Job: %s", job.Endpoint.Name) - var repoSyncRequestParams string = fmt.Sprintf("{\"repoURL\":%q}", job.Endpoint.APIEndpoint.String()) - // Could be delete or sync - if job.Action == 0 { - log.Debug("Syncing new repository") - metadata := SyncMetadata{ - Status: "Synchronizing", - Busy: true, - } - m.portalProxy.UpdateEndpointMetadata(job.Endpoint.GUID, marshalSyncMetadata(metadata)) - syncURL := fmt.Sprintf("%s%s/sync/%s", m.SyncServiceURL, chartRepoPathPrefix, job.Endpoint.Name) - - //Hit the sync server container endpoint to trigger a sync for given repo - response, err := putRequest(syncURL, strings.NewReader(repoSyncRequestParams)) - metadata.Busy = false - if err != nil { - log.Warn("Request to sync repository failed: %v", err) - metadata.Status = "Sync Failed" - } else { - statusResponse := common.SyncJobStatusResponse{} - defer response.Body.Close() - err := json.NewDecoder(response.Body).Decode(&statusResponse) - if err != nil { - log.Errorf("Unable to parse response from chart-repo server, sync request may not be processed: %v", err) - metadata.Status = "Sync Failed" - } else if statusResponse.Status != common.SyncStatusInProgress { - log.Errorf("Failed to synchronize repo: %v, response: %v, statusResponse", job.Endpoint.Name, err) - metadata.Status = "Sync Failed" - } else { - metadata.Status = "Synchronizing" - metadata.Busy = true - m.updateMetadata(job.Endpoint.GUID, metadata) - log.Infof("Sync in progress for repository: %s", job.Endpoint.APIEndpoint.String()) - //Now wait for success - statusURL := fmt.Sprintf("%s%s/status/%s", m.SyncServiceURL, chartRepoPathPrefix, job.Endpoint.Name) - err := waitForSyncComplete(statusURL) - metadata.Busy = false - if err == nil { - // Need to get the actual status - status, err := getSyncStatus(statusURL) - if err == nil { - metadata.Status = status.Status - } else { - metadata.Status = "Sync Failed" - } - } else { - metadata.Status = "Sync Failed" - log.Errorf("Failed to fetch sync status for repo: %v, %v", job.Endpoint.Name, err) - } - } - } - m.updateMetadata(job.Endpoint.GUID, metadata) - } else if job.Action == 1 { - log.Infof("Deleting Helm Repository: %s", job.Endpoint.Name) - //Hit the sync server container endpoint to trigger a delete for given repo - deleteURL := fmt.Sprintf("%s%s/delete/%s", m.SyncServiceURL, chartRepoPathPrefix, job.Endpoint.Name) - response, err := putRequest(deleteURL, strings.NewReader(repoSyncRequestParams)) - //Extract status from response - if err != nil { - log.Warn("Request to delete repository failed: %v+", err) - } else { - statusResponse := common.SyncJobStatusResponse{} - defer response.Body.Close() - err := json.NewDecoder(response.Body).Decode(&statusResponse) - if err != nil { - log.Errorf("Unable to parse response from chart-repo server, delete request may not be processed: %v", err) - } else if statusResponse.Status != common.DeleteStatusInProgress { - log.Errorf("Failed to delete repo: %v, response: %v, statusResponse", job.Endpoint.Name, err) - } - } - } - } - - log.Debug("processSyncRequests finished") -} - -func waitForSyncComplete(url string) error { - return wait.Poll(statusPollInterval*time.Second, time.Duration(statusPollTimeout)*time.Second, func() (bool, error) { - var complete = false - statusResponse, err := getSyncStatus(url) - if err == nil && statusResponse.Status == common.SyncStatusSynced || statusResponse.Status == common.SyncStatusFailed { - // Note: complete can mean synced okay or sync failed - complete = true + metadata := SyncMetadata{ + Status: "Synchronizing", + Busy: true, } - return complete, err - }) -} + m.portalProxy.UpdateEndpointMetadata(job.Endpoint.GUID, marshalSyncMetadata(metadata)) -func getSyncStatus(url string) (*common.SyncJobStatusResponse, error) { - resp, err := http.Get(url) - if err == nil { - defer resp.Body.Close() - statusResponse := common.SyncJobStatusResponse{} - err = json.NewDecoder(resp.Body).Decode(&statusResponse) - if err == nil { - return &statusResponse, nil + chartIndexURL := job.Endpoint.APIEndpoint.String() + metadata.Status = "Synchronized" + metadata.Busy = false + err := m.syncHelmRepository(job.Endpoint.GUID, job.Endpoint.Name, chartIndexURL) + if err != nil { + log.Warn("Helm Repository sync repository failed for repository %s - %v", job.Endpoint.GUID, err) + metadata.Status = "Sync Failed" } - } - return nil, err -} -func marshalSyncMetadata(metadata SyncMetadata) string { - jsonString, err := json.Marshal(metadata) - if err != nil { - return "" + // Update the job status + m.updateMetadata(job.Endpoint.GUID, metadata) } - return string(jsonString) + log.Debug("processSyncRequests finished") } func (m *Monocular) updateMetadata(endpoint string, metadata SyncMetadata) { @@ -234,24 +120,10 @@ func (m *Monocular) updateMetadata(endpoint string, metadata SyncMetadata) { } } -//https://gist.github.com/maniankara/a10d19960293b34b608ac7ef068a3d63 -func putRequest(url string, data io.Reader) (*http.Response, error) { - client := &http.Client{} - req, err := http.NewRequest(http.MethodPut, url, data) - var resp *http.Response - if err == nil { - resp, err = client.Do(req) - } - return resp, err -} - -//https://gist.github.com/maniankara/a10d19960293b34b608ac7ef068a3d63 -func getRequest(url string, data io.Reader) (*http.Response, error) { - client := &http.Client{} - req, err := http.NewRequest(http.MethodPut, url, data) - var resp *http.Response - if err == nil { - resp, err = client.Do(req) +func marshalSyncMetadata(metadata SyncMetadata) string { + jsonString, err := json.Marshal(metadata) + if err != nil { + return "" } - return resp, err + return string(jsonString) } diff --git a/src/jetstream/plugins/monocular/sync_worker.go b/src/jetstream/plugins/monocular/sync_worker.go new file mode 100644 index 0000000000..e168d2c611 --- /dev/null +++ b/src/jetstream/plugins/monocular/sync_worker.go @@ -0,0 +1,139 @@ +package monocular + +import ( + "fmt" + "strings" + + yaml "gopkg.in/yaml.v2" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/monocular/store" + uuid "github.com/satori/go.uuid" + log "github.com/sirupsen/logrus" +) + +type syncResult struct { + Charts []store.ChartStoreRecord + Latest store.ChartStoreRecord +} + +func (m *Monocular) syncHelmRepository(endpointID, repoName, url string) error { + + // Add index.yaml to the URL + var downloadURL string + + // Append "index.yaml" to the Chart Repository URL + if strings.HasSuffix(url, "/") { + downloadURL = fmt.Sprintf("%sindex.yaml", url) + } else { + downloadURL = fmt.Sprintf("%s/index.yaml", url) + } + + // Read the index.html file from the repository + httpClient := m.portalProxy.GetHttpClient(false) + resp, err := httpClient.Get(downloadURL) + if err != nil { + return fmt.Errorf("Could not download Helm Repository Index: %s", err) + } + if resp.StatusCode != 200 { + return fmt.Errorf("Could not download Helm Repository Index: %s", resp.Status) + } + + defer resp.Body.Close() + + // Marshal to the index structure + var index IndexFile + + decoder := yaml.NewDecoder(resp.Body) + err = decoder.Decode(&index) + if err != nil { + return fmt.Errorf("Error marshalling Helm Repository Index: %+v", err) + } + + var latestCharts []store.ChartStoreRecord + var allCharts []store.ChartStoreRecord + + // Iterate over each chart in the index + for name, chartVersions := range index.Entries { + log.Debugf("Helm Repository Sync: Processing chart: %s", name) + syncRsult := m.procesChartVersions(endpointID, repoName, name, chartVersions) + latestCharts = append(latestCharts, syncRsult.Latest) + allCharts = append(allCharts, syncRsult.Charts...) + } + + // Cache latest charts + if err = m.cacheCharts(latestCharts); err != nil { + log.Warnf("Error caching helm charts: %+v", err) + } + + // Finally, delete all files that are no longer referenced in the database + if err = m.cleanCacheFiles(endpointID, allCharts); err != nil { + log.Errorf("%s", err) + } + + log.Infof("Sync completed for %s", repoName) + + return nil +} + +func (m *Monocular) procesChartVersions(endpoint, repoName, name string, chartVersions []IndexFileMetadata) syncResult { + + result := syncResult{} + + // Find the newest version + var latestSemVer *store.SemanticVersion + for _, chartVersion := range chartVersions { + sv := store.NewSemanticVersion(chartVersion.Version) + if sv.LessThanReleaseVersions(latestSemVer) { + latestSemVer = &sv + } + } + + latestVersion := latestSemVer.Text + + // Generate a new batch update id - we use this to remove any charts that we not updated in this sync - these + // will have an old batch update id afetr processing + batchID := uuid.NewV4().String() + + // Write all versions database + for _, chartVersion := range chartVersions { + if len(chartVersion.URLs) == 0 { + log.Warnf("Can not index Chart %s, Version %s - Chart does not have any Chart URLs", chartVersion.Name, chartVersion.Version) + } else { + if len(chartVersion.URLs) > 1 { + log.Warnf("Chart %s, Version %s - Chart has more than 1 Chart URL - only using the first URL", chartVersion.Name, chartVersion.Version) + } + + // Create a record for the Chart Version that we will store in the database + record := store.ChartStoreRecord{ + EndpointID: endpoint, + Name: chartVersion.Name, + Repository: repoName, + Version: chartVersion.Version, + AppVersion: chartVersion.AppVersion, + Description: chartVersion.Description, + IconURL: chartVersion.Icon, + ChartURL: chartVersion.URLs[0], + Sources: chartVersion.Sources, + Created: chartVersion.Created, + Digest: chartVersion.Digest, + IsLatest: chartVersion.Version == latestVersion, + } + + result.Charts = append(result.Charts, record) + if record.IsLatest { + result.Latest = record + } + + if err := m.ChartStore.Save(record, batchID); err != nil { + log.Warnf("Error saving Chart %s, Version %s to the database: %+v", record.Name, record.Version, err) + } + } + } + + // Delete versions not updated in this batch + if err := m.ChartStore.DeleteBatch(endpoint, name, batchID); err != nil { + log.Warnf("Error deleting old Chart batches: Name %s, Batch ID %s, error: %+v", name, batchID, err) + } + + return result +} diff --git a/src/jetstream/plugins/monocular/types.go b/src/jetstream/plugins/monocular/types.go new file mode 100644 index 0000000000..0c828daa13 --- /dev/null +++ b/src/jetstream/plugins/monocular/types.go @@ -0,0 +1,70 @@ +package monocular + +import "time" + +// IndexFile represents the index.yaml structure for a Helm Repository +type IndexFile struct { + APIVersion string `json:"apiVersion,omitempty"` + Entries map[string][]IndexFileMetadata `json:"entries,omitempty"` +} + +// IndexFileMetadata represents the metadata for a single chart version +type IndexFileMetadata struct { + Name string `json:"name,omitempty"` + AppVersion string `json:"appVersion" yaml:"appVersion"` + Description string `json:"description,omitempty"` + Digest string `json:"digest,omitempty"` + Version string `json:"version,omitempty"` + Created time.Time `json:"created"` + Icon string `json:"icon,omitempty"` + URLs []string `json:"-" yaml:"urls"` + Sources []string `json:"-" yaml:"sources"` + APIVersion string `json:"-" yaml:"apiVersion"` +} + +// ChartMaintainer describes a Chart maintainer. +type ChartMaintainer struct { + // Name is a user name or organization name + Name string `json:"name,omitempty"` + // Email is an optional email address to contact the named maintainer + Email string `json:"email,omitempty"` + // URL is an optional URL to an address for the named maintainer + URL string `json:"url,omitempty"` +} + +// ChartMetadata for a Chart file. This models the structure of a Chart.yaml file. +type ChartMetadata struct { + // The name of the chart + Name string `json:"name,omitempty"` + // The URL to a relevant project page, git repo, or contact person + Home string `json:"home,omitempty"` + // Source is the URL to the source code of this chart + Sources []string `json:"sources,omitempty"` + // A SemVer 2 conformant version string of the chart + Version string `json:"version,omitempty"` + // A one-sentence description of the chart + Description string `json:"description,omitempty"` + // A list of string keywords + Keywords []string `json:"keywords,omitempty"` + // A list of name and URL/email address combinations for the maintainer(s) + Maintainers []*ChartMaintainer `json:"maintainers,omitempty"` + // The URL to an icon file. + Icon string `json:"icon,omitempty"` + // The API Version of this chart. + APIVersion string `json:"apiVersion,omitempty"` + // The condition to check to enable chart + Condition string `json:"condition,omitempty"` + // The tags to check to enable chart + Tags string `json:"tags,omitempty"` + // The version of the application enclosed inside of this chart. + AppVersion string `json:"appVersion,omitempty"` + // Whether or not this chart is deprecated + Deprecated bool `json:"deprecated,omitempty"` + // Annotations are additional mappings uninterpreted by Helm, + // made available for inspection by other applications. + Annotations map[string]string `json:"annotations,omitempty"` + // KubeVersion is a SemVer constraint specifying the version of Kubernetes required. + KubeVersion string `json:"kubeVersion,omitempty"` + // Specifies the chart type: application or library + Type string `json:"type,omitempty"` +} diff --git a/src/jetstream/plugins/monocular/utils/urlpoll.go b/src/jetstream/plugins/monocular/utils/urlpoll.go deleted file mode 100644 index 3e71baddc0..0000000000 --- a/src/jetstream/plugins/monocular/utils/urlpoll.go +++ /dev/null @@ -1,144 +0,0 @@ -// https://golang.org/doc%2Fcodewalk%2Furlpoll.go - -package utils - -import ( - "ioutil" - "log" - "net/http" - "time" -) - -const ( - numPollers = 2 // number of Poller goroutines to launch - pollInterval = 20 * time.Second // how often to poll each URL - statusInterval = 10 * time.Second // how often to log status to stdout - errTimeout = 10 * time.Second // back-off timeout on error -) - -var urls = []string{ - "http://www.google.com/", - "http://golang.org/", - "http://blog.golang.org/", -} - -// State represents the last-known state of a URL. -type State struct { - url string - status string - body string - error string -} - -// StateMonitor maintains a map that stores the state of the URLs being -// polled, and prints the current state every updateInterval nanoseconds. -// It returns a chan State to which resource state should be sent. -func StateMonitor(updateInterval time.Duration) chan<- State { - updates := make(chan State) - urlStatus := make(map[string]string) - ticker := time.NewTicker(updateInterval) - go func() { - for { - select { - case <-ticker.C: - logState(urlStatus) - case s := <-updates: - urlStatus[s.url] = s.status - } - } - }() - return updates -} - -// logState prints a state map. -func logState(s map[string]string) { - log.Println("Current state:") - for k, v := range s { - log.Printf(" %s %s", k, v) - } -} - -// Resource represents an HTTP URL to be polled by this program. -type Resource struct { - url string - errCount int -} - -// Poll executes an HTTP HEAD request for url -// and returns the HTTP status string or an error string. -func (r *Resource) PollHead() string { - resp, err := http.Head(r.url) - if err != nil { - log.Println("Error", r.url, err) - r.errCount++ - return err.Error() - } - r.errCount = 0 - return resp.Status -} - -// Poll executes an HTTP GET request for url -// and returns the HTTP status string, body as a string. -func (r *Resource) PollGet() (string, string, error) { - - var body = "" - resp, err := http.Get(r.url) - defer resp.Body.Close() - if err != nil { - r.errCount++ - return body, resp.Status, err - } - - bodyBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - r.errCount++ - return body, resp.Status, err - } - - r.errCount = 0 - body = string(bodyBytes) - return body, resp.Status, nil -} - -// Sleep sleeps for an appropriate interval (dependent on error state) -// before sending the Resource to done. -func (r *Resource) Sleep(done chan<- *Resource) { - time.Sleep(pollInterval + errTimeout*time.Duration(r.errCount)) - done <- r -} - -func BodyPoller(in <-chan *Resource, out chan<- *Resource, status chan<- State) { - for r := range in { - b, s, e := r.PollGet() - errorString = "" - if e != nil { - errorString = e.Error() - } - status <- State{r.url, s, b, errorString} - out <- r - } -} - -func main() { - // Create our input and output channels. - pending, complete := make(chan *Resource), make(chan *Resource) - - // Launch the StateMonitor. - status := StateMonitor(statusInterval) - - // Launch some Poller goroutines. - for i := 0; i < numPollers; i++ { - go Poller(pending, complete, status) - } - - // Send some Resources to the pending queue. - go func() { - for _, url := range urls { - pending <- &Resource{url: url} - } - }() - - for r := range complete { - go r.Sleep(pending) - } -} diff --git a/src/jetstream/repository/interfaces/endpoints.go b/src/jetstream/repository/interfaces/endpoints.go index 376a286e77..f843ad254c 100644 --- a/src/jetstream/repository/interfaces/endpoints.go +++ b/src/jetstream/repository/interfaces/endpoints.go @@ -18,9 +18,14 @@ type RoutePlugin interface { AddAdminGroupRoutes(echoContext *echo.Group) } +// EndpointAction identifies the type of action for an endpoint notification type EndpointAction int const ( + // EndpointRegisterAction is for when an endpoint is registered EndpointRegisterAction EndpointAction = iota + // EndpointUnregisterAction is for when an endpoint is unregistered EndpointUnregisterAction + // EndpointUpdateAction is for when an endpoint is updated (e.g. renamed) + EndpointUpdateAction ) From 491190de8613e3c29dc4869e64d6592dca781b76 Mon Sep 17 00:00:00 2001 From: Neil MacDougall <nwmac@users.noreply.github.com> Date: Tue, 15 Sep 2020 13:57:30 +0100 Subject: [PATCH 617/648] Helm: Improve experience for editing chart values (#469) * Indicate if the Helm chart has a schema * Fixes * Fix unit tests * Update go.sum * Replace foundation DB with sql db and file cache * Tidy ups and final tweaks to get versions working * Tidy up build and remove fdb * Update .gitignore * Remove unused functions * FIx upgrade and add in sources * Improve the edit experience for entering Helm chart values * Fix unit test * Remove fdb helm chart values * Fix default cache folder name * Remove unused commented code * Fix comments * Formatting * Tidy ups * Fix stylesheet issue with var * Update comments * Add missing import following refactor * Fixes for diff and for form <-> editor transitions * Fix issues with async delete and edit * Tidy up chartName * Fix HelmChartID rename to HelmChartReference * Fixes * FIx backend merge issues * Fixes * Get upgrade working * Fix unit tests due to moving of create release component * Additional unit test fixes * One more test fix * Fix for tests * Import MD App Module for unit test fix * Remove commented out code block * Use drop down menu for the values actions * Improve comments and remove console.log * Set volume for helm cache when deployed to k8s * Fix fro kuberenetes deployment * Ensure db statements aer modified for different DBs * Address PR feedback * Fix missing param to error log msg * Add support for retrieving icon for a specific version * Use icon for the chart version. Reduce loading indicators * Bug fix for icon on list view * Fix icons on Helm Hub * Merge fixes * Fix merge issue * Fix merge issue * Fix unit tests * Address PR feedback --- angular.json | 12 +- package.json | 8 +- .../suse-extensions/sass/_all-theme.scss | 3 +- .../create-release.component.scss | 50 -- .../create-release/create-release.module.ts | 19 - .../src/custom/helm/helm.module.ts | 2 - .../src/custom/helm/helm.routing.ts | 3 - .../chart-details-usage.component.ts | 2 +- .../chart-values-editor.component.html | 63 +++ .../chart-values-editor.component.scss | 104 +++++ .../chart-values-editor.component.spec.ts | 41 ++ .../chart-values-editor.component.theme.scss | 65 +++ .../chart-values-editor.component.ts | 427 ++++++++++++++++++ .../chart-values-editor/diffvalues.ts | 50 ++ .../json-schema-generator.ts | 288 ++++++++++++ .../create-release.component.html | 13 +- .../create-release.component.scss | 18 + .../create-release.component.spec.ts | 20 +- .../create-release.component.ts | 93 ++-- .../helm-release-summary-tab.component.ts | 1 + .../upgrade-release.component.html | 25 +- .../upgrade-release.component.ts | 50 +- .../kubernetes/workloads/workloads.module.ts | 14 + .../kubernetes/workloads/workloads.routing.ts | 3 + 24 files changed, 1172 insertions(+), 202 deletions(-) delete mode 100644 src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.scss delete mode 100644 src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.module.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.html create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.spec.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.theme.scss create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/diffvalues.ts create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/json-schema-generator.ts rename src/frontend/packages/suse-extensions/src/custom/{helm => kubernetes/workloads}/create-release/create-release.component.html (70%) create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.scss rename src/frontend/packages/suse-extensions/src/custom/{helm => kubernetes/workloads}/create-release/create-release.component.spec.ts (61%) rename src/frontend/packages/suse-extensions/src/custom/{helm => kubernetes/workloads}/create-release/create-release.component.ts (73%) diff --git a/angular.json b/angular.json index 001160c86a..79b33bcd31 100644 --- a/angular.json +++ b/angular.json @@ -28,7 +28,17 @@ "input": "custom-src/frontend/assets/custom", "output": "/core/assets/custom" }, - "src/frontend/packages/core/favicon.ico" + "src/frontend/packages/core/favicon.ico", + { + "glob": "**/*", + "input": "node_modules/ngx-monaco-editor/assets/monaco", + "output": "/core/assets/monaco" + }, + { + "glob": "**/*", + "input": "node_modules/@cfstratos/monaco-yaml/lib", + "output": "/core/assets/monaco/vs/language/yaml" + } ], "styles": [ "src/frontend/packages/core/src/styles.scss", diff --git a/package.json b/package.json index 15e022fa11..0806aac4f6 100644 --- a/package.json +++ b/package.json @@ -61,31 +61,33 @@ "@angular/platform-browser-dynamic": "^9.1.6", "@angular/platform-server": "^9.1.6", "@angular/router": "^9.1.6", + "@cfstratos/ajsf-material": "^0.1.6", + "@cfstratos/monaco-yaml": "^2.5.0", "@ngrx/effects": "^9.1.2", "@ngrx/router-store": "^9.1.2", "@ngrx/store": "^9.1.2", "@ngrx/store-devtools": "^9.1.2", "@swimlane/ngx-charts": "^13.0.3", "@swimlane/ngx-graph": "^7.0.1", - "@types/moment-timezone": "^0.5.13", "@types/marked": "^0.7.4", + "@types/moment-timezone": "^0.5.13", "angular2-virtual-scroll": "^0.4.16", "core-js": "^3.6.5", "immer": "^6.0.3", + "intersect": "^1.0.1", "lodash-es": "^4.17.14", "mappy-breakpoints": "^0.2.3", "marked": "^0.8.2", - "intersect": "^1.0.1", "moment": "^2.24.0", "moment-timezone": "^0.5.28", "ngrx-store-localstorage": "9.0.0", "ngx-moment": "^3.5.0", + "ngx-monaco-editor": "^9.0.0", "normalizr": "^3.6.0", "reselect": "^4.0.0", "rxjs": "^6.5.5", "rxjs-spy": "^7.0.2", "rxjs-websockets": "~8.0.1", - "@cfstratos/ajsf-material": "^0.1.5", "ts-md5": "^1.2.7", "tslib": "^1.10.0", "web-animations-js": "^2.3.2", diff --git a/src/frontend/packages/suse-extensions/sass/_all-theme.scss b/src/frontend/packages/suse-extensions/sass/_all-theme.scss index 116316ed35..3d775747a6 100644 --- a/src/frontend/packages/suse-extensions/sass/_all-theme.scss +++ b/src/frontend/packages/suse-extensions/sass/_all-theme.scss @@ -6,6 +6,7 @@ @import '../src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.theme'; @import '../src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.theme'; @import '../src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.theme'; +@import '../src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.theme'; @mixin apply-theme-suse-extensions($stratos-theme) { @@ -18,5 +19,5 @@ @include monocular-chart-card($theme, $app-theme); @include helm-release-summary-tab-theme($theme, $app-theme); @include kube-node-link-theme($theme, $app-theme); - + @include app-chart-values-editor-theme($theme, $app-theme); } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.scss b/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.scss deleted file mode 100644 index a2974cf6f0..0000000000 --- a/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.scss +++ /dev/null @@ -1,50 +0,0 @@ -:host { - flex: 1; -} - -.helm-create-release { - &__heading { - align-items: center; - display: flex; - } - - &__title { - flex: 1; - font-size: 14px; - } - &__button { - height: 36px; - } -} - -form { - flex: 1; - - mat-checkbox { - display: flex; - height: 23px; - margin-top: 10px; - } -} - -.overrides { - &__yaml { - background-color: rgba(0, 0, 0, .1); - font-family: 'Source Code Pro', monospace; - height: 400px; - } - - &_form { - max-width: 100%; - } - - &_form-field { - flex: 1; - height: 100%; - width: 100%; - } -} - -form.overrides_form { - max-width: 100%; -} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.module.ts b/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.module.ts deleted file mode 100644 index 924e790d1e..0000000000 --- a/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.module.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; - -import { CoreModule } from '../../../../../core/src/core/core.module'; -import { SharedModule } from '../../../../../core/src/shared/shared.module'; -import { CreateReleaseComponent } from './create-release.component'; - - -@NgModule({ - imports: [ - CommonModule, - CoreModule, - SharedModule - ], - declarations: [ - CreateReleaseComponent, - ] -}) -export class CreateReleaseModule { } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm.module.ts b/src/frontend/packages/suse-extensions/src/custom/helm/helm.module.ts index 0bba8cacac..02f818996c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm.module.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/helm.module.ts @@ -4,7 +4,6 @@ import { NgModule } from '@angular/core'; import { CoreModule } from '../../../../core/src/core/core.module'; import { SharedModule } from '../../../../core/src/shared/shared.module'; import { MonocularChartViewComponent } from './chart-view/monocular.component'; -import { CreateReleaseModule } from './create-release/create-release.module'; import { HelmRoutingModule } from './helm.routing'; import { MonocularChartCardComponent } from './list-types/monocular-chart-card/monocular-chart-card.component'; import { MonocularTabBaseComponent } from './monocular-tab-base/monocular-tab-base.component'; @@ -17,7 +16,6 @@ import { CatalogTabComponent } from './tabs/catalog-tab/catalog-tab.component'; CommonModule, SharedModule, HelmRoutingModule, - CreateReleaseModule, MonocularModule ], declarations: [ diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm.routing.ts b/src/frontend/packages/suse-extensions/src/custom/helm/helm.routing.ts index e42207d593..b523106013 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm.routing.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/helm.routing.ts @@ -2,7 +2,6 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { MonocularChartViewComponent } from './chart-view/monocular.component'; -import { CreateReleaseComponent } from './create-release/create-release.component'; import { MonocularTabBaseComponent } from './monocular-tab-base/monocular-tab-base.component'; import { CatalogTabComponent } from './tabs/catalog-tab/catalog-tab.component'; @@ -18,8 +17,6 @@ const monocular: Routes = [ }, { pathMatch: 'full', path: 'charts/:endpoint/:repo/:chartName/:version', component: MonocularChartViewComponent }, { path: 'charts/:endpoint/:repo/:chartName', component: MonocularChartViewComponent }, - { pathMatch: 'full', path: 'install/:endpoint/:repo/:name/:version', component: CreateReleaseComponent }, - { path: 'install/:endpoint/:repo/:name', component: CreateReleaseComponent }, ]; @NgModule({ diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.ts index 73025fe6ac..0f213b27e8 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.ts @@ -39,7 +39,7 @@ export class ChartDetailsUsageComponent implements OnInit { } get installUrl(): string { - return `/monocular/install/${getMonocularEndpoint(this.route, this.chart)}/${this.chart.id}/${this.currentVersion}`; + return `/workloads/install/${getMonocularEndpoint(this.route, this.chart)}/${this.chart.id}/${this.currentVersion}`; } } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.html new file mode 100644 index 0000000000..2baa79aaf0 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.html @@ -0,0 +1,63 @@ +<mat-card class="editor-card"> + <div *ngIf="loading$ | async" class="editor-loading"> + <div> + <div class="editor-loading__msg">Loading ...</div> + <mat-progress-bar class="editor-loading__progress-bar" [color]="'primary'" mode="indeterminate"></mat-progress-bar> + </div> + </div> + <mat-toolbar class="editor-toolbar"> + <mat-button-toggle-group [value]="mode" name="editMode" aria-label="Edit Mode" (change)="editModeChanged($event)" *ngIf="schema; else editorOnly"> + <mat-button-toggle value="form">Form</mat-button-toggle> + <mat-button-toggle value="editor">YAML</mat-button-toggle> + </mat-button-toggle-group> + + <ng-template #editorOnly> + <div class="editor-title">YAML Editor</div> + </ng-template> + + <div class="editor-spacer"></div> + + <div class="editor-toolbar-buttons"> + <mat-menu #appMenu="matMenu"> + <button mat-menu-item (click)="copyValues()">Copy from chart values</button> + <button *ngIf="releaseValues" mat-menu-item (click)="copyReleaseValues()">Copy from release values</button> + <mat-divider *ngIf="mode === 'editor'" class="editor-menu-divider"></mat-divider> + <button *ngIf="mode === 'editor'" mat-menu-item (click)="diff()">Diff with chart values</button> + <mat-divider class="editor-menu-divider"></mat-divider> + <button mat-menu-item (click)="clearFormValues()">Clear</button> + </mat-menu> + <button mat-button [matMenuTriggerFor]="appMenu"> + <span>Values</span> + <mat-icon>arrow_drop_down</mat-icon> + </button> + </div> + + <div class="editor-toolbar-buttons"> + <mat-button-toggle (click)="toggleLineNumbers()" checked="lineNumbers" *ngIf="mode === 'editor'"> + <mat-icon>format_list_numbered</mat-icon> + </mat-button-toggle> + <mat-button-toggle (click)="toggleMinimap()" checked="minimap" *ngIf="mode === 'editor'"> + <mat-icon>map</mat-icon> + </mat-button-toggle> + </div> + + </mat-toolbar> + <div [ngClass]="{'editor-hidden': mode !== 'form'}" class="editor-form"> + <div class="editor-yaml-error" *ngIf="yamlError"> + <div class="editor-yaml-error__msg"> + <div> + <mat-icon class="editor-yaml-error__icon text-warning">warning</mat-icon> + </div> + <div class="editor-yaml-error__text"> + <div>Error - YAML is not valid</div> + <div>Use the YAML editor to correct it so the values can be loaded into the form</div> + </div> + </div> + </div> + <json-schema-form *ngIf="hasSchema" (onChanges)="formChanged($event)" #schemaForm loadExternalAssets="false" [options]="{ addSubmit: false }" [schema]="schema" framework="material-design" [data]="initialFormData"></json-schema-form> + </div> + <div [ngClass]="{'editor-hidden': mode !== 'editor'}" class="editor-monaco" #monacoContainer> + <ngx-monaco-editor #monacoEditor class="editor-monaco-edit "[options]="editorOptions" [model]="model" [(ngModel)]="code" (onInit)="onMonacoInit($event)"></ngx-monaco-editor> + </div> + +</mat-card> diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.scss new file mode 100644 index 0000000000..8e28d24ff1 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.scss @@ -0,0 +1,104 @@ +:host { + display: flex; + width: 100%; +} +.editor { + + $toolbar-height: 40px; + + &-card { + flex: 1; + padding: 0; + } + + &-title { + font-size: 14px; + } + + &-form { + display: block; + height: calc(100% - #{$toolbar-height}); + overflow-y: scroll; + padding: 20px; + width: 100%; + + &.editor-hidden { + display: none; + } + } + + &-loading { + align-items: center; + display: flex; + height: 100%; + justify-content: center; + position: absolute; + width: 100%; + z-index: 100; + + &__msg { + text-align: center; + } + &__progress-bar { + margin-top: 10px; + min-width: 120px; + } + } + + &-yaml-error { + align-items: center; + display: flex; + height: calc(100% - #{$toolbar-height}); + justify-content: center; + margin: -20px; + opacity: 0.7; + position: absolute; + width: 100%; + z-index: 100; + + &__msg { + display: flex; + flex-direction: row; + } + &__text { + line-height: 24px; + } + &__icon { + font-size: 32px; + height: 32px; + margin-right: 12px; + width: 32px; + } + } + + &-toolbar-buttons { + display: flex; + mat-button-toggle { + margin-left: 8px; + } + } + + &-spacer { + flex: 1 1 auto; + } + + &-monaco { + display: block; + + &.editor-hidden { + display: none; + } + } + + &-monaco-edit { + position: absolute; + } + + &-menu-divider { + margin: 4px 0; + } +} + +.mat-card.editor-card>:first-child { + margin: 0; +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.spec.ts new file mode 100644 index 0000000000..8dc9906951 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.spec.ts @@ -0,0 +1,41 @@ +import { HttpClient, HttpClientModule, HttpHandler } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { createBasicStoreModule } from '@stratosui/store/testing'; + +import { ConfirmationDialogService } from '../../../../../../core/src/shared/components/confirmation-dialog.service'; +import { MDAppModule } from './../../../../../../core/src/core/md.module'; +import { ChartValuesEditorComponent } from './chart-values-editor.component'; + +describe('ChartValuesEditorComponent', () => { + let component: ChartValuesEditorComponent; + let fixture: ComponentFixture<ChartValuesEditorComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ChartValuesEditorComponent ], + providers: [ + HttpClient, + HttpHandler, + ConfirmationDialogService, + ], + imports: [ + MDAppModule, + HttpClientModule, + HttpClientTestingModule, + createBasicStoreModule(), + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ChartValuesEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.theme.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.theme.scss new file mode 100644 index 0000000000..fa694c22cd --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.theme.scss @@ -0,0 +1,65 @@ +@mixin app-chart-values-editor-theme($theme, $app-theme) { + + $background: map-get($theme, background); + $foreground: map-get($theme, foreground); + + $app-background: map-get($app-theme, app-background-color); + + // Fixes some layout issues with Angular Json Schema Form due to use of Angular flex-layout + + // Also tweaks sizing and spacing of elements + + $vert-padding: 10px; + $toolbar-icon-size: 16px; + $toolbar-item-height: 24px; + $toolbar-item-font-size: 12px; + + // We discourage use of !important, but this is only way to override + // the angular flex settings in the ajsf library - otherwise the layout is wrong + .form-flex-column { + flex-flow: column !important; + } + + .legend { + font-size: 14px; + padding: $vert-padding 0; + } + + .editor-loading, .editor-yaml-error { + background-color: $app-background; + } + + // Make controls in the editor toolbar smaller + .editor-toolbar { + height: 40px; + + .mat-button-toggle-button { + display: flex; + } + .mat-button-toggle-label-content { + font-size: $toolbar-item-font-size; + line-height: $toolbar-item-height; + padding: 0 8px; + + .mat-icon { + font-size: $toolbar-icon-size; + height: $toolbar-icon-size; + width: $toolbar-icon-size; + } + } + .mat-button { + font-size: $toolbar-item-font-size; + line-height: $toolbar-item-height; + padding: 0 12px; + } + + } + + // Override hover color for context menu to align with Stratos theme + // Monaco doesn't seem to expose this as a theme colour + .monaco-editor .monaco-menu .action-item.focused a.action-menu-item { + background: mat-color($background, 'hover') !important; + color: mat-color($foreground, 'text') !important; + } + +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts new file mode 100644 index 0000000000..9b5f28c925 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts @@ -0,0 +1,427 @@ +import { HttpClient } from '@angular/common/http'; +import { AfterViewInit, Component, ElementRef, Input, OnDestroy, OnInit, Renderer2, ViewChild } from '@angular/core'; +import { JsonSchemaFormComponent } from '@cfstratos/ajsf-core'; +import * as yaml from 'js-yaml'; +import { BehaviorSubject, combineLatest, fromEvent, Observable, of, Subscription } from 'rxjs'; +import { catchError, debounceTime, filter, map, startWith, tap } from 'rxjs/operators'; + +import { ConfirmationDialogConfig } from '../../../../../../core/src/shared/components/confirmation-dialog.config'; +import { ConfirmationDialogService } from '../../../../../../core/src/shared/components/confirmation-dialog.service'; +import { ThemeService } from './../../../../../../store/src/theme.service'; +import { diffObjects } from './diffvalues'; +import { generateJsonSchemaFromObject } from './json-schema-generator'; + + +export interface ChartValuesConfig { + + // URL of the JSON Schema for the chart values + schemaUrl: string; + + // URL of the Chart Values + valuesUrl: string; + + // Values for the current release (optional) + releaseValues? : string +} + +// Height of the toolbar that sits above the editor conmponent +const TOOLBAR_HEIGHT = 40; + +// Editor modes - can be either using the form or the code editor +enum EditorMode { + CodeEditor = 'editor', + JSonSchemaForm = 'form', +} + +@Component({ + selector: 'app-chart-values-editor', + templateUrl: './chart-values-editor.component.html', + styleUrls: ['./chart-values-editor.component.scss'] +}) +export class ChartValuesEditorComponent implements OnInit, OnDestroy, AfterViewInit { + + @Input() set config(config: ChartValuesConfig) { + if (!!config) { + this.schemaUrl = config.schemaUrl; + this.valuesUrl = config.valuesUrl; + this.releaseValues = config.releaseValues; + this.init(); + } + } + + schemaUrl: string; + valuesUrl: string; + releaseValues: string; + + // Model for the editor - we set this once when the YAML support has been loaded + public model; + + // Editor mode - either 'editor' for the Monaco Code Editor or 'form' for the JSON Schema Form editor + public mode: EditorMode = EditorMode.CodeEditor; + + // Content shown in the code editor + public code = ''; + + // JSON Schema + public schema: any; + + public hasSchema = false; + + // Data shown in the form on load + public initialFormData = {}; + + // Data updated in the form as the user changes it + public formData = {}; + + // Is the YAML in the code editor invalid? + public yamlError = false; + + // Monaco Code Editor settings + public minimap = true; + public lineNumbers = true; + + // Chart Values - as both raw text (keeping comments) and parsed JSON + public chartValuesYaml: string; + public chartValues: any; + + // Default Monaco options + public editorOptions = { + automaticLayout: false, // We will resize the editor to fit the available space + contextmenu: false, // Turn off the right-click context menu + tabSize: 2, + }; + + // Monaco editor + public editor: any; + + // Observable - are we still loading resources? + public loading$: Observable<boolean>; + + // Observable for tracking if the Monaco editor has loaded + private monacoLoaded$ = new BehaviorSubject<boolean>(false); + + private resizeSub: Subscription; + private themeSub: Subscription; + + // Track whether the user changes the code in the text editor + private codeOnEnter: string; + + // Reference to the editor, so we can adjust its size to fit + @ViewChild('monacoEditor', {read: ElementRef}) monacoEditor: ElementRef; + + @ViewChild('schemaForm') schemaForm: JsonSchemaFormComponent; + + // Confirmation dialog - copy values + overwriteValuesConfirmation = new ConfirmationDialogConfig( + 'Overwrite Values?', + 'Are you sure you want to replace your values with those from values.yaml?', + 'Overwrite' + ); + + // Confirmation dialog - copy release values + overwriteReleaseValuesConfirmation = new ConfirmationDialogConfig( + 'Overwrite Values?', + 'Are you sure you want to replace your values with those from the release?', + 'Overwrite' + ); + + // Confirmation dialog - diff values + overwriteDiffValuesConfirmation = new ConfirmationDialogConfig( + 'Overwrite Values?', + 'Are you sure you want to replace your values with the diff with values.yaml?', + 'Overwrite' + ); + + // Confirmation dialog - clear values + clearValuesConfirmation = new ConfirmationDialogConfig( + 'Clear Values?', + 'Are you sure you want to clear the form values?', + 'Overwrite' + ); + + constructor( + private elRef: ElementRef, + private renderer: Renderer2, + private httpClient: HttpClient, + private themeService: ThemeService, + private confirmDialog: ConfirmationDialogService, + ) {} + + ngOnInit(): void { + // Listen for window resize and resize the editor when this happens + this.resizeSub = fromEvent(window, 'resize').pipe(debounceTime(150)).subscribe(event => this.resize()); + } + + private init() { + // Observabled for loading schema and values for the Chart + const schema$ = this.httpClient.get(this.schemaUrl).pipe(catchError(e => of(null))); + const values$: Observable<string | unknown> = this.httpClient.get(this.valuesUrl, { responseType: 'text' }).pipe( + catchError(e => of(null)) + ); + + // We need the schame, value sand the monaco editor to be all loaded before we're ready + this.loading$ = combineLatest(schema$, values$, this.monacoLoaded$).pipe( + filter(([schema, values, loaded]) => schema !== undefined && values !== undefined && loaded), + tap(([schema, values, loaded]) => { + this.schema = schema; + if (values !== null) { + this.chartValuesYaml = values as string; + this.chartValues = yaml.safeLoad(values); + // Set the form to the chart values initially, so if the user does nothing, they get the defaults + this.initialFormData = this.chartValues; + } + // Default to form if there is a schema + if (schema !== null) { + this.hasSchema = true; + this.mode = EditorMode.JSonSchemaForm; + // Register schema with the Monaco editor + this.registerSchema(this.schema); + } else { + // No Schema, so register an auto-generated schema from the Chart's values + this.registerSchema(generateJsonSchemaFromObject('Generated Schema', this.chartValues)); + + // Inherit the previous values if available (upgrade) + if (this.releaseValues) { + this.code = yaml.safeDump(this.releaseValues); + } + } + this.updateModel(); + }), + map(([schema, values, loaded]) => !loaded), + startWith(true) + ); + } + + ngAfterViewInit(): void { + this.resizeEditor(); + } + + ngOnDestroy(): void { + if (this.resizeSub) { + this.resizeSub.unsubscribe(); + } + if (this.themeSub) { + this.themeSub.unsubscribe(); + } + } + + // Toggle editor minimap on/off + toggleMinimap() { + this.minimap = !this.minimap; + this.editor.updateOptions({ minimap: { enabled: this.minimap } }); + } + + // Toggle editor line numbers on/off + toggleLineNumbers() { + this.lineNumbers = !this.lineNumbers; + this.editor.updateOptions({ lineNumbers: this.lineNumbers ? 'on' : 'off' }); + } + + // Store the update form data when the form changes + // AJSF two-way binding seems to cause issues + formChanged(data: any) { + this.formData = data; + } + + // The edit mode has changed (form or editor) + editModeChanged(mode) { + this.mode = mode.value; + + if (this.mode === EditorMode.CodeEditor) { + // Form -> Editor + // Only copy if there is not an error - otherwise keep the invalid yaml from the editor that needs fixing + if (!this.yamlError) { + this.code = this.getDiff(this.formData); + this.codeOnEnter = this.code; + } + + // Need to resize the editor, as it will be freshly shown + this.resizeEditor(); + } else { + // Editor -> Form + // Try and parse the YAML - if we can't this is an error, so we can't edit this back in the form + try { + if (this.codeOnEnter === this.code) { + return + } + + // Parse as json + const json = yaml.safeLoad(this.code || '{}'); + // Must be an object, otherwise it was not valid + if (typeof(json) !== 'object') { + throw new Error('Invalid YAML'); + } + this.yamlError = false; + const data = { + ...this.formData, + ...json + }; + this.initialFormData = data; + this.formData = data; + } catch (e) { + // The yaml in the code editor is invalid, so we can't marshal it back to json for the from editor + this.yamlError = true; + } + } + } + + // Called once the Monaco editor has loaded and then each time the model is update + // Store a reference to the editor and ensure the editor theme is synchronized with the Stratos theme + onMonacoInit(editor) { + this.editor = editor; + this.resize(); + + // Only load the YAML support once - when we set the model, onMonacoInit will et + if (this.model) { + return; + } + + // Load the YAML Language support - require is available as it will have been loaded by the Monaco vs loader + const req = (window as any).require; + req(['vs/language/yaml/monaco.contribution'], () => { + // Set the model now that YAML support is loaded - this will update the editor correctly + this.updateModel(); + this.monacoLoaded$.next(true); + }); + + // Watch for theme changes - set light/dark theme in the monaco editor as the Stratos theme changes + this.themeSub = this.themeService.getTheme().subscribe(theme => { + const monaco = (window as any).monaco; + const monacoTheme = (theme.styleName === 'dark-theme') ? 'vs-dark' : 'vs'; + monaco.editor.setTheme(monacoTheme); + }); + } + + private updateModel() { + this.model = { + language: 'yaml', + uri: this.getSchemaUri() + }; + } + + // Delayed resize of editor to fit + resizeEditor() { + setTimeout(() => this.resize(), 1); + } + + // Resize editor to fit + resize() { + // Return if resize before editor has been set + if (!this.editor) { + return; + } + + // Get width and height of the host element + const w = this.elRef.nativeElement.offsetWidth; + let h = this.elRef.nativeElement.offsetHeight; + + // Check if host element not visible (does not have a size) + if ((w === 0) && (h === 0)) { + return; + } + + // Remove height of toolbar (since this is incluced in the height of the host element) + h = h - TOOLBAR_HEIGHT; + + // Set the Monaco editor to the same size as the container + this.renderer.setStyle(this.monacoEditor.nativeElement, 'width', `${w}px`); + this.renderer.setStyle(this.monacoEditor.nativeElement, 'height', `${h}px`); + + // Ask Monaco to layout again with its new size + this.editor.layout(); + } + + // Get an absolute URI for the Schema - it is not fetched, just used as a reference + // schemaUrl is a relative URL - e.g. /p1/v1/chartsvc.... + getSchemaUri(): string { + return `https://stratos.app/schemas${this.schemaUrl}`; + } + + // Register the schema with the Monaco editor + // Reference: https://github.com/pengx17/monaco-yaml/blob/master/examples/umd/index.html#L69 + registerSchema(schema: any) { + const monaco = (window as any).monaco; + monaco.languages.yaml.yamlDefaults.setDiagnosticsOptions({ + enableSchemaRequest: true, + hover: true, + completion: true, + validate: true, + format: true, + schemas: [ + { + uri: this.getSchemaUri(), + fileMatch: [this.getSchemaUri()], + schema + } + ] + }); + } + + public getValues(): any { + return (this.mode === EditorMode.JSonSchemaForm) ? this.formData : yaml.safeLoad(this.code); + } + + public copyValues() { + const confirm = this.mode === EditorMode.JSonSchemaForm || this.mode === EditorMode.CodeEditor && this.code.length > 0; + if (confirm) { + this.confirmDialog.open(this.overwriteValuesConfirmation, () => { + this.doCopyValues(); + }); + } else { + this.doCopyValues(); + } + } + + // Copy the chart values into either the form or the code editor, depending on the current mode + private doCopyValues() { + if (this.mode === EditorMode.JSonSchemaForm) { + this.initialFormData = this.chartValues; + } else { + // Use the raw Yaml, so we keep comments and formatting + this.code = this.chartValuesYaml; + } + } + + public copyReleaseValues() { + const confirm = this.mode === EditorMode.JSonSchemaForm || this.mode === EditorMode.CodeEditor && this.code.length > 0; + if (confirm) { + this.confirmDialog.open(this.overwriteReleaseValuesConfirmation, () => { + this.doCopyReleaseValues(); + }); + } else { + this.doCopyReleaseValues(); + } + } + + // Copy the release values into either the form or the code editor, depending on the current mode + private doCopyReleaseValues() { + if (this.mode === EditorMode.JSonSchemaForm) { + this.initialFormData = this.releaseValues; + } else { + this.code = yaml.safeDump(this.releaseValues); + } + } + + // Reset the form values + clearFormValues() { + this.confirmDialog.open(this.clearValuesConfirmation, () => { + this.initialFormData = {}; + }); + } + + // Update the code editor to only show the YAML that contains the differences with the values.yaml + diff() { + this.confirmDialog.open(this.overwriteDiffValuesConfirmation, () => { + const userValues = yaml.safeLoad(this.code); + this.code = this.getDiff(userValues); + }); + } + + getDiff(userValues: any): string { + let code = yaml.safeDump(diffObjects(userValues, this.chartValues)); + if (code.trim() === '{}') { + code = ''; + } + return code + } +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/diffvalues.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/diffvalues.ts new file mode 100644 index 0000000000..4fc2d15946 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/diffvalues.ts @@ -0,0 +1,50 @@ +// Helper for diffing user values and chart values + +function arraysAreEqual(a1: any[], a2: any[]): boolean { + if (a1.length !== a2.length) { + return false + } + + for (let i=0; i<a1.length; i++) { + // Compare each item in the array + if (typeof(a1[i] === 'object')) { + const diff = diffObjects(a1[i], a2[i]); + if (Object.keys(diff).length !== 0) { + return false; + } + } else if (a1[i] !== a2[i]) { + return false; + } + } + return true; +} + +// NOTE: This is a one-way diff only +// diffObjects is main export - diffs two objects and returns only the diffrence +export function diffObjects(src: any, dest: any): any { + if (!src) { + return {}; + } + Object.keys(src).forEach(key => { + if (typeof(src[key]) === typeof(dest[key])) { + if(src[key] === null && dest[key] === null) { + delete src[key]; + } else if(Array.isArray(src[key])) { + // Array + if (arraysAreEqual(src[key], dest[key])) { + delete src[key]; + } + } else if (typeof(src[key]) === 'object') { + // Object + diffObjects(src[key], dest[key]); + if (src[key] && Object.keys(src[key]).length === 0) { + delete src[key]; + } + } else if (src[key] === dest[key]) { + // Value + delete src[key]; + } + } + }); + return src; +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/json-schema-generator.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/json-schema-generator.ts new file mode 100644 index 0000000000..531d3aa985 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/json-schema-generator.ts @@ -0,0 +1,288 @@ +// Generate a JSON Schema from an object +// This code incorporates the library: https://github.com/nijikokun/generate-schema/blob/master/src/schemas/json.js +// It is modified for Typescript and to mark all properties as not required + +// Reference: https://github.com/stephenhandley/type-of-is/blob/master/index.js +// Modified for Typescript + +const BUILT_IN_TYPES = [ + Object, + Function, + Array, + String, + Boolean, + Number, + Date, + RegExp, + Error +]; + +const _toString = ({}).toString; + +function isBuiltIn(_constructor): boolean { + for (const bit of BUILT_IN_TYPES) { + if (bit === _constructor) { + return true; + } + } + return false; +}; + +function of(obj) { + if ((obj === null) || (obj === undefined)) { + return obj; + } else { + return obj.constructor; + } +} + +function stringType(obj) { + // [object Blah] -> Blah + const stype = _toString.call(obj).slice(8, -1); + if ((obj === null) || (obj === undefined)) { + return stype.toLowerCase(); + } + + const ctype = of(obj); + if (ctype && !isBuiltIn(ctype)) { + return ctype.name; + } else { + return stype; + } +}; + +// Reference: https://github.com/nijikokun/generate-schema/blob/master/src/schemas/json.js + + +const DRAFT = 'http://json-schema.org/draft-04/schema#' + +function getPropertyFormat(value) { + const type = stringType(value).toLowerCase() + + if (type === 'date') return 'date-time' + return null +} + +function getPropertyType(value) { + const type = stringType(value).toLowerCase() + + if (type === 'number') return Number.isInteger(value) ? 'integer' : type + if (type === 'date') return 'string' + if (type === 'regexp') return 'string' + if (type === 'function') return 'string' + return type +} + +function getUniqueKeys(a, b, c) { + a = Object.keys(a) + b = Object.keys(b) + c = c || [] + + let value + let cIndex + let aIndex + + for (let keyIndex = 0, keyLength = b.length; keyIndex < keyLength; keyIndex++) { + value = b[keyIndex] + aIndex = a.indexOf(value) + cIndex = c.indexOf(value) + + if (aIndex === -1) { + if (cIndex !== -1) { + // Value is optional, it doesn't exist in A but exists in B(n) + c.splice(cIndex, 1) + } + } else if (cIndex === -1) { + // Value is required, it exists in both B and A, and is not yet present in C + c.push(value) + } + } + + return c +} + +function processArray(array, output?, nested?: boolean) { + let format + let oneOf + let type + + if (nested && output) { + output = { items: output } + } else { + output = output || {} + output.type = getPropertyType(array) + output.items = output.items || {} + type = output.items.type || null + } + + // Determine whether each item is different + for (let arrIndex = 0, arrLength = array.length; arrIndex < arrLength; arrIndex++) { + const elementType = getPropertyType(array[arrIndex]) + const elementFormat = getPropertyFormat(array[arrIndex]) + + if (type && elementType !== type) { + output.items.oneOf = [] + oneOf = true + break + } else { + type = elementType + format = elementFormat + } + } + + // Setup type otherwise + if (!oneOf && type) { + output.items.type = type + if (format) { + output.items.format = format + } + } else if (oneOf && type !== 'object') { + output.items = { + oneOf: [{ type }], + required: false + } + } + + // Process each item depending + if (typeof output.items.oneOf !== 'undefined' || type === 'object') { + for (let itemIndex = 0, itemLength = array.length; itemIndex < itemLength; itemIndex++) { + const value = array[itemIndex] + const itemType = getPropertyType(value) + const itemFormat = getPropertyFormat(value) + let arrayItem + if (itemType === 'object') { + if (output.items.properties) { + output.items.required = false + } + arrayItem = processObject(value, oneOf ? {} : output.items.properties, true) + } else if (itemType === 'array') { + arrayItem = processArray(value, oneOf ? {} : output.items.properties, true) + } else { + arrayItem = {} + arrayItem.type = itemType + if (itemFormat) { + arrayItem.format = itemFormat + } + } + if (oneOf) { + const childType = stringType(value).toLowerCase() + const tempObj: any = {}; + if (!arrayItem.type && childType === 'object') { + tempObj.properties = arrayItem + tempObj.type = 'object' + arrayItem = tempObj + } + output.items.oneOf.push(arrayItem) + } else { + if (output.items.type !== 'object') { + continue; + } + output.items.properties = arrayItem + } + } + } + return nested ? output.items : output +} + +function processObject(object: any, output?: any, nested?: boolean) { + if (nested && output) { + output = { properties: output } + } else { + output = output || {} + output.type = getPropertyType(object) + output.properties = output.properties || {} + output.required = [] + } + + for(const key of Object.keys(object)) { + const value = object[key] + let typ = getPropertyType(value) + const format = getPropertyFormat(value) + + typ = typ === 'undefined' ? 'null' : typ + + if (typ === 'object') { + output.properties[key] = processObject(value, output.properties[key]) + continue + } + + if (typ === 'array') { + output.properties[key] = processArray(value, output.properties[key]) + continue + } + + if (output.properties[key]) { + const entry = output.properties[key] + const hasTypeArray = Array.isArray(entry.type) + + // When an array already exists, we check the existing + // type array to see if it contains our current property + // type, if not, we add it to the array and continue + if (hasTypeArray && entry.type.indexOf(typ) < 0) { + entry.type.push(typ) + } + + // When multiple fields of differing types occur, + // json schema states that the field must specify the + // primitive types the field allows in array format. + if (!hasTypeArray && entry.type !== typ) { + entry.type = [entry.type, typ] + } + + continue + } + + output.properties[key] = {} + output.properties[key].type = typ + + if (format) { + output.properties[key].format = format + } + } + + return nested ? output.properties : output +} + + +export function generateJsonSchemaFromObject(title, object) { + let processOutput + const output: any = { + $schema: DRAFT + } + + // Determine title exists + if (typeof title !== 'string') { + object = title + title = undefined + } else { + output.title = title + } + + // Set initial object type + output.type = stringType(object).toLowerCase() + + // Process object + if (output.type === 'object') { + processOutput = processObject(object) + output.type = processOutput.type + output.properties = processOutput.properties + + // For a generated schema, nothing is marked as required + // This is a modification to the library + output.required = false; + } + + if (output.type === 'array') { + processOutput = processArray(object) + output.type = processOutput.type + output.items = processOutput.items + + if (output.title) { + output.items.title = output.title + output.title += ' Set' + } + } + + // Output + return output +} \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.html similarity index 70% rename from src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.html rename to src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.html index 4aec9bb603..000f286a25 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.html @@ -36,17 +36,6 @@ </form> </app-step> <app-step [title]="'Overrides'" [onNext]="submit" [finishButtonText]="'Install'" [onEnter]="onEnterOverrides"> - <form [formGroup]="overrides" class="stepper-form overrides_form"> - <div class="helm-create-release__heading"> - <h3 class="helm-create-release__title">Enter YAML Value Overrides</h3> - <button (click)="useValuesYaml()" [disabled]="!valuesYaml" class="helm-create-release__button" mat-button - color="primary">Copy from values.yaml</button> - </div> - <mat-form-field [floatLabel]="'always'" class="overrides_form-field"> - <mat-label>Values</mat-label> - <textarea #overridesYamlTextArea class="overrides__yaml" matInput formControlName="values" name="Values" - spellcheck="false"></textarea> - </mat-form-field> - </form> + <app-chart-values-editor #editor [config]="config"></app-chart-values-editor> </app-step> </app-steppers> \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.scss new file mode 100644 index 0000000000..cf807defaa --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.scss @@ -0,0 +1,18 @@ +:host { + flex: 1; +} + +.helm-create-release { + &__heading { + align-items: center; + display: flex; + } + + &__title { + flex: 1; + font-size: 14px; + } + &__button { + height: 36px; + } +} diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.spec.ts similarity index 61% rename from src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.spec.ts rename to src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.spec.ts index c214981990..f2533edc8b 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.spec.ts @@ -3,15 +3,15 @@ import { HttpClient } from '@angular/common/http'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { ConfirmationDialogService } from '../../../../../core/src/shared/components/confirmation-dialog.service'; -import { TabNavService } from '../../../../../core/tab-nav.service'; -import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; -import { InternalEventMonitorFactory } from '../../../../../store/src/monitors/internal-event-monitor.factory'; -import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; -import { KubernetesBaseTestModules } from '../../kubernetes/kubernetes.testing.module'; -import { MockChartService } from '../monocular/shared/services/chart.service.mock'; -import { ChartsService } from '../monocular/shared/services/charts.service'; -import { ConfigService } from '../monocular/shared/services/config.service'; +import { ConfirmationDialogService } from '../../../../../../core/src/shared/components/confirmation-dialog.service'; +import { TabNavService } from '../../../../../../core/tab-nav.service'; +import { EntityMonitorFactory } from '../../../../../../store/src/monitors/entity-monitor.factory.service'; +import { InternalEventMonitorFactory } from '../../../../../../store/src/monitors/internal-event-monitor.factory'; +import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; +import { MockChartService } from '../../../helm/monocular/shared/services/chart.service.mock'; +import { ChartsService } from '../../../helm/monocular/shared/services/charts.service'; +import { ConfigService } from '../../../helm/monocular/shared/services/config.service'; +import { KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { CreateReleaseComponent } from './create-release.component'; describe('CreateReleaseComponent', () => { @@ -52,8 +52,6 @@ describe('CreateReleaseComponent', () => { }); it('should be created', () => { - httpMock.expectOne('/pp/v1/chartsvc/v1/assets/undefined/undefined/versions/undefined/values.yaml'); - expect(component).toBeTruthy(); }); diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.ts similarity index 73% rename from src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.ts rename to src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.ts index 372ea2e145..fcc3ccff84 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/create-release/create-release.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.ts @@ -1,27 +1,26 @@ -import { HttpClient } from '@angular/common/http'; import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { MatTextareaAutosize } from '@angular/material/input'; import { ActivatedRoute } from '@angular/router'; import { BehaviorSubject, combineLatest, Observable, of, Subscription } from 'rxjs'; import { distinctUntilChanged, filter, first, map, pairwise, startWith, switchMap } from 'rxjs/operators'; -import { EndpointsService } from '../../../../../core/src/core/endpoints.service'; -import { safeUnsubscribe } from '../../../../../core/src/core/utils.service'; -import { ConfirmationDialogConfig } from '../../../../../core/src/shared/components/confirmation-dialog.config'; -import { ConfirmationDialogService } from '../../../../../core/src/shared/components/confirmation-dialog.service'; -import { StepOnNextFunction, StepOnNextResult } from '../../../../../core/src/shared/components/stepper/step/step.component'; -import { RequestInfoState } from '../../../../../store/src/reducers/api-request-reducer/types'; -import { kubeEntityCatalog } from '../../kubernetes/kubernetes-entity-catalog'; -import { KUBERNETES_ENDPOINT_TYPE } from '../../kubernetes/kubernetes-entity-factory'; -import { KubernetesNamespace } from '../../kubernetes/store/kube.types'; -import { getFirstChartUrl } from '../../kubernetes/workloads/workload.utils'; -import { helmEntityCatalog } from '../helm-entity-catalog'; -import { createMonocularProviders } from '../monocular/stratos-monocular-providers.helpers'; -import { getMonocularEndpoint, stratosMonocularEndpointGuid } from '../monocular/stratos-monocular.helper'; -import { HelmInstallValues } from '../store/helm.types'; -import { ChartsService } from './../monocular/shared/services/charts.service'; -import { HelmChartReference } from './../store/helm.types'; +import { EndpointsService } from '../../../../../../core/src/core/endpoints.service'; +import { safeUnsubscribe } from '../../../../../../core/src/core/utils.service'; +import { + StepOnNextFunction, + StepOnNextResult, +} from '../../../../../../core/src/shared/components/stepper/step/step.component'; +import { RequestInfoState } from '../../../../../../store/src/reducers/api-request-reducer/types'; +import { helmEntityCatalog } from '../../../helm/helm-entity-catalog'; +import { ChartsService } from '../../../helm/monocular/shared/services/charts.service'; +import { createMonocularProviders } from '../../../helm/monocular/stratos-monocular-providers.helpers'; +import { getMonocularEndpoint, stratosMonocularEndpointGuid } from '../../../helm/monocular/stratos-monocular.helper'; +import { HelmChartReference, HelmInstallValues } from '../../../helm/store/helm.types'; +import { kubeEntityCatalog } from '../../kubernetes-entity-catalog'; +import { KUBERNETES_ENDPOINT_TYPE } from '../../kubernetes-entity-factory'; +import { KubernetesNamespace } from '../../store/kube.types'; +import { getFirstChartUrl } from '../workload.utils'; +import { ChartValuesConfig, ChartValuesEditorComponent } from './../chart-values-editor/chart-values-editor.component'; @Component({ selector: 'app-create-release', @@ -33,13 +32,6 @@ import { HelmChartReference } from './../store/helm.types'; }) export class CreateReleaseComponent implements OnInit, OnDestroy { - // Confirmation dialog - overwriteValuesConfirmation = new ConfirmationDialogConfig( - 'Overwrite Values?', - 'Are you sure you want to replace your values with those from values.yaml?', - 'Overwrite' - ); - // isLoading$ = observableOf(false); paginationStateSub: Subscription; @@ -49,47 +41,33 @@ export class CreateReleaseComponent implements OnInit, OnDestroy { details: FormGroup; namespaces$: Observable<string[]>; - overrides: FormGroup; private endpointChanged = new BehaviorSubject(null); @ViewChild('releaseNameInputField', { static: true }) releaseNameInputField: ElementRef; - @ViewChild('overridesYamlTextArea', { static: true }) overridesYamlTextArea: ElementRef; - @ViewChild(MatTextareaAutosize, { static: false }) overridesYamlAutosize: MatTextareaAutosize; - - public valuesYaml = ''; + @ViewChild('editor', { static: true }) editor: ChartValuesEditorComponent; private subs: Subscription[] = []; private createdNamespace = false; private chart: HelmChartReference; + public config: ChartValuesConfig; constructor( private route: ActivatedRoute, public endpointsService: EndpointsService, - private httpClient: HttpClient, - private confirmDialog: ConfirmationDialogService, private chartsService: ChartsService, ) { const chart = this.route.snapshot.params as HelmChartReference; this.cancelUrl = `/monocular/charts/${getMonocularEndpoint(this.route)}/${chart.repo}/${chart.name}/${chart.version}`; this.chart = chart; - this.setupDetailsStep(); - - this.overrides = new FormGroup({ - values: new FormControl('') - }); - - // Fetch the values.yaml for the Chart - const valuesYamlUrl = `/pp/v1/chartsvc/v1/assets/${chart.repo}/${chart.name}/versions/${chart.version}/values.yaml`; + this.config = { + valuesUrl: `/pp/v1/chartsvc/v1/assets/${chart.repo}/${chart.name}/versions/${chart.version}/values.yaml`, + schemaUrl: `/pp/v1/chartsvc/v1/assets/${chart.repo}/${chart.name}/versions/${chart.version}/values.schema.json`, + } - this.httpClient.get(valuesYamlUrl, { responseType: 'text' }) - .subscribe(response => { - this.valuesYaml = response; - }, err => { - console.error('Failed to fetch chart values: ', err.message || err); - }); + this.setupDetailsStep(); } private setupDetailsStep() { @@ -186,21 +164,6 @@ export class CreateReleaseComponent implements OnInit, OnDestroy { return lowerCase.length ? namespaces.filter(ns => ns.toLowerCase().indexOf(lowerCase) >= 0) : namespaces; } - public useValuesYaml() { - if (this.overrides.value.values.length !== 0) { - this.confirmDialog.open(this.overwriteValuesConfirmation, () => { - this.replaceWithValuesYaml(); - }); - - } else { - this.replaceWithValuesYaml(); - } - } - - private replaceWithValuesYaml() { - this.overrides.controls.values.setValue(this.valuesYaml, { onlySelf: true }); - } - ngOnInit() { // Auto select endpoint if there is only one this.kubeEndpoints$.pipe(first()).subscribe(ep => { @@ -214,11 +177,9 @@ export class CreateReleaseComponent implements OnInit, OnDestroy { }); } + // Ensure the editor is resized when the overrides step becomes visible onEnterOverrides = () => { - setTimeout(() => { - // this.overridesYamlAutosize.resizeToFitContent(true); - this.overridesYamlTextArea.nativeElement.focus(); - }, 1); + this.editor.resizeEditor(); }; submit: StepOnNextFunction = () => { @@ -261,7 +222,7 @@ export class CreateReleaseComponent implements OnInit, OnDestroy { // Build the request body const values: HelmInstallValues = { ...this.details.value, - ...this.overrides.value, + values: this.editor.getValues(), chart: { name: this.route.snapshot.params.name, repo: this.route.snapshot.params.repo, diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts index 0a8fbac769..a3f7b44bb4 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts @@ -122,6 +122,7 @@ export class HelmReleaseSummaryTabComponent implements OnDestroy { this.hasUpgrade$ = this.helmReleaseHelper.hasUpgrade().pipe(map(v => v ? v.version : null)); + // Can upgrade if the Chart is available this.canUpgrade$ = this.helmReleaseHelper.hasUpgrade(true).pipe(map(v => !!v)); this.resources$ = combineLatest( diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.html index dae9721887..4a3ab48abc 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.html @@ -2,29 +2,11 @@ Upgrade Workload </app-page-header> <app-steppers [cancel]="cancelUrl"> - <app-step title="Version" [valid]="validate$ | async"> + <app-step title="Version" [valid]="validate$ | async" [onNext]="onNext"> <app-list class="workload-upgrade__list" *ngIf="listConfig" [listConfig]="listConfig"></app-list> </app-step> - <app-step title="Overrides" [onNext]="doUpgrade" finishButtonText="Upgrade"> - <div class="workload-upgrade__form"> - <form [formGroup]="overrides" class="stepper-form overrides_form"> - <div class="workload-upgrade__heading"> - <h3 class="workload-upgrade__title">Enter YAML Value Overrides</h3> - <!-- - <button (click)="useValuesYaml()" [disabled]="!valuesYaml" class=""workload-upgrade__button" mat-button - color="primary">Copy from values.yaml</button> - --> - </div> - <mat-form-field [floatLabel]="'always'" class="overrides_form-field"> - <mat-label>Values</mat-label> - <textarea #overridesYamlTextArea class="overrides__yaml" matInput formControlName="values" name="Values" - spellcheck="false"></textarea> - </mat-form-field> - </form> - <!-- - <mat-slide-toggle (change)="toggleAdvancedOptions()">Show Advanced Options</mat-slide-toggle> - --> - </div> + <app-step title="Overrides" [onNext]="doUpgrade" finishButtonText="Upgrade" [onEnter]="onEnterOverrides"> + <app-chart-values-editor #editor [config]="config"></app-chart-values-editor> </app-step> <!-- Add support for recreate pods and other advanced options in the future @@ -33,5 +15,4 @@ <h3 class="workload-upgrade__title">Enter YAML Value Overrides</h3> </app-step> --> - </app-steppers> \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts index 328ded2c6f..2502205f7e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts @@ -1,14 +1,18 @@ -import { Component } from '@angular/core'; -import { FormControl, FormGroup } from '@angular/forms'; +import { Component, ViewChild } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; import { Observable, of } from 'rxjs'; -import { filter, first, map, pairwise } from 'rxjs/operators'; +import { filter, first, map, pairwise, tap } from 'rxjs/operators'; -import { StepComponent, StepOnNextFunction } from '../../../../../../core/src/shared/components/stepper/step/step.component'; +import { + StepComponent, + StepOnNextFunction, + StepOnNextResult, +} from '../../../../../../core/src/shared/components/stepper/step/step.component'; import { ActionState } from '../../../../../../store/src/reducers/api-request-reducer/types'; import { stratosMonocularEndpointGuid } from '../../../helm/monocular/stratos-monocular.helper'; import { HelmUpgradeValues, MonocularVersion } from '../../../helm/store/helm.types'; +import { ChartValuesConfig, ChartValuesEditorComponent } from '../chart-values-editor/chart-values-editor.component'; import { HelmReleaseHelperService } from '../release/tabs/helm-release-helper.service'; import { HelmReleaseGuid } from '../workload.types'; import { getFirstChartUrl } from '../workload.utils'; @@ -34,11 +38,14 @@ import { ReleaseUpgradeVersionsListConfig } from './release-version-list-config' }) export class UpgradeReleaseComponent { + @ViewChild('editor', { static: true }) editor: ChartValuesEditorComponent; + public cancelUrl; public listConfig: ReleaseUpgradeVersionsListConfig; public validate$: Observable<boolean>; private version: MonocularVersion; - public overrides: FormGroup; + + public config: ChartValuesConfig; private monocularEndpointId: string; @@ -52,11 +59,6 @@ export class UpgradeReleaseComponent { this.cancelUrl = `/workloads/${this.helper.guid}`; - // Form for overrides step (Helm Values) - this.overrides = new FormGroup({ - values: new FormControl('') - }); - this.helper.hasUpgrade(true).pipe( filter(c => !!c), first() @@ -80,6 +82,32 @@ export class UpgradeReleaseComponent { }); } + // Ensure the editor is resized when the overrides step becomes visible + onEnterOverrides = () => { + this.editor.resizeEditor(); + } + + // Update the editor with the chosen version when the user moves to the next step + onNext = (): Observable<StepOnNextResult> => { + const chart = this.version.relationships.chart.data; + const version = this.version.attributes.version; + + // Fetch the release metadata so that we have the values used to install the current release + return this.helper.release$.pipe( + first(), + tap(release => { + this.config = { + schemaUrl: `/pp/v1/chartsvc/v1/assets/${chart.repo.name}/${chart.name}/versions/${version}/values.schema.json`, + valuesUrl: `/pp/v1/chartsvc/v1/assets/${chart.repo.name}/${chart.name}/versions/${version}/values.yaml`, + releaseValues: release.config + }; + }), + map(() => { + return { success: true } + }) + ); + } + // Hide/show the advanced options step toggleAdvancedOptions() { this.showAdvancedOptions = !this.showAdvancedOptions; @@ -93,7 +121,7 @@ export class UpgradeReleaseComponent { // Add the chart url into the values const values: HelmUpgradeValues = { - values: this.overrides.controls.values.value, + values: this.editor.getValues(), restartPods: false, chart: { name: this.version.relationships.chart.data.name, diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts index 44ab5ce4bb..7359657da2 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts @@ -1,10 +1,14 @@ import { CommonModule, DatePipe } from '@angular/common'; import { NgModule } from '@angular/core'; +import { MaterialDesignFrameworkModule } from '@cfstratos/ajsf-material'; import { NgxGraphModule } from '@swimlane/ngx-graph'; +import { MonacoEditorModule, NgxMonacoEditorConfig } from 'ngx-monaco-editor'; import { CoreModule } from '../../../../../core/src/core/core.module'; import { SharedModule } from '../../../../../core/src/shared/shared.module'; import { KubernetesModule } from '../kubernetes.module'; +import { ChartValuesEditorComponent } from './chart-values-editor/chart-values-editor.component'; +import { CreateReleaseComponent } from './create-release/create-release.component'; import { HelmReleaseCardComponent } from './list-types/helm-release-card/helm-release-card.component'; import { HelmReleaseTabBaseComponent } from './release/helm-release-tab-base/helm-release-tab-base.component'; import { @@ -25,6 +29,12 @@ import { WorkloadsRouting } from './workloads.routing'; import { HelmReleaseHistoryTabComponent } from './release/tabs/helm-release-history-tab/helm-release-history-tab.component'; import { WorkloadLiveReloadComponent } from './release/workload-live-reload/workload-live-reload.component'; +// Default config for the Monaco edfior +const monacoConfig: NgxMonacoEditorConfig = { + baseUrl: '/core/assets', // configure base path for monaco editor + defaultOptions: { scrollBeyondLastLine: false } +}; + @NgModule({ imports: [ CoreModule, @@ -34,6 +44,8 @@ import { WorkloadLiveReloadComponent } from './release/workload-live-reload/work WorkloadsRouting, NgxGraphModule, KubernetesModule, + MaterialDesignFrameworkModule, + MonacoEditorModule.forRoot(monacoConfig), ], declarations: [ HelmReleasesTabComponent, @@ -46,6 +58,8 @@ import { WorkloadLiveReloadComponent } from './release/workload-live-reload/work HelmReleaseResourceGraphComponent, HelmReleaseCardComponent, HelmReleaseAnalysisTabComponent, + ChartValuesEditorComponent, + CreateReleaseComponent, WorkloadLiveReloadComponent, UpgradeReleaseComponent, HelmReleaseHistoryTabComponent, diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.routing.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.routing.ts index a9773315f8..5b91cda06a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.routing.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.routing.ts @@ -2,6 +2,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { NgxChartsModule } from '@swimlane/ngx-charts'; +import { CreateReleaseComponent } from './create-release/create-release.component'; import { HelmReleaseTabBaseComponent } from './release/helm-release-tab-base/helm-release-tab-base.component'; import { HelmReleaseAnalysisTabComponent, @@ -27,6 +28,8 @@ const routes: Routes = [ component: HelmReleasesTabComponent, pathMatch: 'full', }, + { pathMatch: 'full', path: 'install/:endpoint/:repo/:name/:version', component: CreateReleaseComponent }, + { pathMatch: 'full', path: 'install/:endpoint/:repo/:name', component: CreateReleaseComponent }, { // guid = kube endpoint path: ':guid/upgrade', From a9353fad00a64c6ef928561de1b9531bc0779e54 Mon Sep 17 00:00:00 2001 From: Richard Cox <richard-cox@users.noreply.github.com> Date: Tue, 15 Sep 2020 15:52:30 +0100 Subject: [PATCH 618/648] Merge upstream (#468) * Ensure filename/no filename text in connect by file dialog is aligned (#4474) - needs porting back * Add typed entity access and `custom-src` removal to change log (#4496) * Fixes #4335: Create Stratos static web site with better documentation (#4446) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura <mjura@users.noreply.github.com> * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] <support@dependabot.com> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * * Review comments fix * Fix broken link to an image in frontend extensions doc * Final tweaks Co-authored-by: Neil MacDougall <neil.macdougall@suse.com> Co-authored-by: Neil MacDougall <nmacdougall@suse.com> Co-authored-by: Richard Cox <richard-cox@users.noreply.github.com> Co-authored-by: Neil MacDougall <nwmac@users.noreply.github.com> Co-authored-by: Michal Jura <mjura@users.noreply.github.com> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Richard Cox <richard.cox@suse.com> * Fix and update customization docs (#4478) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura <mjura@users.noreply.github.com> * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] <support@dependabot.com> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * Apply new theming process to acme example * First pass at customization docs, most of themeing done * Final pass at customization docs * Merge fixes Co-authored-by: Veerapuram Varadhan <v.varadhan@gmail.com> Co-authored-by: Neil MacDougall <neil.macdougall@suse.com> Co-authored-by: Neil MacDougall <nmacdougall@suse.com> Co-authored-by: Neil MacDougall <nwmac@users.noreply.github.com> Co-authored-by: Michal Jura <mjura@users.noreply.github.com> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update base README, point to website (#4488) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura <mjura@users.noreply.github.com> * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] <support@dependabot.com> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * Apply new theming process to acme example * First pass at customization docs, most of themeing done * Final pass at customization docs * Update base README, point to website Co-authored-by: Veerapuram Varadhan <v.varadhan@gmail.com> Co-authored-by: Neil MacDougall <neil.macdougall@suse.com> Co-authored-by: Neil MacDougall <nmacdougall@suse.com> Co-authored-by: Neil MacDougall <nwmac@users.noreply.github.com> Co-authored-by: Michal Jura <mjura@users.noreply.github.com> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update developer docs (#4489) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura <mjura@users.noreply.github.com> * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] <support@dependabot.com> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * Apply new theming process to acme example * First pass at customization docs, most of themeing done * Final pass at customization docs * Update base README, point to website * Update developer docs Still need an overhall, but updated to remove incorrect data and apply website context Co-authored-by: Veerapuram Varadhan <v.varadhan@gmail.com> Co-authored-by: Neil MacDougall <neil.macdougall@suse.com> Co-authored-by: Neil MacDougall <nmacdougall@suse.com> Co-authored-by: Neil MacDougall <nwmac@users.noreply.github.com> Co-authored-by: Michal Jura <mjura@users.noreply.github.com> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix docs (#4497) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura <mjura@users.noreply.github.com> * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] <support@dependabot.com> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * Apply new theming process to acme example * First pass at customization docs, most of themeing done * Final pass at customization docs * Update base README, point to website * Update developer docs Still need an overhall, but updated to remove incorrect data and apply website context * Fix PR template Co-authored-by: Veerapuram Varadhan <v.varadhan@gmail.com> Co-authored-by: Neil MacDougall <neil.macdougall@suse.com> Co-authored-by: Neil MacDougall <nmacdougall@suse.com> Co-authored-by: Neil MacDougall <nwmac@users.noreply.github.com> Co-authored-by: Michal Jura <mjura@users.noreply.github.com> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Ensure images dependent on techPreview flag are included in imagelist.txt (#4500) * Improve clean-symlinks script (#4509) * Add support for including breaking changes in the changelog * Improve clean-symlinks script * Update changelog.sh * Update changelog.sh * Remove trailing line * Remove tailing line * Change nginx ciphers and make configurable via helm chart values (#4507) * Change nginx ciphers and make configurable via helm chart values * Add support for including breaking changes in the changelog * Improve clean-symlinks script * Update changelog.sh * Update changelog.sh * Remove trailing line * Remove tailing line * Fix bug with cert path patching * Fig bug where protocols not patched * Update version to 4.0.1, add change log (#4514) * Update version to 4.0.1, add change log * Update package-lock * Fix deploy from gitlab using a group's repo (#4479) - only supported user based repos - fixes #4153 * Add additional time ranges to base metrics range selector (#4480) - fixes https://github.com/SUSE/stratos/issues/415 * Add some time saving comments to cf permissions checker (#4508) * Move tab-nav and xsrf module source files to the src folder (#4518) * Move tab-nav files * Move xsrf module * Fix GitHub branch limit (#4510) * Ensure we fetch all repo's and branches when deploying github apps * Ensure we only fetch branches once - SUSE/stratos vs suse/stratos - ensure branches are treated as 'local' lists * Increate page size for github commits and gitlab requests * Remove use of nodejs util module (#4521) * Remove use of nodejs util module * Fix comment typo * Remove dependencies between store and core that have crept in (#4517) * Remove dependencies between store and core that have crept in * Fix issue with unit tests * Import fixes and unit test fix * Fix store testing package (#4520) * Fix store testing package * Update public-api.ts Co-authored-by: Richard Cox <richard-cox@users.noreply.github.com> Co-authored-by: Richard Cox <richard.cox@suse.com> * Fix the position of the header guide array (#4524) - shown when there are no registered endpoints (and some incoming scenarios) - position is determined by location of associated button in header - position of button can change given visibility of other buttons (notification bell, endpoint backup, etc) - now check positiion after a delay, add fade in to mask delay * Metrics: Ensure trailing slashes are ignored when comparing URLs (#4527) * Remove imports of the form 'frontend/....' (#4519) * Move tab-nav files * Move xsrf module * Remove imports of the form 'frontend/...' * Move endpopints-health-check.ts (#4530) * Add UMD Ids to external modules in store package (#4522) * Add UMB Ids to external modules in store package * Add moment * Address PR feedback * Improvements & more checks to autoscaler scheduled date tests (#4535) - allow more time to skip between date and time values in date fields - add a check to - add checks to ensure we have the correct number of scheduled date rules * Remove api driven views (#4537) * Remove logger service and action history (#4538) * Remove logger service * Missed a few and removed action history as it was not being used * Remove action history * A few more * Fix for removed method * Add support for API keys (#4515) * Add backend support for API keys * Add last_used field to API keys * Use secure random value as key secret * Add tests for ListAPIKeys and AddAPIKey * Cover the rest of psql_apikeys.go with tests * Refactor the code a bit * Storing SQL queries in a struct should ensure that `datastore.ModifySQLStatement` gets called on all of them. * A wrapper func around `db.Exec` reduces copypasta. * Actually call `InitRepositoryProvider` for API keys package * Add route handler tests using gomock * Actually use skipper in xsrfMiddleware; minor clean-up * Update moment imports to remove warning when building library (#4534) * Update moment imports to remove warning when building library * Fix references to moment-timezone and markdown - contains some changes to e2e tests as well, might need to be reverted * Fix unit test * Fix `import *` in e2e tests Co-authored-by: Richard Cox <richard.cox@suse.com> * Fix Helm upgrade bug (#4544) * Fix Helm upgrade bug * Fix unit tests * Store api_keys.last_used in UTC (#4541) * Add UI for Stratos API Keys (#4523) * Add backend support for API keys * Add last_used field to API keys * Add base api keys page * Add basic api key entity framework (untested) * Add a basic api keys list (untested, need to wire in properties/columns + actions) * Fix entity type related issues * Add basic way to create api key * Wire in delete to list * Improve 'no api keys' ux * Final tidy up * Other fixes * Fix unit tests * Add 'Last Used' column to API keys list * Don't flash up 'no entries' when we haven't loaded api keys yet * Fix last used sorting - takes into account timezone - use cacheing to cater for often called sort * Fix unit test after changes in master * Fix after moment change * Remove now unrequired sorting of api key last used date via moment Co-authored-by: Ivan Kapelyukhin <ikapelyukhin@suse.com> * Add basic e2e tests for API Keys (#4536) * Add backend support for API keys * Add last_used field to API keys * Add base api keys page * Add basic api key entity framework (untested) * Add a basic api keys list (untested, need to wire in properties/columns + actions) * Fix entity type related issues * Add basic way to create api key * Wire in delete to list * Improve 'no api keys' ux * Final tidy up * Other fixes * Fix unit tests * Add basic e2e tests for api keys * Add 'Last Used' column to API keys list * Don't flash up 'no entries' when we haven't loaded api keys yet * Fix last used sorting - takes into account timezone - use cacheing to cater for often called sort * Fix unit test after changes in master * Fix after moment change * Beef up test, fix for initial empty state * Remove now unrequired sorting of api key last used date via moment Co-authored-by: Ivan Kapelyukhin <ikapelyukhin@suse.com> * Bump docusaurus version and add support for versioning (#4506) * Bump docusaurus version and versioning - Bump docusaurus to latest 2.0 version - Fix errors thrown up by new linting process - Add version support, setup 4.0.0 - Enable dark mode and fix in home screen * Move status_updates back into root project docs folder * Fix links - fix dead links - ensure all links are relative (so work when versioned) * Remove version 4.0.0 * WIP * WIP * Completed * Temporarily remove docsVersionDropdown until first version added to internal-versions * Update website docs * Build fix for temp situation where there's no versions * Add custom version of 'not latest released docs' message * Improve versioning and dark mode toggle - Add `All Versions page` - Conditionally include versions in drop down - Improve dark mode toggle icons * Temporarily remove dependent on versions - will be added back in when 4.0 sha is known * Update Versions Process * Add 4.0.0 * Fix in `build`/`serve` world - worked fine in `start` world... * Update 4.0.0 with temp version * Multiple improvements - Fix clean git repo before run - Swizzle docs version drop down nav bar item, move in custom code - Improve docs (latest version must appear in drop down) * Changes following review * Reduce font in version warning * API Keys: Make feature configurable for different user types (#4540) * Add a special type and parsing for the new config option * Add APIKeysEnabled to /info, check it in the middleware * Add test coverage for apiKeyMiddleware * Block API keys endpoints if disabled in the config; add tests * Fix failing test * Add API_KEYS_ENABLED to the Helm chart * Add JSON schema to the Helm chart * Add docs for UAA SSO user permissions management (#4554) * Bump docusaurus version and versioning - Bump docusaurus to latest 2.0 version - Fix errors thrown up by new linting process - Add version support, setup 4.0.0 - Enable dark mode and fix in home screen * Move status_updates back into root project docs folder * Fix links - fix dead links - ensure all links are relative (so work when versioned) * Remove version 4.0.0 * WIP * WIP * Completed * Temporarily remove docsVersionDropdown until first version added to internal-versions * Update website docs * Build fix for temp situation where there's no versions * Add custom version of 'not latest released docs' message * Improve versioning and dark mode toggle - Add `All Versions page` - Conditionally include versions in drop down - Improve dark mode toggle icons * Temporarily remove dependent on versions - will be added back in when 4.0 sha is known * Update Versions Process * Add 4.0.0 * Fix in `build`/`serve` world - worked fine in `start` world... * Update 4.0.0 with temp version * Add troubleshooting section for SSO * Update SSO auth troubleshooting section * Add basic developers guide for working with helm (#4511) * Bump docusaurus version and versioning - Bump docusaurus to latest 2.0 version - Fix errors thrown up by new linting process - Add version support, setup 4.0.0 - Enable dark mode and fix in home screen * Move status_updates back into root project docs folder * Fix links - fix dead links - ensure all links are relative (so work when versioned) * Add basic developers guide for working with helm * Fix post merge issue * Enable/disable API keys UI given API keys config setting (#4559) * Add a special type and parsing for the new config option * Add APIKeysEnabled to /info, check it in the middleware * Add test coverage for apiKeyMiddleware * Block API keys endpoints if disabled in the config; add tests * Fix failing test * Add API_KEYS_ENABLED to the Helm chart * Add JSON schema to the Helm chart * Enable/disable API keys UI given API keys config setting * Changes following review * Fix unit tests Co-authored-by: Ivan Kapelyukhin <ikapelyukhin@suse.com> * Enable linting for all packages (#4561) * Enable linting for all packages - lots to solve! - we may wish to exclude some files * Fix linting * Bump angular json schema form (#4564) * Bump angular json schema form * Handle bug where mat-error maring is too big - We may want to consider moving this upstream Co-authored-by: Richard Cox <richard.cox@suse.com> * Docs: Update internal versions & Automate future updates (#4558) * Docs: Update where 4.0.0 is built from, add 4.0.1 - means we can delete the old `docs-versioning` branch - both versions point to the same commit (when branch merged to master), but reduces user confusion when they can't find the docs for the latest released version. Going forward this will point directly to the release label * Automatically update website's internal-versions.json on new releases - Add new github action - Action will trigger on a release being published - Action looks at github `release` object associated with event and extracts a version label and git label - Use extracted values adds new value to internal-versions.json - Creates a PR with the change * Fix checkout issue * Update documentation-versioning.yml * Merge fixes, remove fdescribe already in master * Fix linting * Fix unit tests Co-authored-by: Veerapuram Varadhan <veerapuram.varadhan@suse.com> Co-authored-by: Neil MacDougall <neil.macdougall@suse.com> Co-authored-by: Neil MacDougall <nmacdougall@suse.com> Co-authored-by: Neil MacDougall <nwmac@users.noreply.github.com> Co-authored-by: Michal Jura <mjura@users.noreply.github.com> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Veerapuram Varadhan <v.varadhan@gmail.com> Co-authored-by: Ivan Kapelyukhin <ikapelyukhin@suse.com> --- .../workflows/documentation-versioning.yml | 40 + .github/workflows/documentation.yml | 2 +- .gitignore | 4 + README.md | 2 +- angular.json | 15 +- deploy/kubernetes/console/README.md | 1 + .../console/templates/deployment.yaml | 2 + deploy/kubernetes/console/values.schema.json | 373 +++ deploy/kubernetes/console/values.yaml | 3 + package-lock.json | 96 +- .../cf-autoscaler/src/cf-autoscaler.module.ts | 2 +- .../autoscaler-transform-metric.ts | 4 +- .../autoscaler-transform-policy.ts | 2 +- .../autoscaler-helpers/autoscaler-util.ts | 2 +- .../autoscaler-validation.ts | 4 +- .../autoscaler-base.component.spec.ts | 2 +- .../autoscaler-metric-page.component.spec.ts | 6 +- ...caler-scale-history-page.component.spec.ts | 6 +- ...autoscaler-tab-extension.component.spec.ts | 2 +- .../autoscaler-tab-extension.component.ts | 14 +- ...it-autoscaler-credential.component.spec.ts | 4 +- .../edit-autoscaler-policy-service.ts | 2 +- ...-autoscaler-policy-step1.component.spec.ts | 2 +- .../edit-autoscaler-policy-step1.component.ts | 2 +- ...-autoscaler-policy-step2.component.spec.ts | 2 +- ...-autoscaler-policy-step3.component.spec.ts | 2 +- .../edit-autoscaler-policy-step3.component.ts | 8 +- ...-autoscaler-policy-step4.component.spec.ts | 2 +- .../edit-autoscaler-policy-step4.component.ts | 6 +- .../edit-autoscaler-policy.component.spec.ts | 4 +- ...cf-app-autoscaler-events-config.service.ts | 2 +- .../combo-series-vertical.component.spec.ts | 2 +- ...scaler-metric-chart-list-config.service.ts | 2 +- .../src/actions/app-variables.actions.ts | 4 +- .../actions/deploy-applications.actions.ts | 1 + .../cloud-foundry/src/cf-entity-generator.ts | 14 +- .../src/cloud-foundry-test.module.ts | 2 - .../application-delete.component.spec.ts | 2 +- .../application-wall.component.spec.ts | 2 +- .../application-tabs-base.component.spec.ts | 2 +- .../build-tab/build-tab.component.spec.ts | 2 +- .../tabs/gitscm-tab/gitscm-tab.component.ts | 6 +- .../log-stream-tab.component.ts | 10 +- .../variables-tab/variables-tab.component.ts | 8 +- .../cli-info-application.component.spec.ts | 2 +- .../create-application.component.spec.ts | 2 +- .../deploy-application-step2.component.ts | 2 +- .../deploy-application.component.spec.ts | 2 +- .../github-project-exists.directive.ts | 2 - .../edit-application.component.spec.ts | 2 +- ...ew-application-base-step.component.spec.ts | 2 +- .../add-route-stepper.component.spec.ts | 2 +- .../ssh-application.component.spec.ts | 2 +- .../add-organization.component.spec.ts | 2 +- .../cf/add-quota/add-quota.component.spec.ts | 2 +- .../add-space-quota.component.spec.ts | 2 +- .../cf/add-space/add-space.component.spec.ts | 5 +- .../src/features/cf/cf.helpers.ts | 2 +- .../cli-info-cloud-foundry.component.spec.ts | 2 +- .../cf/cloud-foundry-section.module.ts | 2 - .../cf/cloud-foundry-section.routing.ts | 5 - .../cloud-foundry-tabs-base.component.spec.ts | 2 +- .../cloud-foundry.component.spec.ts | 2 +- .../edit-organization.component.spec.ts | 2 +- .../edit-quota/edit-quota.component.spec.ts | 2 +- .../edit-space-quota-step.component.ts | 6 +- .../edit-space-quota.component.spec.ts | 2 +- .../edit-space/edit-space.component.spec.ts | 2 +- .../quota-definition.component.spec.ts | 2 +- .../space-quota-definition.component.spec.ts | 2 +- .../cloud-foundry-cell-base.component.spec.ts | 2 +- .../cloud-foundry-firehose-formatter.ts | 13 +- .../cloud-foundry-firehose.component.ts | 6 +- ...rganization-space-quotas.component.spec.ts | 2 +- ...oundry-organization-base.component.spec.ts | 2 +- ...ndry-organization-events.component.spec.ts | 4 +- ...ndry-organization-spaces.component.spec.ts | 2 +- ...cloud-foundry-space-base.component.spec.ts | 2 +- ...oud-foundry-space-events.component.spec.ts | 6 +- ...-space-service-instances.component.spec.ts | 3 - ...ud-foundry-space-summary.component.spec.ts | 2 +- ...loud-foundry-space-users.component.spec.ts | 2 +- .../cloud-foundry-space-users.component.ts | 6 +- ...dry-organization-summary.component.spec.ts | 2 +- ...undry-organization-users.component.spec.ts | 2 +- ...ud-foundry-organizations.component.spec.ts | 2 +- .../cloud-foundry-quotas.component.spec.ts | 2 +- ...loud-foundry-summary-tab.component.spec.ts | 2 +- .../invite-users.component.spec.ts | 2 +- .../manage-users-confirm.component.ts | 16 +- .../manage-users-modify.component.ts | 14 +- .../manage-users.component.spec.ts | 2 +- .../remove-user/remove-user.component.spec.ts | 2 +- .../remove-user/remove-user.component.ts | 4 +- .../service-catalog-page.component.spec.ts | 2 +- .../service-tabs-base.component.spec.ts | 2 +- .../detach-service-instance.component.spec.ts | 2 +- .../services-wall.component.spec.ts | 2 +- ...rvice-instance-base-step.component.spec.ts | 2 +- .../add-service-instance.component.spec.ts | 2 +- .../create-service-instance-helper.service.ts | 16 +- .../card-cf-info/card-cf-info.component.ts | 2 +- .../list-types/app/cf-apps-data-source.ts | 9 +- .../cf-cell-health-list-config.service.ts | 5 +- .../cf-cells/cf-cells-list-config.service.ts | 2 +- ...cell-feature-flag-description.component.ts | 2 +- .../table-cell-service-broker.component.ts | 25 +- .../cf-org-permission-cell.component.ts | 6 +- .../list-types/cf-users/cf-permission-cell.ts | 2 +- .../cf-space-permission-cell.component.ts | 8 +- ...hub-commits-list-config-app-tab.service.ts | 2 +- .../scm/github-pagination.helper.ts | 153 + .../shared/data-services/scm/github-scm.ts | 39 +- .../shared/data-services/scm/gitlab-scm.ts | 61 +- .../app-name-unique.directive.ts | 4 +- .../src/store/effects/deploy-app.effects.ts | 24 +- .../src/store/effects/github.effects.ts | 4 +- .../src/store/effects/request.effects.ts | 4 +- .../src/store/effects/users-roles.effects.ts | 6 +- .../cf-user-permissions-checkers.ts | 28 +- .../user-permissions/cf-user-roles-fetch.ts | 19 +- .../sass/components/json-schema-form.scss | 5 + .../api-driven-views/api-drive-views.types.ts | 7 - .../api-driven-views-routing.module.ts | 45 - .../api-driven-views.module.ts | 28 - .../api-entity-list.component.html | 1 - .../api-entity-list.component.scss | 0 .../api-entity-list.component.spec.ts | 25 - .../api-entity-list.component.ts | 15 - .../api-entity-type-selector.component.html | 2 - .../api-entity-type-selector.component.scss | 0 ...api-entity-type-selector.component.spec.ts | 59 - .../api-entity-type-selector.component.ts | 40 - .../api-endpoint-select-page.component.html | 6 - .../api-endpoint-select-page.component.scss | 0 ...api-endpoint-select-page.component.spec.ts | 40 - .../api-endpoint-select-page.component.ts | 39 - ...i-endpoint-type-select-page.component.html | 6 - ...i-endpoint-type-select-page.component.scss | 0 ...ndpoint-type-select-page.component.spec.ts | 38 - ...api-endpoint-type-select-page.component.ts | 47 - .../api-entity-list-page.component.html | 1 - .../api-entity-list-page.component.scss | 0 .../api-entity-list-page.component.spec.ts | 36 - .../api-entity-list-page.component.ts | 26 - ...api-entity-type-select-page.component.html | 4 - ...api-entity-type-select-page.component.scss | 0 ...-entity-type-select-page.component.spec.ts | 38 - .../api-entity-type-select-page.component.ts | 52 - src/frontend/packages/core/src/app.module.ts | 4 +- src/frontend/packages/core/src/app.routing.ts | 7 +- .../src/core/apiKey-auth-guard.service.ts | 30 + .../packages/core/src/core/core.module.ts | 4 +- .../{ => src/core}/endpoints-health-checks.ts | 6 +- .../core/src/core/endpoints.service.ts | 2 +- .../src/core/extension/extension-service.ts | 14 +- .../core/src/core/logger.service.spec.ts | 24 - .../packages/core/src/core/logger.service.ts | 77 - .../current-user-permissions.service.ts | 16 +- .../stratos-user-permissions.checker.ts | 43 +- .../packages/core/src/core/utils.service.ts | 9 +- .../core/src/environments/environment.prod.ts | 4 - .../core/src/environments/environment.ts | 4 - .../about-page/about-page.component.spec.ts | 8 +- .../diagnostics-page.component.spec.ts | 8 +- .../eula-page/eula-page.component.spec.ts | 8 +- .../add-api-key-dialog.component.html | 28 + .../add-api-key-dialog.component.scss | 28 + .../add-api-key-dialog.component.spec.ts | 40 + .../add-api-key-dialog.component.ts | 66 + .../api-keys-page.component.html | 33 + .../api-keys-page.component.scss | 15 + .../api-keys-page.component.spec.ts | 42 + .../api-keys-page/api-keys-page.component.ts | 63 + .../src/features/api-keys/api-keys.module.ts | 22 + .../src/features/api-keys/api-keys.routing.ts | 18 + .../dashboard-base.component.spec.ts | 2 +- .../dashboard-base.component.ts | 6 +- .../page-side-nav.component.spec.ts | 4 +- .../page-side-nav/page-side-nav.component.ts | 4 +- .../side-nav/side-nav.component.html | 2 +- .../dashboard/side-nav/side-nav.component.ts | 23 +- .../backup-endpoints.component.spec.ts | 2 +- .../backup-endpoints.component.ts | 2 +- ...backup-restore-endpoints.component.spec.ts | 2 +- .../restore-endpoints.service.ts | 4 +- .../restore-endpoints.component.spec.ts | 2 +- .../connect-endpoint-dialog.component.spec.ts | 4 +- ...reate-endpoint-base-step.component.spec.ts | 23 +- .../create-endpoint.component.spec.ts | 7 +- .../edit-endpoint.component.spec.ts | 6 +- .../endpoints-page.component.html | 2 +- .../endpoints-page.component.spec.ts | 2 +- .../error-page/error-page.component.spec.ts | 2 +- .../events-page/events-page.component.spec.ts | 8 +- .../home/home/home-page.component.spec.ts | 8 +- .../features/home/home/home-page.component.ts | 2 - .../src/features/metrics/metrics.helpers.ts | 15 +- .../src/features/metrics/metrics.module.ts | 13 +- .../metrics/metrics/metrics.component.spec.ts | 9 +- .../no-endpoints-non-admin.component.spec.ts | 8 +- .../local-account-wizard.component.spec.ts | 8 +- .../setup-welcome.component.spec.ts | 2 +- .../console-uaa-wizard.component.spec.ts | 10 +- .../edit-profile-info.component.spec.ts | 2 +- .../profile-info.component.spec.ts | 9 +- .../packages/core/src/route-reuse-stragegy.ts | 2 +- .../app-action-monitor.component.ts | 4 +- .../boolean-indicator.component.ts | 44 +- .../copy-to-clipboard.component.ts | 5 +- .../date-time/date-time.component.ts | 9 +- .../favorites-meta-card.component.ts | 9 +- .../table-cell/table-cell.component.spec.ts | 4 - .../list/list-table/table.component.ts | 2 +- .../list-types/apiKeys/apiKey-data-source.ts | 32 + .../apiKeys/apiKey-list-config.service.ts | 103 + .../endpoint-card/endpoint-card.component.ts | 2 +- .../endpoint/endpoint-list.helpers.ts | 4 +- .../shared/components/list/list.component.ts | 2 +- .../components/list/list.component.types.ts | 4 +- .../markdown-preview.component.spec.ts | 3 +- .../markdown-preview.component.ts | 6 +- .../metrics-range-selector.component.ts | 2 +- .../nested-tabs/nested-tabs.component.ts | 3 +- .../no-content-message.component.html | 2 +- .../no-content-message.component.scss | 5 + .../no-content-message.component.ts | 19 +- .../page-header/page-header.component.html | 107 +- .../page-header/page-header.component.spec.ts | 8 +- .../page-header/page-header.component.ts | 10 +- .../show-page-header.component.spec.ts | 2 +- .../show-page-header.component.ts | 2 +- .../page-sub-nav.component.spec.ts | 2 +- .../page-sub-nav/page-sub-nav.component.ts | 4 +- .../recent-entities.component.ts | 10 +- .../sidepanel-preview.component.spec.ts | 3 +- .../start-end-date.component.spec.ts | 2 +- .../start-end-date.component.ts | 2 +- .../stepper/steppers/steppers.component.ts | 4 +- .../src/shared/components/user-avatar/md5.ts | 3 +- .../user-avatar/user-avatar.component.ts | 3 +- .../user-profile-banner.component.ts | 3 +- .../metrics-range-selector-manager.service.ts | 2 +- .../metrics-range-selector.service.ts | 12 +- .../services/metrics-range-selector.types.ts | 2 +- .../packages/core/src/shared/shared.module.ts | 2 + src/frontend/packages/core/src/styles.scss | 1 + .../core/{ => src}/tab-nav.service.ts | 6 +- .../packages/core/{ => src}/tab-nav.types.ts | 0 .../packages/core/{ => src}/xsrf.module.ts | 0 src/frontend/packages/core/tslint.json | 2 +- src/frontend/packages/store/ng-package.json | 16 +- .../src/actions/action-history.actions.ts | 9 - .../store/src/actions/apiKey.actions.ts | 44 + .../src/actions/internal-events.actions.ts | 2 +- .../packages/store/src/actions/log.actions.ts | 60 - .../store/src/actions/router.actions.ts | 4 +- .../store/src/actions/system.actions.ts | 12 +- .../src/actions/user-favourites.actions.ts | 12 +- .../packages/store/src/apiKey.types.ts | 7 + src/frontend/packages/store/src/app-state.ts | 2 - .../src/effects/action-history.effects.ts | 26 - .../store/src/effects/apiKey.effects.ts | 132 + .../store/src/effects/endpoint.effects.ts | 4 +- .../action-dispatcher.spec.ts | 54 +- .../entity-catalog-entity.ts | 16 + .../entity-catalog-entity.types.ts | 29 +- .../entity-catalog/entity-catalog.helper.ts | 5 +- .../src/entity-catalog/entity-catalog.spec.ts | 9 +- .../src/entity-catalog/entity-catalog.ts | 1 - .../entity-pagination-request-pipeline.ts | 2 - .../packages/store/src/entity-service.ts | 10 +- .../src/helpers/paginated-request-helpers.ts | 49 +- .../src/helpers/stratos-entity-factory.ts | 4 + .../internal-event-monitor.factory.spec.ts | 7 +- .../src/monitors/internal-event.monitor.ts | 4 +- src/frontend/packages/store/src/public-api.ts | 25 +- .../packages/store/src/reducers.module.ts | 2 - .../src/reducers/action-history-reducer.ts | 23 - .../api-request-reducer/fail-request.ts | 3 +- .../api-request-reducer/start-request.ts | 3 +- .../api-request-reducer/succeed-request.ts | 3 +- .../api-request-reducer/update-request.ts | 5 +- .../current-user-roles.reducer.ts | 7 +- .../packages/store/src/store.module.ts | 6 +- .../store/src/stratos-action-builders.ts | 49 +- .../store/src/stratos-entity-catalog.ts | 8 + .../store/src/stratos-entity-generator.ts | 52 +- .../packages/store/src/types/auth.types.ts | 8 +- src/frontend/packages/store/src/utils.ts | 6 + .../packages/store/testing/ng-package.json | 5 +- .../store/testing/src/store-test-helper.ts | 33 +- .../store/testing/src/store-test.module.ts | 4 +- .../packages/store/testing/tslint.json | 2 +- .../demo-helper/demo-helper.component.spec.ts | 2 +- .../monocular-tab-base.component.spec.ts | 2 +- .../chart-details-readme.component.ts | 2 +- .../chart-item/chart-item.component.spec.ts | 2 - .../monocular/charts/charts.component.spec.ts | 2 - .../shared/services/charts.service.ts | 6 +- .../shared/services/repos.service.ts | 6 +- .../analysis-report-selector.component.ts | 8 +- .../kube-console.component.spec.ts | 6 +- .../kubedash-configuration.component.spec.ts | 6 +- .../kubernetes-dashboard.component.spec.ts | 2 +- .../kubernetes/kubernetes-metrics.helpers.ts | 3 +- ...amespace-analysis-report.component.spec.ts | 6 +- ...netes-namespace-services.component.spec.ts | 2 +- .../kubernetes-node.component.spec.ts | 2 +- .../kubernetes-resource-viewer.component.ts | 2 +- .../kubernetes-tab-base.component.spec.ts | 2 +- .../kubernetes/kubernetes.testing.module.ts | 2 +- .../kubernetes/kubernetes.component.spec.ts | 2 +- .../analysis-reports-list-config.service.ts | 2 +- .../kubernetes/list-types/kube-list.helper.ts | 2 +- .../kubernetes-pod-containers.component.ts | 2 +- .../pod-metrics/pod-metrics.component.spec.ts | 2 +- .../analysis-info-card.component.ts | 2 +- ...bernetes-analysis-report.component.spec.ts | 6 +- .../kubernetes-analysis-tab.component.spec.ts | 6 +- .../kubernetes-summary.component.spec.ts | 2 +- .../create-release.component.spec.ts | 4 +- .../helm-release-socket-service.ts | 4 +- .../helm-release-tab-base.component.spec.ts | 8 +- ...elm-release-analysis-tab.component.spec.ts | 19 +- .../helm-release-history-tab.component.ts | 2 +- ...m-release-resource-graph.component.spec.ts | 10 +- ...helm-release-summary-tab.component.spec.ts | 2 +- .../helm-release-summary-tab.component.ts | 4 +- .../helm-release-values-tab.component.spec.ts | 2 +- .../releases-tab.component.spec.ts | 2 +- .../release-version-list-config.ts | 2 +- src/jetstream/apikeys.go | 92 + src/jetstream/apikeys_test.go | 389 +++ src/jetstream/auth_test.go | 4 +- src/jetstream/config.dev | 3 + .../datastore/20200814140918_ApiKeys.go | 22 + src/jetstream/go.mod | 1 + src/jetstream/go.sum | 7 + src/jetstream/info.go | 1 + src/jetstream/main.go | 33 +- src/jetstream/middleware.go | 240 +- src/jetstream/middleware_test.go | 378 +++ src/jetstream/plugins/metrics/main.go | 13 +- src/jetstream/plugins/metrics/main_test.go | 3 + src/jetstream/portal_proxy.go | 2 + src/jetstream/repository/apikeys/apikeys.go | 12 + .../repository/apikeys/mock_apikeys.go | 107 + .../repository/apikeys/psql_apikeys.go | 184 ++ .../repository/apikeys/psql_apikeys_test.go | 339 +++ .../repository/interfaces/apikeys.go | 12 + .../repository/interfaces/config/config.go | 40 +- .../repository/interfaces/structs.go | 9 +- .../repository/mock_interfaces/mock_auth.go | 107 + src/test-e2e/apikeys/apikey-e2e-helper.ts | 29 + src/test-e2e/apikeys/apikeys-e2e.spec.ts | 119 + .../apikeys/po/apikey-add-dialog.po.ts | 35 + .../apikeys/po/apikeys-list-page.po.ts | 34 + .../application-autoscaler-e2e.spec.ts | 30 +- .../application-deploy-local-e2e.spec.ts | 2 +- .../po/application-page-autoscaler.po.ts | 10 +- .../po/create-autoscaler-policy-step.po.ts | 2 +- .../po/message-no-autoscaler-policy.ts | 2 +- .../endpoints/endpoints-register-e2e.spec.ts | 1 - src/test-e2e/helpers/request-helpers.ts | 6 +- src/test-e2e/helpers/uaa-request-helpers.ts | 2 +- src/test-e2e/locale.helper.ts | 2 +- src/test-e2e/po/form.po.ts | 1 + src/test-e2e/po/page-header.po.ts | 12 +- src/tsconfig.json | 2 + website/README.md | 50 +- website/add-version.sh | 27 + website/build-versions.sh | 151 + website/docs/advanced/sso.md | 17 +- .../deploy/cloud-foundry/cloud-foundry.md | 6 +- website/docs/developer/deploy.md | 8 + .../docs/developer/developers-guide-helm.md | 59 + website/docs/developer/frontend.md | 6 +- website/docs/developer/introduction.md | 4 +- website/docs/extensions/frontend.md | 32 +- website/docs/extensions/introduction.md | 14 +- website/docs/extensions/v4-migration.md | 12 +- website/docs/introduction.md | 2 +- website/docs/overview.md | 2 +- website/docs/status_updates.md | 721 ----- website/docusaurus.config.js | 107 +- website/internal-versions.json | 4 + website/package-lock.json | 2595 ++++++++++------- website/package.json | 13 +- website/sidebars.js | 6 + website/src/css/custom.css | 28 +- website/src/pages/index.js | 24 +- website/src/pages/versions.js | 95 + .../src/theme/DocVersionSuggestions/index.js | 64 + .../DocVersionSuggestions/styles.module.css | 3 + website/src/theme/Navbar/index.js | 209 ++ website/src/theme/Navbar/styles.module.css | 26 + .../DocsVersionDropdownNavbarItem.js | 81 + 398 files changed, 7091 insertions(+), 3580 deletions(-) create mode 100644 .github/workflows/documentation-versioning.yml create mode 100644 deploy/kubernetes/console/values.schema.json create mode 100644 src/frontend/packages/cloud-foundry/src/shared/data-services/scm/github-pagination.helper.ts create mode 100644 src/frontend/packages/core/sass/components/json-schema-form.scss delete mode 100644 src/frontend/packages/core/src/api-driven-views/api-drive-views.types.ts delete mode 100644 src/frontend/packages/core/src/api-driven-views/api-driven-views-routing.module.ts delete mode 100644 src/frontend/packages/core/src/api-driven-views/api-driven-views.module.ts delete mode 100644 src/frontend/packages/core/src/api-driven-views/components/api-entity-list/api-entity-list.component.html delete mode 100644 src/frontend/packages/core/src/api-driven-views/components/api-entity-list/api-entity-list.component.scss delete mode 100644 src/frontend/packages/core/src/api-driven-views/components/api-entity-list/api-entity-list.component.spec.ts delete mode 100644 src/frontend/packages/core/src/api-driven-views/components/api-entity-list/api-entity-list.component.ts delete mode 100644 src/frontend/packages/core/src/api-driven-views/components/api-type-selector/api-entity-type-selector.component.html delete mode 100644 src/frontend/packages/core/src/api-driven-views/components/api-type-selector/api-entity-type-selector.component.scss delete mode 100644 src/frontend/packages/core/src/api-driven-views/components/api-type-selector/api-entity-type-selector.component.spec.ts delete mode 100644 src/frontend/packages/core/src/api-driven-views/components/api-type-selector/api-entity-type-selector.component.ts delete mode 100644 src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.html delete mode 100644 src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.scss delete mode 100644 src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.spec.ts delete mode 100644 src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.ts delete mode 100644 src/frontend/packages/core/src/api-driven-views/features/api-endpoint-type-select-page/api-endpoint-type-select-page.component.html delete mode 100644 src/frontend/packages/core/src/api-driven-views/features/api-endpoint-type-select-page/api-endpoint-type-select-page.component.scss delete mode 100644 src/frontend/packages/core/src/api-driven-views/features/api-endpoint-type-select-page/api-endpoint-type-select-page.component.spec.ts delete mode 100644 src/frontend/packages/core/src/api-driven-views/features/api-endpoint-type-select-page/api-endpoint-type-select-page.component.ts delete mode 100644 src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.html delete mode 100644 src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.scss delete mode 100644 src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.spec.ts delete mode 100644 src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.ts delete mode 100644 src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.html delete mode 100644 src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.scss delete mode 100644 src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.spec.ts delete mode 100644 src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.ts create mode 100644 src/frontend/packages/core/src/core/apiKey-auth-guard.service.ts rename src/frontend/packages/core/{ => src/core}/endpoints-health-checks.ts (74%) delete mode 100644 src/frontend/packages/core/src/core/logger.service.spec.ts delete mode 100644 src/frontend/packages/core/src/core/logger.service.ts create mode 100644 src/frontend/packages/core/src/features/api-keys/add-api-key-dialog/add-api-key-dialog.component.html create mode 100644 src/frontend/packages/core/src/features/api-keys/add-api-key-dialog/add-api-key-dialog.component.scss create mode 100644 src/frontend/packages/core/src/features/api-keys/add-api-key-dialog/add-api-key-dialog.component.spec.ts create mode 100644 src/frontend/packages/core/src/features/api-keys/add-api-key-dialog/add-api-key-dialog.component.ts create mode 100644 src/frontend/packages/core/src/features/api-keys/api-keys-page/api-keys-page.component.html create mode 100644 src/frontend/packages/core/src/features/api-keys/api-keys-page/api-keys-page.component.scss create mode 100644 src/frontend/packages/core/src/features/api-keys/api-keys-page/api-keys-page.component.spec.ts create mode 100644 src/frontend/packages/core/src/features/api-keys/api-keys-page/api-keys-page.component.ts create mode 100644 src/frontend/packages/core/src/features/api-keys/api-keys.module.ts create mode 100644 src/frontend/packages/core/src/features/api-keys/api-keys.routing.ts create mode 100644 src/frontend/packages/core/src/shared/components/list/list-types/apiKeys/apiKey-data-source.ts create mode 100644 src/frontend/packages/core/src/shared/components/list/list-types/apiKeys/apiKey-list-config.service.ts rename src/frontend/packages/core/{ => src}/tab-nav.service.ts (94%) rename src/frontend/packages/core/{ => src}/tab-nav.types.ts (100%) rename src/frontend/packages/core/{ => src}/xsrf.module.ts (100%) delete mode 100644 src/frontend/packages/store/src/actions/action-history.actions.ts create mode 100644 src/frontend/packages/store/src/actions/apiKey.actions.ts delete mode 100644 src/frontend/packages/store/src/actions/log.actions.ts create mode 100644 src/frontend/packages/store/src/apiKey.types.ts delete mode 100644 src/frontend/packages/store/src/effects/action-history.effects.ts create mode 100644 src/frontend/packages/store/src/effects/apiKey.effects.ts delete mode 100644 src/frontend/packages/store/src/reducers/action-history-reducer.ts create mode 100644 src/frontend/packages/store/src/utils.ts create mode 100644 src/jetstream/apikeys.go create mode 100644 src/jetstream/apikeys_test.go create mode 100644 src/jetstream/datastore/20200814140918_ApiKeys.go create mode 100644 src/jetstream/middleware_test.go create mode 100644 src/jetstream/repository/apikeys/apikeys.go create mode 100644 src/jetstream/repository/apikeys/mock_apikeys.go create mode 100644 src/jetstream/repository/apikeys/psql_apikeys.go create mode 100644 src/jetstream/repository/apikeys/psql_apikeys_test.go create mode 100644 src/jetstream/repository/interfaces/apikeys.go create mode 100644 src/jetstream/repository/mock_interfaces/mock_auth.go create mode 100644 src/test-e2e/apikeys/apikey-e2e-helper.ts create mode 100644 src/test-e2e/apikeys/apikeys-e2e.spec.ts create mode 100644 src/test-e2e/apikeys/po/apikey-add-dialog.po.ts create mode 100644 src/test-e2e/apikeys/po/apikeys-list-page.po.ts create mode 100755 website/add-version.sh create mode 100755 website/build-versions.sh create mode 100644 website/docs/developer/deploy.md create mode 100644 website/docs/developer/developers-guide-helm.md delete mode 100644 website/docs/status_updates.md create mode 100644 website/internal-versions.json create mode 100644 website/src/pages/versions.js create mode 100644 website/src/theme/DocVersionSuggestions/index.js create mode 100644 website/src/theme/DocVersionSuggestions/styles.module.css create mode 100644 website/src/theme/Navbar/index.js create mode 100644 website/src/theme/Navbar/styles.module.css create mode 100644 website/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.js diff --git a/.github/workflows/documentation-versioning.yml b/.github/workflows/documentation-versioning.yml new file mode 100644 index 0000000000..76eab9369b --- /dev/null +++ b/.github/workflows/documentation-versioning.yml @@ -0,0 +1,40 @@ +name: documentation-versions + +on: + release: + types: [published] + +jobs: + update-docs-internal-versions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + with: + node-version: '12.x' + - name: Update internal-versions.json + run: | + cd website + if [ -e yarn.lock ]; then + yarn install --frozen-lockfile + elif [ -e package-lock.json ]; then + npm ci + else + npm i + fi + npm run add-version + - name: Create Pull Request + id: cpr + uses: peter-evans/create-pull-request@v3.3.0 + with: + commit-message: Update Docs Versions + delete-branch: true + title: Update website docs version following release + body: | + - Auto-generated + labels: | + ready for review + draft: false + - name: Check output + run: | + echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}" diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 4b78eefdc3..a705479e7a 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -25,4 +25,4 @@ jobs: else npm i fi - npm run build \ No newline at end of file + npm run build diff --git a/.gitignore b/.gitignore index ac5cbdb218..eb452d4518 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,7 @@ go-vendor-*.tgz website/build website/site-dist website/.docusaurus +website/versioned_docs +website/versioned_sidebars +website/versions.json +website/versions-repo diff --git a/README.md b/README.md index 528627e08f..2c4ca4c6b3 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Please visit our new [documentation site](https://stratos.app/). There you can d 1. Guides for [developers](https://stratos.app/docs/developer/introduction). 1. How to [extend](https://stratos.app/docs/extensions/introduction) Stratos [functionality](https://stratos.app/docs/extensions/frontend) and apply a custom [theme](https://stratos.app/docs/extensions/theming). -> For more SUSE specific information, specifically regarding Stratos Kubernetes and Helm functionality, please see our `/docs` folder. +> For more SUSE specific information, for example regarding Stratos Kubernetes and Helm functionality, please see our `/docs` folder. ## Acknowledgements diff --git a/angular.json b/angular.json index 79b33bcd31..18d9daa972 100644 --- a/angular.json +++ b/angular.json @@ -192,11 +192,7 @@ ], "tslintConfig": "src/frontend/packages/core/tslint.json", "files": [ - "src/frontend/packages/core/src/**/*.ts", - "src/frontend/packages/core/src/custom/**/*.ts" - ], - "exclude": [ - "!src/frontend/packages/core/**" + "src/frontend/packages/core/src/**/*.ts" ] } } @@ -237,9 +233,6 @@ "tslintConfig": "src/frontend/packages/store/tslint.json", "files": [ "src/frontend/packages/store/src/**/*.ts" - ], - "exclude": [ - "!src/frontend/packages/store/**" ] } } @@ -311,9 +304,6 @@ "tslintConfig": "src/frontend/packages/cloud-foundry/tslint.json", "files": [ "src/frontend/packages/cloud-foundry/src/**/*.ts" - ], - "exclude": [ - "!src/frontend/packages/cloud-foundry/**" ] } } @@ -354,9 +344,6 @@ "tslintConfig": "src/frontend/packages/cf-autoscaler/tslint.json", "files": [ "src/frontend/packages/cf-autoscaler/src/**/*.ts" - ], - "exclude": [ - "!src/frontend/packages/cf-autoscaler/**" ] } } diff --git a/deploy/kubernetes/console/README.md b/deploy/kubernetes/console/README.md index ef228ed1d7..ee238aa880 100644 --- a/deploy/kubernetes/console/README.md +++ b/deploy/kubernetes/console/README.md @@ -75,6 +75,7 @@ The following table lists the configurable parameters of the Stratos Helm chart |console.templatesConfigMapName|Name of config map that provides the template files for user invitation emails|| |console.userInviteSubject|Email subject of the user invitation message|| |console.techPreview|Enable/disable Tech Preview features|false| +|console.apiKeysEnabled|Enable/disable API key-based access to Stratos API (disabled, admin_only, all_users)|admin_only| |console.ui.listMaxSize|Override the default maximum number of entities that a configured list can fetch. When a list meets this amount additional pages are not fetched|| |console.ui.listAllowLoadMaxed|If the maximum list size is met give the user the option to fetch all results|false| |console.localAdminPassword|Use local admin user instead of UAA - set to a password to enable|| diff --git a/deploy/kubernetes/console/templates/deployment.yaml b/deploy/kubernetes/console/templates/deployment.yaml index 50d94a2c5d..40280e1ba8 100644 --- a/deploy/kubernetes/console/templates/deployment.yaml +++ b/deploy/kubernetes/console/templates/deployment.yaml @@ -282,6 +282,8 @@ spec: value: {{ default "" .Values.console.userInviteSubject | quote }} - name: ENABLE_TECH_PREVIEW value: {{ default "false" .Values.console.techPreview | quote }} + - name: API_KEYS_ENABLED + value: {{ default "admin_only" .Values.console.apiKeysEnabled | quote }} - name: HELM_CACHE_FOLDER value: /helm-cache {{- if .Values.console.ui }} diff --git a/deploy/kubernetes/console/values.schema.json b/deploy/kubernetes/console/values.schema.json new file mode 100644 index 0000000000..34651e77ae --- /dev/null +++ b/deploy/kubernetes/console/values.schema.json @@ -0,0 +1,373 @@ +{ + "$schema": "http://json-schema.org/schema#", + "type": "object", + "properties": { + "autoCleanup": { + "type": "boolean" + }, + "configInit": { + "type": "object", + "properties": { + "nodeSelector": { + "type": "object" + } + } + }, + "console": { + "type": "object", + "properties": { + "apiKeysEnabled": { + "type": "string", + "enum": ["disabled", "admin_only", "all_users"] + }, + "autoRegisterCF": { + "type": ["string", "null"] + }, + "backendLogLevel": { + "type": "string" + }, + "cookieDomain": { + "type": ["string", "null"] + }, + "deploymentAnnotations": { + "type": "object" + }, + "deploymentExtraLabels": { + "type": "object" + }, + "jobAnnotations": { + "type": "object" + }, + "jobExtraLabels": { + "type": "object" + }, + "localAdminPassword": { + "type": ["string", "null"] + }, + "nodeSelector": { + "type": "object" + }, + "podAnnotations": { + "type": "object" + }, + "podExtraLabels": { + "type": "object" + }, + "service": { + "type": "object", + "properties": { + "annotations": { + "type": "object" + }, + "externalIPs": { + "type": "array" + }, + "externalName": { + "type": ["string", "null"] + }, + "extraLabels": { + "type": "object" + }, + "http": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "servicePort": { + "type": "integer" + } + } + }, + "ingress": { + "type": "object", + "properties": { + "annotations": { + "type": "object" + }, + "enabled": { + "type": "boolean" + }, + "extraLabels": { + "type": "object" + }, + "host": { + "type": ["string", "null"] + }, + "secretName": { + "type": ["string", "null"] + }, + "tls": { + "type": "object", + "properties": { + "crt": { + "type": ["string", "null"] + }, + "key": { + "type": ["string", "null"] + } + } + } + } + }, + "loadBalancerIP": { + "type": ["string", "null"] + }, + "loadBalancerSourceRanges": { + "type": "array" + }, + "servicePort": { + "type": "integer" + }, + "type": { + "type": "string" + } + } + }, + "sessionStoreSecret": { + "type": ["string", "null"] + }, + "sslCiphers": { + "type": ["string", "null"] + }, + "sslProtocols": { + "type": ["string", "null"] + }, + "ssoLogin": { + "type": "boolean" + }, + "ssoOptions": { + "type": ["string", "null"] + }, + "statefulSetAnnotations": { + "type": "object" + }, + "statefulSetExtraLabels": { + "type": "object" + }, + "techPreview": { + "type": "boolean" + }, + "templatesConfigMapName": { + "type": ["string", "null"] + }, + "tlsSecretName": { + "type": ["string", "null"] + }, + "ui": { + "type": "object", + "properties": { + "listAllowLoadMaxed": { + "type": "boolean" + }, + "listMaxSize": { + "type": ["integer", "null"] + } + } + }, + "userInviteSubject": { + "type": ["string", "null"] + } + } + }, + "consoleVersion": { + "type": "string" + }, + "dockerRegistrySecret": { + "type": "string" + }, + "env": { + "type": "object", + "properties": { + "DOMAIN": { + "type": ["string", "null"] + }, + "SMTP_AUTH": { + "type": "string" + }, + "SMTP_FROM_ADDRESS": { + "type": ["string", "null"] + }, + "SMTP_HOST": { + "type": ["string", "null"] + }, + "SMTP_PASSWORD": { + "type": ["string", "null"] + }, + "SMTP_PORT": { + "type": "string" + }, + "SMTP_USER": { + "type": ["string", "null"] + }, + "UAA_HOST": { + "type": ["string", "null"] + }, + "UAA_PORT": { + "type": "integer" + }, + "UAA_ZONE": { + "type": "string" + } + } + }, + "imagePullPolicy": { + "type": "string" + }, + "images": { + "type": "object", + "properties": { + "configInit": { + "type": "string" + }, + "console": { + "type": "string" + }, + "mariadb": { + "type": "string" + }, + "proxy": { + "type": "string" + } + } + }, + "kube": { + "type": "object", + "properties": { + "auth": { + "type": "string" + }, + "external_console_https_port": { + "type": "integer" + }, + "organization": { + "type": "string" + }, + "registry": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "hostname": { + "type": ["string", "null"] + }, + "password": { + "type": ["string", "null"] + }, + "username": { + "type": ["string", "null"] + } + } + }, + "storage_class": { + "type": "object", + "properties": { + "persistent": { + "type": ["string", "null"] + } + } + } + } + }, + "mariadb": { + "type": "object", + "properties": { + "database": { + "type": "string" + }, + "external": { + "type": "boolean" + }, + "host": { + "type": ["string", "null"] + }, + "nodeSelector": { + "type": "object" + }, + "persistence": { + "type": "object", + "properties": { + "accessMode": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "size": { + "type": "string" + }, + "storageClass": { + "type": ["string", "null"] + } + } + }, + "port": { + "type": "null" + }, + "resources": { + "type": "object", + "properties": { + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + } + } + }, + "rootPassword": { + "type": ["string", "null"] + }, + "tls": { + "type": ["string", "null"] + }, + "type": { + "type": ["string", "null"] + }, + "user": { + "type": "string" + }, + "userPassword": { + "type": ["string", "null"] + } + } + }, + "services": { + "type": "object", + "properties": { + "loadbalanced": { + "type": "boolean" + } + } + }, + "uaa": { + "type": "object", + "properties": { + "consoleAdminIdentifier": { + "type": ["string", "null"] + }, + "consoleClient": { + "type": ["string", "null"] + }, + "consoleClientSecret": { + "type": ["string", "null"] + }, + "endpoint": { + "type": ["string", "null"] + }, + "skipSSLValidation": { + "type": "boolean" + } + } + }, + "useLb": { + "type": "boolean" + } + } +} diff --git a/deploy/kubernetes/console/values.yaml b/deploy/kubernetes/console/values.yaml index 3e72d57812..e9999e9d35 100644 --- a/deploy/kubernetes/console/values.yaml +++ b/deploy/kubernetes/console/values.yaml @@ -67,6 +67,9 @@ console: # Enable/disable Tech Preview techPreview: false + # Enable/disable API key-based access to Stratos API + apiKeysEnabled: admin_only + ui: # Override the default maximum number of entities that a configured list can fetch. When a list meets this amount additional pages are not fetched listMaxSize: diff --git a/package-lock.json b/package-lock.json index 77791d470f..6ae8d5b767 100644 --- a/package-lock.json +++ b/package-lock.json @@ -986,6 +986,12 @@ "@babel/types": "^7.10.4" }, "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, "@babel/types": { "version": "7.11.5", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", @@ -1009,6 +1015,12 @@ "@babel/types": "^7.10.4" }, "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, "@babel/types": { "version": "7.11.5", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", @@ -1086,6 +1098,12 @@ "@babel/types": "^7.10.4" } }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, "@babel/highlight": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", @@ -1136,6 +1154,12 @@ "@babel/types": "^7.10.4" }, "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, "@babel/types": { "version": "7.11.5", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", @@ -1178,6 +1202,12 @@ "@babel/types": "^7.10.4" }, "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, "@babel/types": { "version": "7.11.5", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", @@ -1391,6 +1421,12 @@ "@babel/highlight": "^7.10.4" } }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, "@babel/highlight": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", @@ -1648,6 +1684,12 @@ "@babel/types": "^7.11.0" }, "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, "@babel/types": { "version": "7.11.5", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", @@ -1737,6 +1779,12 @@ "@babel/types": "^7.11.0" } }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, "@babel/highlight": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", @@ -2178,6 +2226,12 @@ "@babel/types": "^7.10.4" } }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, "@babel/types": { "version": "7.11.5", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", @@ -2304,6 +2358,12 @@ "@babel/types": "^7.11.0" } }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, "@babel/highlight": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", @@ -2472,6 +2532,12 @@ "@babel/types": "^7.10.4" } }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, "@babel/highlight": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", @@ -2655,6 +2721,12 @@ "@babel/types": "^7.11.0" } }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, "@babel/highlight": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", @@ -2853,6 +2925,12 @@ "@babel/types": "^7.11.0" } }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, "@babel/highlight": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", @@ -3051,6 +3129,12 @@ "@babel/types": "^7.11.0" } }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, "@babel/highlight": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", @@ -3820,18 +3904,18 @@ } }, "@cfstratos/ajsf-core": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@cfstratos/ajsf-core/-/ajsf-core-0.1.5.tgz", - "integrity": "sha512-IMLZJW8I173XovjV7k4hQBXWwr3dPsUa8mLvdVNVUY1RUIv6fY6+QsJXggMb+VMxTSqn+HOq7S1WfEH/Xrt1Dg==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@cfstratos/ajsf-core/-/ajsf-core-0.1.6.tgz", + "integrity": "sha512-G47ZAvn7ynuUkB2XzeewxhDB9yB5EYcBWfmBYB6FeEkFzX9PpcaCm8QK72IO6Kis+HjAvO5kwqiud9TXUPhMNw==", "requires": { "ajv": "^6.10.0", "lodash-es": "^4.17.15" } }, "@cfstratos/ajsf-material": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@cfstratos/ajsf-material/-/ajsf-material-0.1.5.tgz", - "integrity": "sha512-e0BSgFho4D4H76YhRBt9xg5zGuoJlcuMs7eWdSABZhW7LKHKKpLWqoGHo6qX4Q3rFTADxXiljKk86FjrMaYfrQ==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@cfstratos/ajsf-material/-/ajsf-material-0.1.6.tgz", + "integrity": "sha512-dUV2aqwQoRCaedNh31ZVZUgDhWqdiJnikag/Nws5h4fFs6sdmL2sS8A18e6onLA8t9t4n0y8PEib6qgxqr3ezw==", "requires": { "@cfstratos/ajsf-core": "~0.1.5", "lodash-es": "^4.17.15" diff --git a/src/frontend/packages/cf-autoscaler/src/cf-autoscaler.module.ts b/src/frontend/packages/cf-autoscaler/src/cf-autoscaler.module.ts index 95b411929b..6474fd5e56 100644 --- a/src/frontend/packages/cf-autoscaler/src/cf-autoscaler.module.ts +++ b/src/frontend/packages/cf-autoscaler/src/cf-autoscaler.module.ts @@ -1,10 +1,10 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { NgxChartsModule } from '@swimlane/ngx-charts'; -import { ExtensionService } from 'frontend/packages/core/src/core/extension/extension-service'; import { CloudFoundrySharedModule } from '../../cloud-foundry/src/shared/cf-shared.module'; import { CoreModule } from '../../core/src/core/core.module'; +import { ExtensionService } from '../../core/src/core/extension/extension-service'; import { MDAppModule } from '../../core/src/core/md.module'; import { SharedModule } from '../../core/src/shared/shared.module'; import { AutoscalerTabExtensionComponent } from './features/autoscaler-tab-extension/autoscaler-tab-extension.component'; diff --git a/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-transform-metric.ts b/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-transform-metric.ts index 899e687833..3e535655bb 100644 --- a/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-transform-metric.ts +++ b/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-transform-metric.ts @@ -1,5 +1,6 @@ -import * as moment from 'moment-timezone'; +import moment from 'moment-timezone'; +import { PaginationResponse } from '../../../../cloud-foundry/src/store/types/cf-api.types'; import { AppAutoscalerMetricBasicInfo, AppAutoscalerMetricData, @@ -10,7 +11,6 @@ import { AppScalingTrigger, } from '../../store/app-autoscaler.types'; import { AutoscalerConstants, getScaleType } from './autoscaler-util'; -import { PaginationResponse } from '../../../../cloud-foundry/src/store/types/cf-api.types'; function initMetricData(metricName: string): AppAutoscalerMetricDataLocal { return { diff --git a/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-transform-policy.ts b/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-transform-policy.ts index 5e11d7642b..b00b5ce017 100644 --- a/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-transform-policy.ts +++ b/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-transform-policy.ts @@ -1,4 +1,4 @@ -import * as moment from 'moment-timezone'; +import moment from 'moment-timezone'; import { AppAutoscalerPolicy, diff --git a/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-util.ts b/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-util.ts index 9e73dfed78..cff3e9856b 100644 --- a/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-util.ts +++ b/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-util.ts @@ -1,4 +1,4 @@ -import * as moment from 'moment-timezone'; +import moment from 'moment-timezone'; import { AppAutoscalerMetricDataPoint, diff --git a/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-validation.ts b/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-validation.ts index 22410741c5..b9300612c4 100644 --- a/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-validation.ts +++ b/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-validation.ts @@ -1,5 +1,5 @@ -import * as intersect from 'intersect'; -import * as moment from 'moment-timezone'; +import intersect from 'intersect'; +import moment from 'moment-timezone'; import { AppRecurringSchedule, AppScalingRule, AppSpecificDate } from '../../store/app-autoscaler.types'; import { AutoscalerConstants } from './autoscaler-util'; diff --git a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-base.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-base.component.spec.ts index 5afb7cda0e..29293ef982 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-base.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-base.component.spec.ts @@ -8,7 +8,7 @@ import { ApplicationService } from '../../../cloud-foundry/src/features/applicat import { ApplicationServiceMock } from '../../../cloud-foundry/test-framework/application-service-helper'; import { CoreModule } from '../../../core/src/core/core.module'; import { SharedModule } from '../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../core/tab-nav.service'; +import { TabNavService } from '../../../core/src/tab-nav.service'; import { CfAutoscalerTestingModule } from '../cf-autoscaler-testing.module'; import { AutoscalerBaseComponent } from './autoscaler-base.component'; diff --git a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-metric-page/autoscaler-metric-page.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-metric-page/autoscaler-metric-page.component.spec.ts index 22140a5cdf..a6b358f4d1 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-metric-page/autoscaler-metric-page.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-metric-page/autoscaler-metric-page.component.spec.ts @@ -7,8 +7,9 @@ import { createEmptyStoreModule } from '@stratosui/store/testing'; import { ApplicationService } from '../../../../cloud-foundry/src/features/applications/application.service'; import { ApplicationServiceMock } from '../../../../cloud-foundry/test-framework/application-service-helper'; import { CoreModule } from '../../../../core/src/core/core.module'; +import { CurrentUserPermissionsService } from '../../../../core/src/core/permissions/current-user-permissions.service'; import { SharedModule } from '../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../core/src/tab-nav.service'; import { CfAutoscalerTestingModule } from '../../cf-autoscaler-testing.module'; import { AutoscalerMetricPageComponent } from './autoscaler-metric-page.component'; @@ -30,7 +31,8 @@ describe('AutoscalerMetricPageComponent', () => { providers: [ DatePipe, { provide: ApplicationService, useClass: ApplicationServiceMock }, - TabNavService + TabNavService, + CurrentUserPermissionsService ] }) .compileComponents(); diff --git a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-scale-history-page/autoscaler-scale-history-page.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-scale-history-page/autoscaler-scale-history-page.component.spec.ts index 78e8a572a4..477a22f4fa 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-scale-history-page/autoscaler-scale-history-page.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-scale-history-page/autoscaler-scale-history-page.component.spec.ts @@ -7,8 +7,9 @@ import { createEmptyStoreModule } from '@stratosui/store/testing'; import { ApplicationService } from '../../../../cloud-foundry/src/features/applications/application.service'; import { ApplicationServiceMock } from '../../../../cloud-foundry/test-framework/application-service-helper'; import { CoreModule } from '../../../../core/src/core/core.module'; +import { CurrentUserPermissionsService } from '../../../../core/src/core/permissions/current-user-permissions.service'; import { SharedModule } from '../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../core/src/tab-nav.service'; import { CfAutoscalerTestingModule } from '../../cf-autoscaler-testing.module'; import { AutoscalerScaleHistoryPageComponent } from './autoscaler-scale-history-page.component'; @@ -30,7 +31,8 @@ describe('AutoscalerScaleHistoryPageComponent', () => { providers: [ DatePipe, { provide: ApplicationService, useClass: ApplicationServiceMock }, - TabNavService + TabNavService, + CurrentUserPermissionsService ] }) .compileComponents(); diff --git a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.spec.ts index b96d66bdb5..43f27e5b57 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.spec.ts @@ -21,7 +21,7 @@ import { import { ApplicationServiceMock } from '../../../../cloud-foundry/test-framework/application-service-helper'; import { CoreModule } from '../../../../core/src/core/core.module'; import { SharedModule } from '../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../core/src/tab-nav.service'; import { CfAutoscalerTestingModule } from '../../cf-autoscaler-testing.module'; import { CardAutoscalerDefaultComponent } from '../../shared/card-autoscaler-default/card-autoscaler-default.component'; import { AutoscalerTabExtensionComponent } from './autoscaler-tab-extension.component'; diff --git a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.ts b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.ts index 9b3b56c9b2..a71a1e76e1 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.ts @@ -71,7 +71,7 @@ import { appAutoscalerAppMetricEntityType, autoscalerEntityFactory } from '../.. createEntityRelationKey(spaceEntityType, organizationEntityType), ], populateMissing: true - }) + }); const canEditApp$ = appEntService.waitForEntity$.pipe( switchMap(app => cups.can( @@ -80,14 +80,14 @@ import { appAutoscalerAppMetricEntityType, autoscalerEntityFactory } from '../.. app.entity.entity.space.entity.organization_guid, app.entity.entity.space.metadata.guid )), - ) + ); const autoscalerEnabled = isAutoscalerEnabled(endpointGuid, esf); return canEditApp$.pipe( switchMap(canEditSpace => canEditSpace ? autoscalerEnabled : of(false)), map(can => !can) - ) + ); } }) @Component({ @@ -179,9 +179,9 @@ export class AutoscalerTabExtensionComponent implements OnInit, OnDestroy { if (buildParts.length < 0) { return false; } - return Number.parseInt(buildParts[0]) >= 3 + return Number.parseInt(buildParts[0], 10) >= 3; }) - ) + ); this.appAutoscalerPolicyService = this.entityServiceFactory.create( this.applicationService.appGuid, @@ -360,7 +360,7 @@ export class AutoscalerTabExtensionComponent implements OnInit, OnDestroy { ], query })); - } + }; metricChartPage() { this.store.dispatch(new RouterNav({ @@ -402,6 +402,6 @@ export class AutoscalerTabExtensionComponent implements OnInit, OnDestroy { 'edit-autoscaler-credential' ] })); - } + }; } diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-credential/edit-autoscaler-credential.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-credential/edit-autoscaler-credential.component.spec.ts index 131139620e..847513f52e 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-credential/edit-autoscaler-credential.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-credential/edit-autoscaler-credential.component.spec.ts @@ -6,8 +6,9 @@ import { RouterTestingModule } from '@angular/router/testing'; import { ApplicationService } from '../../../../cloud-foundry/src/features/applications/application.service'; import { ApplicationServiceMock } from '../../../../cloud-foundry/test-framework/application-service-helper'; import { CoreModule } from '../../../../core/src/core/core.module'; +import { CurrentUserPermissionsService } from '../../../../core/src/core/permissions/current-user-permissions.service'; import { SharedModule } from '../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../core/src/tab-nav.service'; import { createBasicStoreModule } from '../../../../store/testing/public-api'; import { CfAutoscalerTestingModule } from '../../cf-autoscaler-testing.module'; import { EditAutoscalerCredentialComponent } from './edit-autoscaler-credential.component'; @@ -33,6 +34,7 @@ describe('EditAutoscalerCredentialComponent', () => { DatePipe, { provide: ApplicationService, useClass: ApplicationServiceMock }, TabNavService, + CurrentUserPermissionsService ] }) .compileComponents(); diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-service.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-service.ts index fc6f92c265..a49603e247 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-service.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import * as moment from 'moment-timezone'; +import moment from 'moment-timezone'; import { BehaviorSubject, Observable } from 'rxjs'; import { filter, first } from 'rxjs/operators'; diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component.spec.ts index 02095792e5..5dd121d938 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component.spec.ts @@ -8,7 +8,7 @@ import { ApplicationService } from '../../../../../cloud-foundry/src/features/ap import { ApplicationServiceMock } from '../../../../../cloud-foundry/test-framework/application-service-helper'; import { CoreModule } from '../../../../../core/src/core/core.module'; import { SharedModule } from '../../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { CfAutoscalerTestingModule } from '../../../cf-autoscaler-testing.module'; import { EditAutoscalerPolicyService } from '../edit-autoscaler-policy-service'; import { EditAutoscalerPolicyStep1Component } from './edit-autoscaler-policy-step1.component'; diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component.ts index dc3be58839..1ff96089b9 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component.ts @@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core'; import { AbstractControl, FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms'; import { ErrorStateMatcher, ShowOnDirtyErrorStateMatcher } from '@angular/material/core'; import { ActivatedRoute } from '@angular/router'; -import * as moment from 'moment-timezone'; +import moment from 'moment-timezone'; import { of as observableOf } from 'rxjs'; import { map } from 'rxjs/operators'; diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.spec.ts index 05552c2692..809b56d90d 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.spec.ts @@ -8,7 +8,7 @@ import { ApplicationService } from '../../../../../cloud-foundry/src/features/ap import { ApplicationServiceMock } from '../../../../../cloud-foundry/test-framework/application-service-helper'; import { CoreModule } from '../../../../../core/src/core/core.module'; import { SharedModule } from '../../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { EditAutoscalerPolicyService } from '../edit-autoscaler-policy-service'; import { EditAutoscalerPolicyStep2Component } from './edit-autoscaler-policy-step2.component'; diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step3/edit-autoscaler-policy-step3.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step3/edit-autoscaler-policy-step3.component.spec.ts index 01d38f4030..c154204509 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step3/edit-autoscaler-policy-step3.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step3/edit-autoscaler-policy-step3.component.spec.ts @@ -8,7 +8,7 @@ import { ApplicationService } from '../../../../../cloud-foundry/src/features/ap import { ApplicationServiceMock } from '../../../../../cloud-foundry/test-framework/application-service-helper'; import { CoreModule } from '../../../../../core/src/core/core.module'; import { SharedModule } from '../../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { EditAutoscalerPolicyService } from '../edit-autoscaler-policy-service'; import { EditAutoscalerPolicyStep3Component } from './edit-autoscaler-policy-step3.component'; diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step3/edit-autoscaler-policy-step3.component.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step3/edit-autoscaler-policy-step3.component.ts index 542688df0a..a8d47f628b 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step3/edit-autoscaler-policy-step3.component.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step3/edit-autoscaler-policy-step3.component.ts @@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core'; import { AbstractControl, FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms'; import { ErrorStateMatcher, ShowOnDirtyErrorStateMatcher } from '@angular/material/core'; import { ActivatedRoute } from '@angular/router'; -import * as moment from 'moment-timezone'; +import moment from 'moment-timezone'; import { ApplicationService } from '../../../../../cloud-foundry/src/features/applications/application.service'; import { AutoscalerConstants, PolicyAlert, shiftArray } from '../../../core/autoscaler-helpers/autoscaler-util'; @@ -12,11 +12,7 @@ import { recurringSchedulesOverlapping, timeIsSameOrAfter, } from '../../../core/autoscaler-helpers/autoscaler-validation'; -import { - AppAutoscalerInvalidPolicyError, - AppAutoscalerPolicy, - AppAutoscalerPolicyLocal, -} from '../../../store/app-autoscaler.types'; +import { AppAutoscalerInvalidPolicyError, AppAutoscalerPolicyLocal } from '../../../store/app-autoscaler.types'; import { EditAutoscalerPolicy } from '../edit-autoscaler-policy-base-step'; import { EditAutoscalerPolicyService } from '../edit-autoscaler-policy-service'; import { diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.spec.ts index a2f2dab563..4c5e3f48fc 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.spec.ts @@ -8,7 +8,7 @@ import { ApplicationService } from '../../../../../cloud-foundry/src/features/ap import { ApplicationServiceMock } from '../../../../../cloud-foundry/test-framework/application-service-helper'; import { CoreModule } from '../../../../../core/src/core/core.module'; import { SharedModule } from '../../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { CfAutoscalerTestingModule } from '../../../cf-autoscaler-testing.module'; import { EditAutoscalerPolicyService } from '../edit-autoscaler-policy-service'; import { EditAutoscalerPolicyStep4Component } from './edit-autoscaler-policy-step4.component'; diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.ts index 78198371b8..3050f2caad 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.ts @@ -3,15 +3,15 @@ import { AbstractControl, FormBuilder, FormGroup, ValidatorFn, Validators } from import { ErrorStateMatcher, ShowOnDirtyErrorStateMatcher } from '@angular/material/core'; import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; -import * as moment from 'moment-timezone'; +import moment from 'moment-timezone'; import { of as observableOf } from 'rxjs'; import { filter, first, map, pairwise } from 'rxjs/operators'; import { ApplicationService } from '../../../../../cloud-foundry/src/features/applications/application.service'; -import { EntityService } from '../../../../../store/src/entity-service'; -import { EntityServiceFactory } from '../../../../../store/src/entity-service-factory.service'; import { StepOnNextFunction } from '../../../../../core/src/shared/components/stepper/step/step.component'; import { AppState } from '../../../../../store/src/app-state'; +import { EntityService } from '../../../../../store/src/entity-service'; +import { EntityServiceFactory } from '../../../../../store/src/entity-service-factory.service'; import { RequestInfoState } from '../../../../../store/src/reducers/api-request-reducer/types'; import { AutoscalerConstants, PolicyAlert } from '../../../core/autoscaler-helpers/autoscaler-util'; import { diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy.component.spec.ts index 9be13c7f96..bfab3dab44 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy.component.spec.ts @@ -7,8 +7,9 @@ import { createEmptyStoreModule } from '@stratosui/store/testing'; import { ApplicationService } from '../../../../cloud-foundry/src/features/applications/application.service'; import { ApplicationServiceMock } from '../../../../cloud-foundry/test-framework/application-service-helper'; import { CoreModule } from '../../../../core/src/core/core.module'; +import { CurrentUserPermissionsService } from '../../../../core/src/core/permissions/current-user-permissions.service'; import { SharedModule } from '../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../core/src/tab-nav.service'; import { CfAutoscalerTestingModule } from '../../cf-autoscaler-testing.module'; import { EditAutoscalerPolicyService } from './edit-autoscaler-policy-service'; import { EditAutoscalerPolicyStep1Component } from './edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component'; @@ -43,6 +44,7 @@ describe('EditAutoscalerPolicyComponent', () => { { provide: ApplicationService, useClass: ApplicationServiceMock }, TabNavService, EditAutoscalerPolicyService, + CurrentUserPermissionsService ] }) .compileComponents(); diff --git a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-config.service.ts b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-config.service.ts index 8732d20bfb..80a55eeb41 100644 --- a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-config.service.ts +++ b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-event/cf-app-autoscaler-events-config.service.ts @@ -1,7 +1,7 @@ import { DatePipe } from '@angular/common'; import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import * as moment from 'moment'; +import moment from 'moment'; import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; import { ApplicationService } from '../../../../../cloud-foundry/src/features/applications/application.service'; diff --git a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-card/combo-chart/combo-series-vertical.component.spec.ts b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-card/combo-chart/combo-series-vertical.component.spec.ts index 367ee3f0d1..4c3f7879ac 100644 --- a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-card/combo-chart/combo-series-vertical.component.spec.ts +++ b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-card/combo-chart/combo-series-vertical.component.spec.ts @@ -9,7 +9,7 @@ import { ApplicationService } from '../../../../../../../cloud-foundry/src/featu import { ApplicationServiceMock } from '../../../../../../../cloud-foundry/test-framework/application-service-helper'; import { CoreModule } from '../../../../../../../core/src/core/core.module'; import { SharedModule } from '../../../../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../../core/src/tab-nav.service'; import { AppAutoscalerComboSeriesVerticalComponent } from './combo-series-vertical.component'; describe('AppAutoscalerComboSeriesVerticalComponent', () => { diff --git a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-list-config.service.ts b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-list-config.service.ts index a5c3fd8aef..7d425f7bbe 100644 --- a/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-list-config.service.ts +++ b/src/frontend/packages/cf-autoscaler/src/shared/list-types/app-autoscaler-metric-chart/app-autoscaler-metric-chart-list-config.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import * as moment from 'moment'; +import moment from 'moment'; import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; import { ApplicationService } from '../../../../../cloud-foundry/src/features/applications/application.service'; diff --git a/src/frontend/packages/cloud-foundry/src/actions/app-variables.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/app-variables.actions.ts index 8a9f974710..fc38618e9a 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/app-variables.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/app-variables.actions.ts @@ -11,9 +11,10 @@ export class AppVariablesUpdate implements Action { type = AppVariables.UPDATE; updatedApplication: UpdateApplication; + guid: string; constructor(public cfGuid: string, public appGuid: string) { - this.guid = 'n/a' // No such thing as an individual app variable guid + this.guid = 'n/a'; // No such thing as an individual app variable guid } protected createUpdateApplication(allEnvVars: ListAppEnvVar[], selectedItems: ListAppEnvVar[]): UpdateApplication { @@ -28,7 +29,6 @@ export class AppVariablesUpdate implements Action { } return updateApp; } - guid: string; } export class AppVariablesDelete extends AppVariablesUpdate { diff --git a/src/frontend/packages/cloud-foundry/src/actions/deploy-applications.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/deploy-applications.actions.ts index 9604b767da..17c35c2e7f 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/deploy-applications.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/deploy-applications.actions.ts @@ -86,6 +86,7 @@ export class FetchBranchesForProject implements PaginatedAction { type = FETCH_BRANCHES_FOR_PROJECT; entityType = gitBranchesEntityType; paginationKey: string; + flattenPagination = true; static createPaginationKey = (scm: GitSCM, projectName: string) => scm.getType() + ':' + projectName; } diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts index 07b5489a05..20e226cbd5 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts @@ -1,5 +1,5 @@ import { Action, Store } from '@ngrx/store'; -import * as moment from 'moment'; +import moment from 'moment'; import { combineLatest, Observable, of } from 'rxjs'; import { first, map } from 'rxjs/operators'; @@ -299,14 +299,14 @@ export function generateCFEntities(): StratosBaseCatalogEntity[] { }, entitiesEmitHandler: (action: PaginatedAction | PaginatedAction[], dispatcher: ActionDispatcher) => { let lastValidationFootprint: string; - const arrayAction = Array.isArray(action) ? action : [action]; + const actionsArray = Array.isArray(action) ? action : [action]; return (state: PaginationEntityState) => { const newValidationFootprint = getPaginationCompareString(state); if (lastValidationFootprint !== newValidationFootprint) { lastValidationFootprint = newValidationFootprint; - arrayAction.forEach(action => dispatcher(new CfValidateEntitiesStart( - action, - state.ids[action.__forcedPageNumber__ || state.currentPage] + actionsArray.forEach(actionFromArray => dispatcher(new CfValidateEntitiesStart( + actionFromArray, + state.ids[actionFromArray.__forcedPageNumber__ || state.currentPage] ))); } }; @@ -314,7 +314,7 @@ export function generateCFEntities(): StratosBaseCatalogEntity[] { entitiesFetchHandler: (store: Store<GeneralEntityAppState>, actions: PaginatedAction[]) => () => { combineLatest(actions.map(action => safePopulatePaginationFromParent(store, action))).pipe( first(), - ).subscribe(actions => actions.forEach(action => store.dispatch(action))); + ).subscribe(newActions => newActions.forEach(newAction => store.dispatch(newAction))); }, paginationConfig: { getEntitiesFromResponse: (response: CFResponse) => response.resources, @@ -1231,7 +1231,7 @@ function generateCfOrgEntity(endpointDefinition: StratosEndpointExtensionDefinit labelPlural: 'Organizations', endpoint: endpointDefinition, icon: 'organization', - iconFont: 'stratos-icons' + iconFont: 'stratos-icons' }; cfEntityCatalog.org = new StratosCatalogEntity< IOrgFavMetadata, diff --git a/src/frontend/packages/cloud-foundry/src/cloud-foundry-test.module.ts b/src/frontend/packages/cloud-foundry/src/cloud-foundry-test.module.ts index 2bc284c36c..3df3235a19 100644 --- a/src/frontend/packages/cloud-foundry/src/cloud-foundry-test.module.ts +++ b/src/frontend/packages/cloud-foundry/src/cloud-foundry-test.module.ts @@ -4,7 +4,6 @@ import { EffectsModule } from '@ngrx/effects'; import { generateASEntities } from '../../cf-autoscaler/src/store/autoscaler-entity-generator'; import { getGitHubAPIURL, GITHUB_API_URL } from '../../core/src/core/github.helpers'; -import { LoggerService } from '../../core/src/core/logger.service'; import { CATALOGUE_ENTITIES, EntityCatalogFeatureModule } from '../../store/src/entity-catalog.module'; import { entityCatalog, TestEntityCatalog } from '../../store/src/entity-catalog/entity-catalog'; import { generateStratosEntities } from '../../store/src/stratos-entity-generator'; @@ -42,7 +41,6 @@ import { CloudFoundryStoreModule } from './store/cloud-foundry.store.module'; providers: [ { provide: GITHUB_API_URL, useFactory: getGitHubAPIURL }, GitSCMService, - LoggerService, LongRunningCfOperationsService, CfUserService, { diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/application-delete.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/application-delete.component.spec.ts index 52aa6ff434..8137d582fc 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/application-delete.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application-delete/application-delete.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { generateTestApplicationServiceProvider } from '../../../../test-framework/application-service-helper'; import { generateCfBaseTestModules } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { ApplicationsModule } from '../applications.module'; diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.spec.ts index e92a929b87..e7060da064 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.spec.ts @@ -1,7 +1,7 @@ import { DatePipe } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CfEndpointsMissingComponent } from '../../../shared/components/cf-endpoints-missing/cf-endpoints-missing.component'; import { CloudFoundryService } from '../../../shared/data-services/cloud-foundry.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-tabs-base.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-tabs-base.component.spec.ts index 19c49370d4..db27792fcc 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-tabs-base.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/application-tabs-base.component.spec.ts @@ -9,7 +9,7 @@ import { CoreModule } from '../../../../../../core/src/core/core.module'; import { getGitHubAPIURL, GITHUB_API_URL } from '../../../../../../core/src/core/github.helpers'; import { MDAppModule } from '../../../../../../core/src/core/md.module'; import { SharedModule } from '../../../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../core/src/tab-nav.service'; import { generateTestApplicationServiceProvider } from '../../../../../test-framework/application-service-helper'; import { generateCfStoreModules } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { ApplicationStateService } from '../../../../shared/services/application-state.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/build-tab/build-tab.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/build-tab/build-tab.component.spec.ts index 58a38edbe5..6f3aa1d408 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/build-tab/build-tab.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/build-tab/build-tab.component.spec.ts @@ -8,7 +8,7 @@ import { CoreModule } from '../../../../../../../../core/src/core/core.module'; import { GITHUB_API_URL } from '../../../../../../../../core/src/core/github.helpers'; import { APP_GUID, CF_GUID } from '../../../../../../../../core/src/shared/entity.tokens'; import { SharedModule } from '../../../../../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../../../core/src/tab-nav.service'; import { AppStoreModule } from '../../../../../../../../store/src/store.module'; import { ApplicationServiceMock } from '../../../../../../../test-framework/application-service-helper'; import { generateCfStoreModules } from '../../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/gitscm-tab/gitscm-tab.component.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/gitscm-tab/gitscm-tab.component.ts index 528965f3c9..32d2c03c3e 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/gitscm-tab/gitscm-tab.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/gitscm-tab/gitscm-tab.component.ts @@ -81,16 +81,16 @@ export class GitSCMTabComponent implements OnInit, OnDestroy { const scm = this.scmService.getSCM(scmType as GitSCMType); const gitRepInfoMeta: GitMeta = { projectName: stProject.deploySource.project, scm }; - this.gitSCMRepoEntityService = cfEntityCatalog.gitRepo.store.getRepoInfo.getEntityService(gitRepInfoMeta) + this.gitSCMRepoEntityService = cfEntityCatalog.gitRepo.store.getRepoInfo.getEntityService(gitRepInfoMeta); const gitMeta: GitMeta = { projectName: stProject.deploySource.project, scm, commitSha }; const repoEntityID = `${scmType}-${projectName}`; const commitEntityID = `${repoEntityID}-${commitSha}`; // FIXME: Should come from action #4245 - this.gitCommitEntityService = cfEntityCatalog.gitCommit.store.getEntityService(commitEntityID, null, gitMeta) + this.gitCommitEntityService = cfEntityCatalog.gitCommit.store.getEntityService(commitEntityID, null, gitMeta); this.gitBranchEntityService = cfEntityCatalog.gitBranch.store.getEntityService(undefined, undefined, { scm, - projectName: projectName, + projectName, branchName: stProject.deploySource.branch }); diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/log-stream-tab/log-stream-tab.component.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/log-stream-tab/log-stream-tab.component.ts index e4226d87c0..cc4a4f4bd7 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/log-stream-tab/log-stream-tab.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/log-stream-tab/log-stream-tab.component.ts @@ -1,13 +1,12 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { NgModel } from '@angular/forms'; import { Store } from '@ngrx/store'; -import * as moment from 'moment'; +import moment from 'moment'; import { NEVER, Observable, Subject } from 'rxjs'; import makeWebSocketObservable, { GetWebSocketResponses } from 'rxjs-websockets'; -import { catchError, share, switchMap, map, first, startWith, debounceTime } from 'rxjs/operators'; +import { catchError, debounceTime, first, map, share, startWith, switchMap } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../../cloud-foundry/src/cf-app-state'; -import { LoggerService } from '../../../../../../../../core/src/core/logger.service'; import { AnsiColorizer } from '../../../../../../../../core/src/shared/components/log-viewer/ansi-colorizer'; import { ApplicationService } from '../../../../application.service'; @@ -39,7 +38,6 @@ export class LogStreamTabComponent implements OnInit { constructor( private applicationService: ApplicationService, private store: Store<CFAppState>, - private logService: LoggerService ) { this.filter = this.jsonFilter.bind(this); } @@ -55,7 +53,7 @@ export class LogStreamTabComponent implements OnInit { }/apps/${this.applicationService.appGuid}/stream`; const socket$ = makeWebSocketObservable(streamUrl).pipe(catchError(e => { - this.logService.error( + console.error( 'Error while connecting to socket: ' + JSON.stringify(e) ); return []; @@ -110,7 +108,7 @@ export class LogStreamTabComponent implements OnInit { const messageString = this.colorizer.colorize(atob(messageObj.message), msgColour, bold) + '\n'; return timeStamp + ': ' + messageSource + ' ' + messageString; } catch (error) { - this.logService.error('Failed to filter jsonMessage from WebSocket: ' + JSON.stringify(error)); + console.error('Failed to filter jsonMessage from WebSocket: ' + JSON.stringify(error)); return jsonString; } } diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/variables-tab/variables-tab.component.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/variables-tab/variables-tab.component.ts index cec72a0142..ff7c2f0fe3 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/variables-tab/variables-tab.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application/application-tabs-base/tabs/variables-tab/variables-tab.component.ts @@ -4,19 +4,18 @@ import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../../cloud-foundry/src/cf-app-state'; -import { LoggerService } from '../../../../../../../../core/src/core/logger.service'; import { ListDataSource, } from '../../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; +import { ListConfig } from '../../../../../../../../core/src/shared/components/list/list.component.types'; +import { stratosEndpointGuidKey } from '../../../../../../../../store/src/entity-request-pipeline/pipeline.types'; import { ListAppEnvVar, } from '../../../../../../shared/components/list/list-types/app-variables/cf-app-variables-data-source'; import { CfAppVariablesListConfigService, } from '../../../../../../shared/components/list/list-types/app-variables/cf-app-variables-list-config.service'; -import { ListConfig } from '../../../../../../../../core/src/shared/components/list/list.component.types'; import { ApplicationService } from '../../../../application.service'; -import { stratosEndpointGuidKey } from '../../../../../../../../store/src/entity-request-pipeline/pipeline.types'; export interface VariableTabAllEnvVarType { name: string; @@ -39,7 +38,6 @@ export class VariablesTabComponent implements OnInit { private store: Store<CFAppState>, private appService: ApplicationService, private listConfig: ListConfig<ListAppEnvVar>, - private loggerService: LoggerService ) { this.envVarsDataSource = listConfig.getDataSource(); } @@ -96,7 +94,7 @@ export class VariablesTabComponent implements OnInit { try { return JSON.parse(value); } catch (err) { - this.loggerService.debug('Failed to parse STRATOS_PROJECT env var', err); + console.warn('Failed to parse STRATOS_PROJECT env var', err); } return ''; } diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/cli-info-application/cli-info-application.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/cli-info-application/cli-info-application.component.spec.ts index ff36e161d2..7c10059edc 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/cli-info-application/cli-info-application.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/cli-info-application/cli-info-application.component.spec.ts @@ -4,7 +4,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { CoreModule } from '../../../../../core/src/core/core.module'; import { MDAppModule } from '../../../../../core/src/core/md.module'; import { SharedModule } from '../../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { generateTestApplicationServiceProvider } from '../../../../test-framework/application-service-helper'; import { generateCfStoreModules } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CloudFoundrySharedModule } from '../../../shared/cf-shared.module'; diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/create-application/create-application.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/create-application/create-application.component.spec.ts index 0d161c70ff..482334781b 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/create-application/create-application.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/create-application/create-application.component.spec.ts @@ -7,7 +7,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { CoreModule } from '../../../../../core/src/core/core.module'; import { SharedModule } from '../../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; import { InternalEventMonitorFactory } from '../../../../../store/src/monitors/internal-event-monitor.factory'; import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-step2/deploy-application-step2.component.ts b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-step2/deploy-application-step2.component.ts index da3babd5ca..59f9703e0a 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-step2/deploy-application-step2.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-step2/deploy-application-step2.component.ts @@ -247,7 +247,7 @@ export class DeployApplicationStep2Component .pipe( // Wait for a new project name change filter(state => state && !state.checking && !state.error && state.exists), - distinctUntilChanged((x, y) => x.name === y.name), + distinctUntilChanged((x, y) => x.name.toLowerCase() === y.name.toLowerCase()), // Convert project name into branches pagination observable switchMap(state => cfEntityCatalog.gitBranch.store.getPaginationService(null, null, { diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application.component.spec.ts index 109a98e22f..2124a1e765 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application.component.spec.ts @@ -7,7 +7,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { CoreModule } from '../../../../../core/src/core/core.module'; import { getGitHubAPIURL, GITHUB_API_URL } from '../../../../../core/src/core/github.helpers'; import { SharedModule } from '../../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { generateCfStoreModules } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CloudFoundrySharedModule } from '../../../shared/cf-shared.module'; import { CfOrgSpaceDataService } from '../../../shared/data-services/cf-org-space-service.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/github-project-exists.directive.ts b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/github-project-exists.directive.ts index ac555207ef..a053f51d2c 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/github-project-exists.directive.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/github-project-exists.directive.ts @@ -14,11 +14,9 @@ interface GithubProjectExistsResponse { githubProjectError: string; } -/* tslint:disable:no-use-before-declare */ const GITHUB_PROJECT_EXISTS = { provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => GithubProjectExistsDirective), multi: true }; -/* tslint:enable */ @Directive({ selector: '[appGithubProjectExists][ngModel]', diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/edit-application/edit-application.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/edit-application/edit-application.component.spec.ts index e5de781a2a..a7ec509698 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/edit-application/edit-application.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/edit-application/edit-application.component.spec.ts @@ -6,7 +6,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { CoreModule } from '../../../../../core/src/core/core.module'; import { SharedModule } from '../../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { ApplicationServiceMock, generateTestApplicationServiceProvider, diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/new-application-base-step/new-application-base-step.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/new-application-base-step/new-application-base-step.component.spec.ts index 7ad81b99f5..3b84e92f6c 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/new-application-base-step/new-application-base-step.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/new-application-base-step/new-application-base-step.component.spec.ts @@ -3,7 +3,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { CoreModule } from '../../../../../core/src/core/core.module'; import { SharedModule } from '../../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { generateCfStoreModules } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { ApplicationDeploySourceTypes } from '../deploy-application/deploy-application-steps.types'; import { NewApplicationBaseStepComponent } from './new-application-base-step.component'; diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/routes/add-route-stepper/add-route-stepper.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/routes/add-route-stepper/add-route-stepper.component.spec.ts index f06e6bbbd2..4c544e020f 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/routes/add-route-stepper/add-route-stepper.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/routes/add-route-stepper/add-route-stepper.component.spec.ts @@ -5,7 +5,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { CoreModule } from '../../../../../../core/src/core/core.module'; import { SteppersModule } from '../../../../../../core/src/shared/components/stepper/steppers.module'; import { SharedModule } from '../../../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../core/src/tab-nav.service'; import { ApplicationServiceMock } from '../../../../../test-framework/application-service-helper'; import { generateCfStoreModules } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { ApplicationService } from '../../application.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/ssh-application/ssh-application.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/applications/ssh-application/ssh-application.component.spec.ts index 3454569951..ea7a43c9c2 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/ssh-application/ssh-application.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/ssh-application/ssh-application.component.spec.ts @@ -3,7 +3,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { CoreModule } from '../../../../../core/src/core/core.module'; import { SharedModule } from '../../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { ApplicationServiceMock } from '../../../../test-framework/application-service-helper'; import { generateCfStoreModules } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { ApplicationService } from '../application.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/add-organization/add-organization.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-organization/add-organization.component.spec.ts index 7af9fd0150..0db36dc5a0 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/add-organization/add-organization.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/add-organization/add-organization.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { AddOrganizationComponent } from './add-organization.component'; import { CreateOrganizationStepComponent } from './create-organization-step/create-organization-step.component'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/add-quota/add-quota.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-quota/add-quota.component.spec.ts index 80bc70ebd1..ef3767615e 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/add-quota/add-quota.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/add-quota/add-quota.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { LongRunningCfOperationsService } from '../../../shared/data-services/long-running-cf-op.service'; import { QuotaDefinitionFormComponent } from '../quota-definition-form/quota-definition-form.component'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/add-space-quota.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/add-space-quota.component.spec.ts index a9f62d776d..dd5ecaaa40 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/add-space-quota.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/add-space-quota/add-space-quota.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { CFBaseTestModules } from '../../../../test-framework/cf-test-helper'; import { SpaceQuotaDefinitionFormComponent } from '../space-quota-definition-form/space-quota-definition-form.component'; import { AddSpaceQuotaComponent } from './add-space-quota.component'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/add-space/add-space.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/add-space/add-space.component.spec.ts index 0684912834..ff17123de0 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/add-space/add-space.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/add-space/add-space.component.spec.ts @@ -1,10 +1,9 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../core/tab-nav.service'; -import { generateCfBaseTestModules } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; +import { CFBaseTestModules } from '../../../../test-framework/cf-test-helper'; import { AddSpaceComponent } from './add-space.component'; import { CreateSpaceStepComponent } from './create-space-step/create-space-step.component'; -import { CFBaseTestModules } from '../../../../test-framework/cf-test-helper'; describe('AddSpaceComponent', () => { let component: AddSpaceComponent; diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/cf.helpers.ts b/src/frontend/packages/cloud-foundry/src/features/cf/cf.helpers.ts index a4ddeb40cf..fb946f7108 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/cf.helpers.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/cf.helpers.ts @@ -358,7 +358,7 @@ export const cfOrgSpaceFilter = (entities: APIResource[], paginationState: Pagin const fetchOrgGuid = (e: APIResource<CfOrgSpaceFilterTypes>): string => { return e.entity.space ? e.entity.space.entity.organization_guid : null; - } + }; // Filter by cf/org/space const cfGuid = paginationState.clientPagination.filter.items.cf; diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/cli-info-cloud-foundry/cli-info-cloud-foundry.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/cli-info-cloud-foundry/cli-info-cloud-foundry.component.spec.ts index 25e3004052..9f5b515cad 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/cli-info-cloud-foundry/cli-info-cloud-foundry.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/cli-info-cloud-foundry/cli-info-cloud-foundry.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-section.module.ts b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-section.module.ts index aa22c90c69..688e900238 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-section.module.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-section.module.ts @@ -129,8 +129,6 @@ import { import { UsersRolesComponent } from './users/manage-users/manage-users.component'; import { RemoveUserComponent } from './users/remove-user/remove-user.component'; -/* tslint:disable:max-line-length */ - @NgModule({ imports: [ CommonModule, diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-section.routing.ts b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-section.routing.ts index b8b6a008f3..53f11cea56 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-section.routing.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-section.routing.ts @@ -90,11 +90,6 @@ import { InviteUsersComponent } from './users/invite-users/invite-users.componen import { UsersRolesComponent } from './users/manage-users/manage-users.component'; import { RemoveUserComponent } from './users/remove-user/remove-user.component'; -/* tslint:disable:max-line-length */ - - -/* tslint:enable:max-line-length */ - const usersRoles = [ { path: 'users/manage', diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.spec.ts index 726f61014c..27d8cdfc0c 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry-tabs-base/cloud-foundry-tabs-base.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { populateStoreWithTestEndpoint, testSCFEndpointGuid } from '@stratosui/store/testing'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry/cloud-foundry.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry/cloud-foundry.component.spec.ts index 4b8d81f2a0..6067c9a0e9 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry/cloud-foundry.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/cloud-foundry/cloud-foundry.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { populateStoreWithTestEndpoint } from '@stratosui/store/testing'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; import { generateCfBaseTestModules, diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization.component.spec.ts index e176d3137a..c93308ee77 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/edit-organization/edit-organization.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota.component.spec.ts index 71fd777494..ae177c3fa5 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/edit-quota/edit-quota.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { CFBaseTestModules } from '../../../../test-framework/cf-test-helper'; import { QuotaDefinitionFormComponent } from '../quota-definition-form/quota-definition-form.component'; import { EditQuotaStepComponent } from './edit-quota-step/edit-quota-step.component'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.ts index 8c0123ad3d..da83e79b84 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota-step/edit-space-quota-step.component.ts @@ -49,7 +49,11 @@ export class EditSpaceQuotaStepComponent implements OnDestroy { } fetchQuotaDefinition() { - this.spaceQuotaDefinition$ = cfEntityCatalog.spaceQuota.store.getEntityService(this.spaceQuotaGuid, this.cfGuid, {}).waitForEntity$.pipe( + this.spaceQuotaDefinition$ = cfEntityCatalog.spaceQuota.store.getEntityService( + this.spaceQuotaGuid, + this.cfGuid, + {} + ).waitForEntity$.pipe( map(data => data.entity), tap((resource) => this.quota = resource.entity) ); diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota.component.spec.ts index a17eee1dee..787c24d760 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space-quota/edit-space-quota.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { CFBaseTestModules } from '../../../../test-framework/cf-test-helper'; import { SpaceQuotaDefinitionFormComponent } from '../space-quota-definition-form/space-quota-definition-form.component'; import { EditSpaceQuotaStepComponent } from './edit-space-quota-step/edit-space-quota-step.component'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space.component.spec.ts index 7c09107d9c..4c99880fe0 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/edit-space/edit-space.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/quota-definition/quota-definition.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/quota-definition/quota-definition.component.spec.ts index 4fb96c3435..ffb1b72d3e 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/quota-definition/quota-definition.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/quota-definition/quota-definition.component.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; import { populateStoreWithTestEndpoint, testSCFEndpointGuid } from '@stratosui/store/testing'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/space-quota-definition/space-quota-definition.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/space-quota-definition/space-quota-definition.component.spec.ts index ee79e073f2..7bde1d12ae 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/space-quota-definition/space-quota-definition.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/space-quota-definition/space-quota-definition.component.spec.ts @@ -3,7 +3,7 @@ import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; import { testSCFEndpoint, testSCFEndpointGuid } from '@stratosui/store/testing'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { EntityCatalogHelpers } from '../../../../../store/src/entity-catalog/entity-catalog.helper'; import { EntityCatalogEntityConfig } from '../../../../../store/src/entity-catalog/entity-catalog.types'; import { endpointEntityType, stratosEntityFactory } from '../../../../../store/src/helpers/stratos-entity-factory'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.spec.ts index ee7518e8c1..760cdc4695 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-cells/cloud-foundry-cell/cloud-foundry-cell-base/cloud-foundry-cell-base.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules } from '../../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CfUserService } from '../../../../../../shared/data-services/cf-user.service'; import { ActiveRouteCfOrgSpace } from '../../../../cf-page.types'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose-formatter.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose-formatter.ts index 51062be14d..2a8615929c 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose-formatter.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose-formatter.ts @@ -1,13 +1,12 @@ -/** - * Formats log messages from the Cloud Foundry firehose - */ -import * as moment from 'moment'; +import moment from 'moment'; -import { LoggerService } from '../../../../../../core/src/core/logger.service'; import { UtilsService } from '../../../../../../core/src/core/utils.service'; import { AnsiColorizer } from '../../../../../../core/src/shared/components/log-viewer/ansi-colorizer'; import { FireHoseItem, HTTP_METHODS } from './cloud-foundry-firehose.types'; +/** + * Formats log messages from the Cloud Foundry firehose + */ /* eslint-disable no-control-regex */ const ANSI_ESCAPE_MATCHER = new RegExp('\x1B\\[([0-9;]*)m', 'g'); @@ -29,7 +28,7 @@ export class CloudFoundryFirehoseFormatter { private colorizer = new AnsiColorizer(); - constructor(private logService: LoggerService, private utils: UtilsService) { } + constructor(private utils: UtilsService) { } // Enable or disable all filters public showAll(all: boolean) { @@ -66,7 +65,7 @@ export class CloudFoundryFirehoseFormatter { filtered = this.handleOtherEvent(cfEvent); } } catch (error) { - this.logService.error('Failed to filter jsonMessage from WebSocket: ' + jsonString); + console.error('Failed to filter jsonMessage from WebSocket: ' + jsonString); filtered = jsonString; } diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose.component.ts index b2bbddcf69..e5bed949b7 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-firehose/cloud-foundry-firehose.component.ts @@ -1,9 +1,8 @@ import { Component, OnInit } from '@angular/core'; import { Observable, Subject } from 'rxjs'; import websocketConnect from 'rxjs-websockets'; -import { catchError, filter, share, map, switchMap } from 'rxjs/operators'; +import { catchError, filter, map, share, switchMap } from 'rxjs/operators'; -import { LoggerService } from '../../../../../../core/src/core/logger.service'; import { UtilsService } from '../../../../../../core/src/core/utils.service'; import { environment } from '../../../../../../core/src/environments/environment.prod'; import { CloudFoundryEndpointService } from '../../services/cloud-foundry-endpoint.service'; @@ -25,7 +24,6 @@ export class CloudFoundryFirehoseComponent implements OnInit { constructor( private cfEndpointService: CloudFoundryEndpointService, - private logService: LoggerService, private utilsService: UtilsService ) { } @@ -36,7 +34,7 @@ export class CloudFoundryFirehoseComponent implements OnInit { }/firehose`; this.setupFirehoseStream(streamUrl); - this.formatter = new CloudFoundryFirehoseFormatter(this.logService, this.utilsService); + this.formatter = new CloudFoundryFirehoseFormatter(this.utilsService); this.filter = this.formatter.jsonFilter.bind(this.formatter); } diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organization-space-quotas/cloud-foundry-organization-space-quotas.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organization-space-quotas/cloud-foundry-organization-space-quotas.component.spec.ts index 3cda3f2573..84998577c1 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organization-space-quotas/cloud-foundry-organization-space-quotas.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organization-space-quotas/cloud-foundry-organization-space-quotas.component.spec.ts @@ -1,7 +1,7 @@ import { DatePipe } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../core/src/tab-nav.service'; import { CFBaseTestModules } from '../../../../../test-framework/cf-test-helper'; import { generateTestCfEndpointServiceProvider } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-base/cloud-foundry-organization-base.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-base/cloud-foundry-organization-base.component.spec.ts index 2270c81012..5ec7cc6be9 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-base/cloud-foundry-organization-base.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-base/cloud-foundry-organization-base.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-events/cloud-foundry-organization-events.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-events/cloud-foundry-organization-events.component.spec.ts index ef58df42ab..c5bcdfd914 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-events/cloud-foundry-organization-events.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-events/cloud-foundry-organization-events.component.spec.ts @@ -1,10 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { - generateActiveRouteCfOrgSpaceMock, -} from 'frontend/packages/cloud-foundry/test-framework/cloud-foundry-endpoint-service.helper'; import { ListConfig } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { CFBaseTestModules } from '../../../../../../test-framework/cf-test-helper'; +import { generateActiveRouteCfOrgSpaceMock } from '../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CloudFoundryEventsListComponent, } from '../../../../../shared/components/cloud-foundry-events-list/cloud-foundry-events-list.component'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-organization-spaces.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-organization-spaces.component.spec.ts index 47b0e37828..3e716a9838 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-organization-spaces.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-organization-spaces.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.spec.ts index b525621558..9e8cb485d2 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.spec.ts index 542fe41631..a09f80521e 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-events/cloud-foundry-space-events.component.spec.ts @@ -1,10 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { - generateActiveRouteCfOrgSpaceMock, -} from 'frontend/packages/cloud-foundry/test-framework/cloud-foundry-endpoint-service.helper'; import { ListConfig } from '../../../../../../../../../core/src/shared/components/list/list.component.types'; import { CFBaseTestModules } from '../../../../../../../../test-framework/cf-test-helper'; +import { + generateActiveRouteCfOrgSpaceMock, +} from '../../../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CloudFoundryEventsListComponent, } from '../../../../../../../shared/components/cloud-foundry-events-list/cloud-foundry-events-list.component'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.spec.ts index d1598bc08b..d26fba5719 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-service-instances/cloud-foundry-space-service-instances.component.spec.ts @@ -10,9 +10,6 @@ import { import { ServiceActionHelperService } from '../../../../../../../shared/data-services/service-action-helper.service'; import { CloudFoundrySpaceServiceInstancesComponent } from './cloud-foundry-space-service-instances.component'; -/* tslint:disable:max-line-length */ -/* tslint:enable:max-line-length */ - @NgModule({ declarations: [TableCellAppCfOrgSpaceHeaderComponent], imports: [CommonModule], diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts index e6e27e1e5b..9b1935631e 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../../../../core/src/tab-nav.service'; import { generateActiveRouteCfOrgSpaceMock, generateCfBaseTestModules, diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.spec.ts index 0df268c9e1..48dec0b9e8 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.ts index 3b82a9e0e0..90c173f4f8 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.ts @@ -1,12 +1,14 @@ import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; -import { CFAppState } from 'frontend/packages/cloud-foundry/src/cf-app-state'; -import { CurrentUserPermissionsService } from 'frontend/packages/core/src/core/permissions/current-user-permissions.service'; import { combineLatest, Observable } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; +import { + CurrentUserPermissionsService, +} from '../../../../../../../../../core/src/core/permissions/current-user-permissions.service'; import { ListConfig } from '../../../../../../../../../core/src/shared/components/list/list.component.types'; import { CFFeatureFlagTypes } from '../../../../../../../cf-api.types'; +import { CFAppState } from '../../../../../../../cf-app-state'; import { CfSpaceUsersListConfigService, } from '../../../../../../../shared/components/list/list-types/cf-space-users/cf-space-users-list-config.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-summary/cloud-foundry-organization-summary.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-summary/cloud-foundry-organization-summary.component.spec.ts index 933c783b42..639ae6ff62 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-summary/cloud-foundry-organization-summary.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-summary/cloud-foundry-organization-summary.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-users/cloud-foundry-organization-users.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-users/cloud-foundry-organization-users.component.spec.ts index 8c989eab9b..1430a0c2e9 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-users/cloud-foundry-organization-users.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cf-organization-users/cloud-foundry-organization-users.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cloud-foundry-organizations.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cloud-foundry-organizations.component.spec.ts index 2c1572821d..ec94265165 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cloud-foundry-organizations.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-organizations/cloud-foundry-organizations.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-quotas/cloud-foundry-quotas.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-quotas/cloud-foundry-quotas.component.spec.ts index 75913ddedf..8e5c68629e 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-quotas/cloud-foundry-quotas.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-quotas/cloud-foundry-quotas.component.spec.ts @@ -1,7 +1,7 @@ import { DatePipe } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../core/src/tab-nav.service'; import { CFBaseTestModules } from '../../../../../test-framework/cf-test-helper'; import { generateTestCfEndpointServiceProvider } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CfOrgsListConfigService } from '../../../../shared/components/list/list-types/cf-orgs/cf-orgs-list-config.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-summary-tab/cloud-foundry-summary-tab.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-summary-tab/cloud-foundry-summary-tab.component.spec.ts index 25dec485d3..9c459b2db9 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-summary-tab/cloud-foundry-summary-tab.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/tabs/cf-summary-tab/cloud-foundry-summary-tab.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/users/invite-users/invite-users.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/invite-users/invite-users.component.spec.ts index 92b37bb6b0..e6586ecd83 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/users/invite-users/invite-users.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/users/invite-users/invite-users.component.spec.ts @@ -1,7 +1,7 @@ import { HttpClient, HttpHandler } from '@angular/common/http'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../core/src/tab-nav.service'; import { generateCfActiveRouteMock, generateCfBaseTestModules, diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-confirm/manage-users-confirm.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-confirm/manage-users-confirm.component.ts index 969da7b695..206a633c2f 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-confirm/manage-users-confirm.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-confirm/manage-users-confirm.component.ts @@ -30,12 +30,6 @@ import { CfUser, OrgUserRoleNames, SpaceUserRoleNames } from '../../../../../sto import { CfRoleChangeWithNames, UserRoleLabels } from '../../../../../store/types/users-roles.types'; import { ManageUsersSetUsernamesHelper } from '../manage-users-set-usernames/manage-users-set-usernames.component'; -/* tslint:disable:max-line-length */ - - - -/* tslint:enable:max-line-length */ - @Component({ selector: 'app-manage-users-confirm', templateUrl: './manage-users-confirm.component.html', @@ -84,8 +78,8 @@ export class UsersRolesConfirmComponent implements OnInit, AfterContentInit { private updateChanges = new Subject(); private nameCache: { - user: { [guid: string]: string }, - role: { [guid: string]: string }, + user: { [guid: string]: string, }, + role: { [guid: string]: string, }, } = { user: {}, role: {} @@ -129,7 +123,7 @@ export class UsersRolesConfirmComponent implements OnInit, AfterContentInit { this.store.select(selectCfUsersRoles).pipe( first(), ).subscribe(usersRoles => this.store.dispatch(new UsersRolesClearUpdateState(usersRoles.changedRoles))); - } + }; fetchUsername = (userGuid: string, users: APIResource<CfUser>[]): string => { let res = this.nameCache.user[userGuid]; @@ -139,11 +133,11 @@ export class UsersRolesConfirmComponent implements OnInit, AfterContentInit { res = users.find(user => user.metadata.guid === userGuid).entity.username; this.nameCache.user[userGuid] = res; return res; - } + }; fetchRoleName = (roleName: OrgUserRoleNames | SpaceUserRoleNames, isOrg: boolean): string => { return isOrg ? UserRoleLabels.org.short[roleName] : UserRoleLabels.space.short[roleName]; - } + }; private createCfObs() { this.cfGuid$ = this.store.select(selectCfUsersRoles).pipe( diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-modify/manage-users-modify.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-modify/manage-users-modify.component.ts index 7782611d97..aec13ada83 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-modify/manage-users-modify.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-modify/manage-users-modify.component.ts @@ -54,11 +54,7 @@ import { ActiveRouteCfOrgSpace } from '../../../cf-page.types'; import { CfRolesService } from '../cf-roles.service'; import { SpaceRolesListWrapperComponent } from './space-roles-list-wrapper/space-roles-list-wrapper.component'; -/* tslint:disable:max-line-length */ - -/* tslint:enable:max-line-length */ - -interface Org { metadata: { guid: string }; } +interface Org { metadata: { guid: string, }; } interface CfUserWithWarning extends CfUser { showWarning: boolean; } @@ -294,21 +290,21 @@ export class UsersRolesModifyComponent implements OnInit, OnDestroy { this.store.dispatch(new UsersRolesFlipSetRoles()); } }); - } + }; onLeave = (isNext: boolean) => { if (!isNext && this.snackBarRef) { this.snackBarRef.dismiss(); this.snackBarRef = null; } - } + }; onNext = () => { return combineLatest([ this.store.select(selectCfUsersIsRemove).pipe(first()), this.cfRolesService.createRolesDiff(this.selectedOrgGuid) ]).pipe( - map(([isRemove, ]) => { + map(([isRemove,]) => { if (isRemove) { // If we're going to eventually remove the roles flip the add to remove this.store.dispatch(new UsersRolesFlipSetRoles()); @@ -318,6 +314,6 @@ export class UsersRolesModifyComponent implements OnInit, OnDestroy { ).pipe(catchError(err => { return observableOf({ success: false }); })); - } + }; } diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users.component.spec.ts index 49c4be9fc8..3c67f45536 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users.component.spec.ts @@ -6,7 +6,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { CoreModule } from '../../../../../../core/src/core/core.module'; import { SharedModule } from '../../../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../core/src/tab-nav.service'; import { generateCfStoreModules } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CfUserServiceTestProvider } from '../../../../../test-framework/user-service-helper'; import { ActiveRouteCfOrgSpace } from '../../cf-page.types'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/users/remove-user/remove-user.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/remove-user/remove-user.component.spec.ts index dc025ef094..bd5d2a63f2 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/users/remove-user/remove-user.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/users/remove-user/remove-user.component.spec.ts @@ -6,7 +6,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { CoreModule } from '../../../../../../core/src/core/core.module'; import { SharedModule } from '../../../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../core/src/tab-nav.service'; import { generateCfStoreModules } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CfUserServiceTestProvider } from '../../../../../test-framework/user-service-helper'; import { ActiveRouteCfOrgSpace } from '../../cf-page.types'; diff --git a/src/frontend/packages/cloud-foundry/src/features/cf/users/remove-user/remove-user.component.ts b/src/frontend/packages/cloud-foundry/src/features/cf/users/remove-user/remove-user.component.ts index 7c4a435f3d..9a9642fe87 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cf/users/remove-user/remove-user.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cf/users/remove-user/remove-user.component.ts @@ -4,7 +4,6 @@ import { Store } from '@ngrx/store'; import { combineLatest as obsCombineLatest, Observable, of as observableOf } from 'rxjs'; import { combineLatest, filter, first, map, startWith } from 'rxjs/operators'; -import { LoggerService } from '../../../../../../core/src/core/logger.service'; import { CurrentUserPermissionsService } from '../../../../../../core/src/core/permissions/current-user-permissions.service'; import { StepOnNextFunction } from '../../../../../../core/src/shared/components/stepper/step/step.component'; import { AppState } from '../../../../../../store/src/app-state'; @@ -49,7 +48,6 @@ export class RemoveUserComponent implements OnDestroy { private activeRouteCfOrgSpace: ActiveRouteCfOrgSpace, private cfUserService: CfUserService, private cfRolesService: CfRolesService, - private logService: LoggerService, private route: ActivatedRoute, private userPerms: CurrentUserPermissionsService ) { @@ -67,7 +65,7 @@ export class RemoveUserComponent implements OnDestroy { first() ); } else { - this.logService.error('User param not defined'); + console.error('User param not defined'); return; } diff --git a/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-catalog-page/service-catalog-page.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-catalog-page/service-catalog-page.component.spec.ts index 57528a599c..90f7083f13 100644 --- a/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-catalog-page/service-catalog-page.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-catalog-page/service-catalog-page.component.spec.ts @@ -5,7 +5,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { CoreModule } from '../../../../../core/src/core/core.module'; import { SharedModule } from '../../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { generateCfStoreModules } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CfEndpointsMissingComponent } from '../../../shared/components/cf-endpoints-missing/cf-endpoints-missing.component'; import { CloudFoundryService } from '../../../shared/data-services/cloud-foundry.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.spec.ts index e25c5c6f2e..f8dc7a2755 100644 --- a/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/service-catalog/service-tabs-base/service-tabs-base.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CfUserPermissionDirective } from '../../../shared/directives/cf-user-permission/cf-user-permission.directive'; import { ServicesService } from '../services.service'; diff --git a/src/frontend/packages/cloud-foundry/src/features/services/detach-service-instance/detach-service-instance.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/services/detach-service-instance/detach-service-instance.component.spec.ts index 32ecc4e292..623415cc93 100644 --- a/src/frontend/packages/cloud-foundry/src/features/services/detach-service-instance/detach-service-instance.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/services/detach-service-instance/detach-service-instance.component.spec.ts @@ -2,7 +2,7 @@ import { DatePipe } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { ServiceActionHelperService } from '../../../shared/data-services/service-action-helper.service'; import { DetachAppsComponent } from './detach-apps/detach-apps.component'; diff --git a/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.spec.ts index 7d20df5522..7cf738ff7f 100644 --- a/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/services/services-wall/services-wall.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CfEndpointsMissingComponent } from '../../../shared/components/cf-endpoints-missing/cf-endpoints-missing.component'; import { CfOrgSpaceDataService } from '../../../shared/data-services/cf-org-space-service.service'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/add-service-instance-base-step/add-service-instance-base-step.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/add-service-instance-base-step/add-service-instance-base-step.component.spec.ts index f104e8d150..ef280c7cbd 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/add-service-instance-base-step/add-service-instance-base-step.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/add-service-instance-base-step/add-service-instance-base-step.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../core/src/tab-nav.service'; import { generateCfBaseTestModules } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { AddServiceInstanceBaseStepComponent } from './add-service-instance-base-step.component'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/add-service-instance/add-service-instance.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/add-service-instance/add-service-instance.component.spec.ts index daa030668e..f79e72c929 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/add-service-instance/add-service-instance.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/add-service-instance/add-service-instance.component.spec.ts @@ -37,7 +37,7 @@ import { } from '../../../../../../core/src/shared/components/multiline-title/multiline-title.component'; import { PageHeaderModule } from '../../../../../../core/src/shared/components/page-header/page-header.module'; import { SteppersModule } from '../../../../../../core/src/shared/components/stepper/steppers.module'; -import { TabNavService } from '../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../core/src/tab-nav.service'; import { EntityMonitorFactory } from '../../../../../../store/src/monitors/entity-monitor.factory.service'; import { InternalEventMonitorFactory } from '../../../../../../store/src/monitors/internal-event-monitor.factory'; import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/create-service-instance-helper.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/create-service-instance-helper.service.ts index f79ffc547c..cc96c5fc5d 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/create-service-instance-helper.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/add-service-instance/create-service-instance-helper.service.ts @@ -52,11 +52,15 @@ export class CreateServiceInstanceHelper { ); const paginationKey = createEntityRelationPaginationKey(servicePlanVisibilityEntityType, this.cfGuid); - this.servicePlanVisibilities$ = cfEntityCatalog.servicePlanVisibility.store.getPaginationService(this.cfGuid, paginationKey, {}).entities$ - } + this.servicePlanVisibilities$ = cfEntityCatalog.servicePlanVisibility.store.getPaginationService( + this.cfGuid, + paginationKey, + {} + ).entities$; + }; getServicePlanVisibilities = (): Observable<APIResource<IServicePlanVisibility>[]> => - this.servicePlanVisibilities$.pipe(filter(p => !!p)) + this.servicePlanVisibilities$.pipe(filter(p => !!p)); getServicePlans(): Observable<APIResource<IServicePlan>[]> { @@ -69,7 +73,7 @@ export class CreateServiceInstanceHelper { filter(p => !!p), map(getServiceName) ); - } + }; getServiceInstancesForService = (servicePlanGuid: string = null, spaceGuid: string = null, cfGuid: string = null) => { let action; @@ -77,7 +81,7 @@ export class CreateServiceInstanceHelper { if (spaceGuid) { paginationKey = createEntityRelationPaginationKey(serviceInstancesEntityType, `${spaceGuid}-${servicePlanGuid}`); const q = [new QParam('service_plan_guid', servicePlanGuid, QParamJoiners.colon).toString()]; - action = cfEntityCatalog.serviceInstance.actions.getAllInSpace(spaceGuid, cfGuid, paginationKey, q) + action = cfEntityCatalog.serviceInstance.actions.getAllInSpace(spaceGuid, cfGuid, paginationKey, q); } else if (servicePlanGuid) { paginationKey = createEntityRelationPaginationKey(serviceInstancesEntityType, servicePlanGuid); action = cfEntityCatalog.serviceInstance.actions.getAllInServicePlan(servicePlanGuid, cfGuid, paginationKey); @@ -98,5 +102,5 @@ export class CreateServiceInstanceHelper { publishReplay(1), refCount() ); - } + }; } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.ts index 8be8e3f5cb..49df7b2862 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.ts @@ -1,11 +1,11 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { fetchAutoscalerInfo } from '@stratosui/cf-autoscaler'; -import { APIResource, EntityInfo } from 'frontend/packages/store/src/types/api.types'; import { Observable, Subscription } from 'rxjs'; import { map, tap } from 'rxjs/operators'; import { EntityServiceFactory } from '../../../../../../store/src/entity-service-factory.service'; +import { APIResource, EntityInfo } from '../../../../../../store/src/types/api.types'; import { ICfV2Info } from '../../../../cf-api.types'; import { CloudFoundryEndpointService } from '../../../../features/cf/services/cloud-foundry-endpoint.service'; import { diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/cf-apps-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/cf-apps-data-source.ts index 137f9073dd..77cbca9094 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/cf-apps-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/cf-apps-data-source.ts @@ -102,13 +102,10 @@ export class CfAppsDataSource extends CFListDataSource<APIResource> { if (app instanceof MultiActionListEntity) { app = app.entity; } - const appState = app.entity.state; - const appGuid = app.metadata.guid; - const cfGuid = app.entity.cfGuid; - if (appState === 'STARTED') { + if (app.entity.state === 'STARTED') { actions.push({ - id: appGuid, - action: cfEntityCatalog.appStats.actions.getMultiple(appGuid, cfGuid) + id: app.metadata.guid, + action: cfEntityCatalog.appStats.actions.getMultiple(app.metadata.guid, app.entity.cfGuid) }); } }); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-health/cf-cell-health-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-health/cf-cell-health-list-config.service.ts index 51e4e54f65..055d15a4e3 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-health/cf-cell-health-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cell-health/cf-cell-health-list-config.service.ts @@ -1,3 +1,4 @@ +/* tslint:disable:max-line-length */ import { DatePipe } from '@angular/common'; import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; @@ -24,6 +25,8 @@ import { import { BaseCfListConfig } from '../base-cf/base-cf-list-config'; import { CfCellHealthDataSource, CfCellHealthEntry, CfCellHealthState } from './cf-cell-health-source'; +// tslint:enable:max-line-length + @Injectable() export class CfCellHealthListConfigService extends BaseCfListConfig<CfCellHealthEntry> { @@ -93,6 +96,6 @@ export class CfCellHealthListConfigService extends BaseCfListConfig<CfCellHealth field: 'state' } }, - ] + ]; getDataSource = () => this.dataSource; } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cells/cf-cells-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cells/cf-cells-list-config.service.ts index 783f2b148f..859b7a7c16 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cells/cf-cells-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-cells/cf-cells-list-config.service.ts @@ -1,3 +1,4 @@ +// tslint:disable:max-line-length import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; @@ -22,7 +23,6 @@ import { ActiveRouteCfCell } from '../../../../../features/cf/cf-page.types'; import { BaseCfListConfig } from '../base-cf/base-cf-list-config'; import { CfCellsDataSource } from './cf-cells-data-source'; -// tslint:disable:max-line-length // tslint:enable:max-line-length diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-feature-flags/table-cell-feature-flag-description/table-cell-feature-flag-description.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-feature-flags/table-cell-feature-flag-description/table-cell-feature-flag-description.component.ts index de671fa58f..324c695ac2 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-feature-flags/table-cell-feature-flag-description/table-cell-feature-flag-description.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-feature-flags/table-cell-feature-flag-description/table-cell-feature-flag-description.component.ts @@ -1,6 +1,6 @@ import { Component, Input } from '@angular/core'; -import { TableCellCustom } from 'frontend/packages/core/src/shared/components/list/list.types'; +import { TableCellCustom } from '../../../../../../../../core/src/shared/components/list/list.types'; import { IFeatureFlag } from '../../../../../../cf-api.types'; import { FeatureFlagDescriptions } from '../cf-feature-flags-data-source'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.ts index eea29808c6..1f61b851d9 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.ts @@ -33,13 +33,20 @@ export class TableCellServiceBrokerComponent extends TableCellCustom<APIResource set row(row: APIResource<IService>) { this.pRow = row; if (row && !this.spaceLink$) { - this.broker$ = cfEntityCatalog.serviceBroker.store.getEntityService(this.row.entity.service_broker_guid, this.row.entity.cfGuid, {}).waitForEntity$ - .pipe( - map(e => e.entity) - ) + this.broker$ = cfEntityCatalog.serviceBroker.store.getEntityService( + this.row.entity.service_broker_guid, + this.row.entity.cfGuid, + {} + ).waitForEntity$.pipe( + map(e => e.entity) + ); this.spaceLink$ = this.broker$.pipe( filter(broker => !!broker.entity.space_guid), - switchMap(broker => cfEntityCatalog.space.store.getWithOrganization.getEntityService(broker.entity.space_guid, broker.entity.cfGuid).waitForEntity$), + switchMap(broker => cfEntityCatalog.space.store.getWithOrganization.getEntityService( + broker.entity.space_guid, + broker.entity.cfGuid + ).waitForEntity$ + ), map(e => e.entity), map(space => ({ name: space.entity.name, @@ -53,7 +60,7 @@ export class TableCellServiceBrokerComponent extends TableCellCustom<APIResource ] }) ) - ) + ); } } get row(): APIResource<IService> { @@ -62,12 +69,12 @@ export class TableCellServiceBrokerComponent extends TableCellCustom<APIResource public spaceLink$: Observable<{ name: string, - link: string[] + link: string[], }>; - public broker$: Observable<APIResource<IServiceBroker>> + public broker$: Observable<APIResource<IServiceBroker>>; constructor() { - super() + super(); } } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-org-permission-cell/cf-org-permission-cell.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-org-permission-cell/cf-org-permission-cell.component.ts index 05653c251e..b69318d2be 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-org-permission-cell/cf-org-permission-cell.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-org-permission-cell/cf-org-permission-cell.component.ts @@ -20,7 +20,7 @@ import { getOrgRoles } from '../../../../../../features/cf/cf.helpers'; import { CfUser, IUserPermissionInOrg, OrgUserRoleNames } from '../../../../../../store/types/cf-user.types'; import { CfCurrentUserPermissions } from '../../../../../../user-permissions/cf-user-permissions-checkers'; import { CfUserService } from '../../../../../data-services/cf-user.service'; -import { CfPermissionCell, ICellPermissionList } from '../cf-permission-cell'; +import { CfPermissionCellDirective, ICellPermissionList } from '../cf-permission-cell'; @Component({ selector: 'app-org-user-permission-cell', @@ -28,7 +28,7 @@ import { CfPermissionCell, ICellPermissionList } from '../cf-permission-cell'; styleUrls: ['./cf-org-permission-cell.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class CfOrgPermissionCellComponent extends CfPermissionCell<OrgUserRoleNames> { +export class CfOrgPermissionCellComponent extends CfPermissionCellDirective<OrgUserRoleNames> { constructor( public store: Store<CFAppState>, @@ -96,6 +96,6 @@ export class CfOrgPermissionCellComponent extends CfPermissionCell<OrgUserRoleNa } public canRemovePermission = (cfGuid: string, orgGuid: string, spaceGuid: string) => - this.userPerms.can(CfCurrentUserPermissions.ORGANIZATION_CHANGE_ROLES, cfGuid, orgGuid) + this.userPerms.can(CfCurrentUserPermissions.ORGANIZATION_CHANGE_ROLES, cfGuid, orgGuid); } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-permission-cell.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-permission-cell.ts index 458162a84b..4f79ca2521 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-permission-cell.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-permission-cell.ts @@ -28,7 +28,7 @@ export interface ICellPermissionList<T> extends IUserRole<T> { } @Directive() -export abstract class CfPermissionCell<T> extends TableCellCustom<APIResource<CfUser>> { +export abstract class CfPermissionCellDirective<T> extends TableCellCustom<APIResource<CfUser>> { userEntity: BehaviorSubject<CfUser> = new BehaviorSubject(null); @Input('row') diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-space-permission-cell/cf-space-permission-cell.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-space-permission-cell/cf-space-permission-cell.component.ts index ba82dc8bb6..d5ac10914d 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-space-permission-cell/cf-space-permission-cell.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-space-permission-cell/cf-space-permission-cell.component.ts @@ -20,7 +20,7 @@ import { getSpaceRoles } from '../../../../../../features/cf/cf.helpers'; import { CfUser, IUserPermissionInSpace, SpaceUserRoleNames } from '../../../../../../store/types/cf-user.types'; import { CfCurrentUserPermissions } from '../../../../../../user-permissions/cf-user-permissions-checkers'; import { CfUserService } from '../../../../../data-services/cf-user.service'; -import { CfPermissionCell, ICellPermissionList } from '../cf-permission-cell'; +import { CfPermissionCellDirective, ICellPermissionList } from '../cf-permission-cell'; @Component({ selector: 'app-cf-space-permission-cell', @@ -28,7 +28,7 @@ import { CfPermissionCell, ICellPermissionList } from '../cf-permission-cell'; styleUrls: ['./cf-space-permission-cell.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class CfSpacePermissionCellComponent extends CfPermissionCell<SpaceUserRoleNames> { +export class CfSpacePermissionCellComponent extends CfPermissionCellDirective<SpaceUserRoleNames> { missingRoles$: Observable<boolean>; @@ -83,7 +83,7 @@ export class CfSpacePermissionCellComponent extends CfPermissionCell<SpaceUserRo filter(org => !!org), first(), map((orgs: APIResource<IOrganization>[]) => { - const orgNames: { [orgGuid: string]: string } = {}; + const orgNames: { [orgGuid: string]: string; } = {}; orgs.forEach(org => { orgNames[org.metadata.guid] = org.entity.name; }); @@ -154,5 +154,5 @@ export class CfSpacePermissionCellComponent extends CfPermissionCell<SpaceUserRo } public canRemovePermission = (cfGuid: string, orgGuid: string, spaceGuid: string) => - this.userPerms.can(CfCurrentUserPermissions.SPACE_CHANGE_ROLES, cfGuid, orgGuid, spaceGuid) + this.userPerms.can(CfCurrentUserPermissions.SPACE_CHANGE_ROLES, cfGuid, orgGuid, spaceGuid); } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/github-commits/github-commits-list-config-app-tab.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/github-commits/github-commits-list-config-app-tab.service.ts index fbca27d4a5..3315917aa2 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/github-commits/github-commits-list-config-app-tab.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/github-commits/github-commits-list-config-app-tab.service.ts @@ -1,7 +1,7 @@ import { DatePipe } from '@angular/common'; import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import * as moment from 'moment'; +import moment from 'moment'; import { Observable } from 'rxjs'; import { combineLatest, filter, first, map } from 'rxjs/operators'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/github-pagination.helper.ts b/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/github-pagination.helper.ts new file mode 100644 index 0000000000..59aaccfdf5 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/github-pagination.helper.ts @@ -0,0 +1,153 @@ +import { HttpClient, HttpResponse } from '@angular/common/http'; +import { PaginationFlattener } from '@stratosui/store'; +import { Observable } from 'rxjs'; + +// --------------- Note ---------------- +// There are two types of pagination github uses +// 1) Pagination info is in the body of the response (GithubPaginationResponse / GithubFlattenerPaginationConfig) +// - Array is in the body of the response +// - Things like total number of results in the body of the response +// 2) Pagination info is in the response header (GithubPaginationArrayResponse / GithubFlattenerForArrayPaginationConfig) +// - Array is the body of the response +// - Thinks like total number of results are in a `link` property in the response header + + +/** + * Body of a github pagination response (pagination info inside body) + */ +type GithubPaginationResponse<T = any> = { + incomplete_results: boolean, + items: T[], + total_count: number, +}; + +/** + * Body of a github pagination response (pagination info is in header) + */ +type GithubPaginationArrayResponse<T = any> = T[]; + +export const GITHUB_PER_PAGE_PARAM = 'per_page'; +export const GITHUB_PER_PAGE_PARAM_VALUE = 100; +const GITHUB_MAX_PAGES = 5; +const GITHUB_PAGE_PARAM = 'page'; +const GITHUB_LINK_PAGE_REGEX = /page=([\d]*)/; + +/** + * Config used with `flattenPagination`. To use when the pagination info is in the body + */ +export class GithubFlattenerPaginationConfig<T> implements PaginationFlattener<T[], GithubPaginationResponse<T>> { + constructor( + private httpClient: HttpClient, + public url: string, + ) { } + + getTotalPages = (res: GithubPaginationResponse<T>): number => { + const total = Math.floor(this.getTotalResults(res) / GITHUB_PER_PAGE_PARAM_VALUE) + 1; + if (total > GITHUB_MAX_PAGES) { + console.warn(`Not fetching all github entities (too many pages: ${total})`); + return GITHUB_MAX_PAGES; + } + return total; + }; + getTotalResults = (res: GithubPaginationResponse<T>): number => { + return res.total_count; + }; + mergePages = (response: any[]): T[] => { + return response.reduce((all, res) => { + return all.concat(...res.items); + }, [] as T[]); + }; + fetch = (...args: any[]): Observable<GithubPaginationResponse<T>> => { + return this.httpClient.get<GithubPaginationResponse<T>>( + this.url, + { + params: { + ...args[0] + }, + } + ); + }; + buildFetchParams = (i: number): any[] => { + const requestOption = { + [GITHUB_PAGE_PARAM]: i.toString(), + [GITHUB_PER_PAGE_PARAM]: GITHUB_PER_PAGE_PARAM_VALUE.toString() + }; + return [requestOption]; + }; + clearResults = (res: GithubPaginationResponse<T>, allResults: number) => { + throw new Error('Not Implemented'); + }; +} + +/** + * Config used with `flattenPagination`. To use when the pagination info in the response header + */ +export class GithubFlattenerForArrayPaginationConfig<T> + implements PaginationFlattener<GithubPaginationArrayResponse<T>, HttpResponse<GithubPaginationArrayResponse<T>>> { + constructor( + private httpClient: HttpClient, + public url: string, + ) { } + + getTotalPages = (res: HttpResponse<GithubPaginationArrayResponse<T>>): number => { + // Link: <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=2>; rel="next", + // <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=34>; rel="last" + + const link = res.headers.get('link'); + if (!link) { + // There's no `link` if there's only one page..... + return 1; + } + const parts = link.split(','); + if (!parts.length) { + throw new Error('Unable to depagination github request (no commas in `link`)'); + } + const last = parts.find(part => part.endsWith('rel="last"')); + if (!last) { + throw new Error('Unable to depagination github request (no `last` in `link`)'); + } + const trimmedLast = last.trim(); + const lastUrl = trimmedLast.slice(1, trimmedLast.indexOf('>')); + const lastPageNumber = GITHUB_LINK_PAGE_REGEX.exec(lastUrl); + if (lastPageNumber.length < 2) { + throw new Error(`Unable to depagination github request (could not find page number in ${lastUrl})`); + } + + const total = parseInt(lastPageNumber[1], 10); + if (total > GITHUB_MAX_PAGES) { + console.warn(`Not fetching all github entities (too many pages: ${total})`); + return GITHUB_MAX_PAGES; + } + return total; + }; + getTotalResults = (res: HttpResponse<GithubPaginationArrayResponse<T>>): number => { + return this.getTotalPages(res) * GITHUB_PER_PAGE_PARAM_VALUE; + }; + mergePages = (response: any[]): GithubPaginationArrayResponse<T> => { + return response.reduce((all, res) => { + return all.concat(...res.body); + }, [] as GithubPaginationArrayResponse<T>); + }; + fetch = (...args: any[]): Observable<HttpResponse<GithubPaginationArrayResponse<T>>> => { + return this.httpClient.get<GithubPaginationArrayResponse<T>>( + this.url, + { + params: { + ...args[0] + }, + // Required to ensure we can access the https response header + observe: 'response' + } + ); + }; + buildFetchParams = (i: number): any[] => { + const requestOption = { + [GITHUB_PAGE_PARAM]: i.toString(), + [GITHUB_PER_PAGE_PARAM]: GITHUB_PER_PAGE_PARAM_VALUE.toString() + }; + return [requestOption]; + }; + clearResults = (res: HttpResponse<GithubPaginationArrayResponse<T>>, allResults: number) => { + throw new Error('Not Implemented'); + }; +} \ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/github-scm.ts b/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/github-scm.ts index 5c6180fb38..915eae9576 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/github-scm.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/github-scm.ts @@ -1,9 +1,16 @@ import { HttpClient } from '@angular/common/http'; +import { flattenPagination } from '@stratosui/store'; import { Observable } from 'rxjs'; -import { filter, map } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import { getGitHubAPIURL } from '../../../../../core/src/core/github.helpers'; import { GitBranch, GitCommit, GitRepo } from '../../../store/types/git.types'; +import { + GITHUB_PER_PAGE_PARAM, + GITHUB_PER_PAGE_PARAM_VALUE, + GithubFlattenerForArrayPaginationConfig, + GithubFlattenerPaginationConfig, +} from './github-pagination.helper'; import { GitSCM, SCMIcon } from './scm'; import { GitSCMType } from './scm.service'; @@ -37,7 +44,14 @@ export class GitHubSCM implements GitSCM { } getBranches(httpClient: HttpClient, projectName: string): Observable<GitBranch[]> { - return httpClient.get(`${this.gitHubURL}/repos/${projectName}/branches`) as Observable<GitBranch[]>; + const url = `${this.gitHubURL}/repos/${projectName}/branches`; + const config = new GithubFlattenerForArrayPaginationConfig<GitBranch>(httpClient, url) + const firstRequest = config.fetch(...config.buildFetchParams(1)) + return flattenPagination( + null, + firstRequest, + config + ) } getCommit(httpClient: HttpClient, projectName: string, commitSha: string): Observable<GitCommit> { @@ -49,7 +63,12 @@ export class GitHubSCM implements GitSCM { } getCommits(httpClient: HttpClient, projectName: string, ref: string): Observable<GitCommit[]> { - return httpClient.get(`${this.gitHubURL}/repos/${projectName}/commits?sha=${ref}`) as Observable<GitCommit[]>; + return httpClient.get<GitCommit[]>( + `${this.gitHubURL}/repos/${projectName}/commits?sha=${ref}`, { + params: { + [GITHUB_PER_PAGE_PARAM]: GITHUB_PER_PAGE_PARAM_VALUE.toString() + } + }); } getCloneURL(projectName: string): string { @@ -70,12 +89,18 @@ export class GitHubSCM implements GitSCM { if (prjParts.length > 1) { url = `${this.gitHubURL}/search/repositories?q=${prjParts[1]}+in:name+fork:true+user:${prjParts[0]}`; } - return httpClient.get(url).pipe( - filter((repos: any) => !!repos.items), + + const config = new GithubFlattenerPaginationConfig<GitRepo>(httpClient, url) + const firstRequest = config.fetch(...config.buildFetchParams(1)) + return flattenPagination( + null, + firstRequest, + config + ).pipe( map(repos => { - return repos.items.map(item => item.full_name); + return repos.map(item => item.full_name); }) - ); + ) } public convertCommit(projectName: string, commit: any): GitCommit { diff --git a/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/gitlab-scm.ts b/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/gitlab-scm.ts index 0d6b210764..badb54157d 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/gitlab-scm.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/data-services/scm/gitlab-scm.ts @@ -1,6 +1,6 @@ import { HttpClient, HttpErrorResponse } from '@angular/common/http'; -import { Observable, of as observableOf } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { combineLatest, Observable, of as observableOf, of } from 'rxjs'; +import { catchError, map } from 'rxjs/operators'; import { Md5 } from 'ts-md5/dist/md5'; import { GitBranch, GitCommit, GitRepo } from '../../../store/types/git.types'; @@ -8,6 +8,8 @@ import { GitSCM, SCMIcon } from './scm'; import { GitSCMType } from './scm.service'; const gitLabAPIUrl = 'https://gitlab.com/api/v4'; +const GITLAB_PER_PAGE_PARAM = 'per_page'; +const GITLAB_PER_PAGE_PARAM_VALUE = 100; export class GitLabSCM implements GitSCM { @@ -29,19 +31,18 @@ export class GitLabSCM implements GitSCM { getRepository(httpClient: HttpClient, projectName: string): Observable<GitRepo> { const parts = projectName.split('/'); - let obs$ = httpClient.get(`${gitLabAPIUrl}/users/${parts[0]}/projects?search=${parts[1]}`); - if (parts.length !== 2) { - obs$ = observableOf(null); - } + const obs$ = parts.length !== 2 ? + observableOf(null) : + httpClient.get(`${gitLabAPIUrl}/projects/${parts.join('%2F')}`); return obs$.pipe( map((data: any) => { - if (data.length !== 1) { + if (!data) { throw new HttpErrorResponse({ status: 404 }); } - return this.convertProject(data[0]); + return this.convertProject(data); }) ); } @@ -59,7 +60,13 @@ export class GitLabSCM implements GitSCM { getBranches(httpClient: HttpClient, projectName: string): Observable<GitBranch[]> { const prjNameEncoded = encodeURIComponent(projectName); - return httpClient.get(`${gitLabAPIUrl}/projects/${prjNameEncoded}/repository/branches`).pipe( + return httpClient.get( + `${gitLabAPIUrl}/projects/${prjNameEncoded}/repository/branches`, { + params: { + [GITLAB_PER_PAGE_PARAM]: GITLAB_PER_PAGE_PARAM_VALUE.toString() + } + } + ).pipe( map((data: any) => { const branches = []; data.forEach(b => { @@ -87,7 +94,13 @@ export class GitLabSCM implements GitSCM { getCommits(httpClient: HttpClient, projectName: string, commitSha: string): Observable<GitCommit[]> { const prjNameEncoded = encodeURIComponent(projectName); - return httpClient.get(`${gitLabAPIUrl}/projects/${prjNameEncoded}/repository/commits?ref_name=${commitSha}`).pipe( + return httpClient.get( + `${gitLabAPIUrl}/projects/${prjNameEncoded}/repository/commits?ref_name=${commitSha}`, { + params: { + [GITLAB_PER_PAGE_PARAM]: GITLAB_PER_PAGE_PARAM_VALUE.toString() + } + } + ).pipe( map((data: any) => { const commits = []; data.forEach(c => commits.push(this.convertCommit(projectName, c))); @@ -110,17 +123,29 @@ export class GitLabSCM implements GitSCM { getMatchingRepositories(httpClient: HttpClient, projectName: string): Observable<string[]> { const prjParts = projectName.split('/'); - let url = `${gitLabAPIUrl}/projects?search=${projectName}`; - if (prjParts.length > 1) { - url = `${gitLabAPIUrl}/users/${prjParts[0]}/projects?search=${prjParts[1]}`; - } - return httpClient.get(url).pipe( - map((repos: any) => { - return repos.map(item => item.path_with_namespace); - }) + + const obs$ = prjParts.length > 1 ? + this.getMatchingUserGroupRepositories(httpClient, prjParts) : + httpClient.get(`${gitLabAPIUrl}/projects?search=${projectName}`, { + params: { + [GITLAB_PER_PAGE_PARAM]: GITLAB_PER_PAGE_PARAM_VALUE.toString() + } + }); + + return obs$.pipe( + map((repos: any[]) => repos.map(item => item.path_with_namespace)), ); } + private getMatchingUserGroupRepositories(httpClient: HttpClient, prjParts: string[]): Observable<any[]> { + return combineLatest([ + httpClient.get<[]>(`${gitLabAPIUrl}/users/${prjParts[0]}/projects/?search=${prjParts[1]}`).pipe(catchError(() => of([]))), + httpClient.get<[]>(`${gitLabAPIUrl}/groups/${prjParts[0]}/projects?search=${prjParts[1]}`).pipe(catchError(() => of([]))), + ]).pipe( + map(([a, b]: [any[], any[]]) => a.concat(b)), + ) + } + private convertProject(prj: any): GitRepo { return { ...prj, diff --git a/src/frontend/packages/cloud-foundry/src/shared/directives/app-name-unique.directive/app-name-unique.directive.ts b/src/frontend/packages/cloud-foundry/src/shared/directives/app-name-unique.directive/app-name-unique.directive.ts index eab0dac1b9..59870a82a3 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/directives/app-name-unique.directive/app-name-unique.directive.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/directives/app-name-unique.directive/app-name-unique.directive.ts @@ -9,11 +9,9 @@ import { CFAppState } from '../../../cf-app-state'; import { environment } from './../../../../../core/src/environments/environment.prod'; import { selectNewAppState } from './../../../store/effects/create-app-effects'; -/* tslint:disable:no-use-before-declare */ const APP_UNIQUE_NAME_PROVIDER = { provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => AppNameUniqueDirective), multi: true }; -/* tslint:enable */ // See: https://medium.com/@kahlil/asynchronous-validation-with-angular-reactive-forms-1a392971c062 @@ -65,7 +63,7 @@ export class AppNameUniqueDirective implements AsyncValidator, OnInit { this.appApplicationNameUnique.set(false); } - public validate(control: AbstractControl): Observable<{ appNameTaken: boolean } | null> { + public validate(control: AbstractControl): Observable<{ appNameTaken: boolean; } | null> { if (!control.dirty) { return observableOf(null); } diff --git a/src/frontend/packages/cloud-foundry/src/store/effects/deploy-app.effects.ts b/src/frontend/packages/cloud-foundry/src/store/effects/deploy-app.effects.ts index 11a4310bf8..c840fe50b1 100644 --- a/src/frontend/packages/cloud-foundry/src/store/effects/deploy-app.effects.ts +++ b/src/frontend/packages/cloud-foundry/src/store/effects/deploy-app.effects.ts @@ -5,7 +5,6 @@ import { Store } from '@ngrx/store'; import { of as observableOf } from 'rxjs'; import { catchError, filter, map, mergeMap, switchMap, withLatestFrom } from 'rxjs/operators'; -import { LoggerService } from '../../../../core/src/core/logger.service'; import { entityCatalog } from '../../../../store/src/entity-catalog/entity-catalog'; import { NormalizedResponse } from '../../../../store/src/types/api.types'; import { PaginatedAction } from '../../../../store/src/types/pagination.types'; @@ -36,24 +35,24 @@ import { CF_ENDPOINT_TYPE } from '../../cf-types'; import { selectDeployAppState } from '../selectors/deploy-application.selector'; import { GitCommit } from '../types/git.types'; -function parseHttpPipeError(res: any, logger: LoggerService): { message?: string } { +function parseHttpPipeError(res: any): { message?: string } { if (!res.status) { return res; } try { return res.json ? res.json() : res; } catch (e) { - logger.warn('Failed to parse response body', e); + console.warn('Failed to parse response body', e); } return {}; } -export function createFailedGithubRequestMessage(error: any, logger: LoggerService) { - const response = parseHttpPipeError(error, logger); +export function createFailedGithubRequestMessage(error: any) { + const response = parseHttpPipeError(error); const message = response.message || ''; return error.status === 403 && message.startsWith('API rate limit exceeded for') ? - 'Github ' + message.substring(0, message.indexOf('(')) : - 'Github request failed'; + 'Git ' + message.substring(0, message.indexOf('(')) : + 'Git request failed'; } @Injectable() @@ -61,7 +60,6 @@ export class DeployAppEffects { constructor( private actions$: Actions, private store: Store<CFAppState>, - private logger: LoggerService, private httpClient: HttpClient ) { } @@ -77,7 +75,7 @@ export class DeployAppEffects { map(res => new ProjectExists(action.projectName, res)), catchError(err => observableOf(err.status === 404 ? new ProjectDoesntExist(action.projectName) : - new ProjectFetchFail(action.projectName, createFailedGithubRequestMessage(err, this.logger)) + new ProjectFetchFail(action.projectName, createFailedGithubRequestMessage(err)) )) ); }) @@ -120,7 +118,7 @@ export class DeployAppEffects { ]; }), catchError(err => [ - new WrapperRequestActionFailed(createFailedGithubRequestMessage(err, this.logger), apiAction, actionType) + new WrapperRequestActionFailed(createFailedGithubRequestMessage(err), apiAction, actionType) ])); })); @@ -152,7 +150,7 @@ export class DeployAppEffects { ]; }), catchError(err => [ - new WrapperRequestActionFailed(createFailedGithubRequestMessage(err, this.logger), apiAction, actionType) + new WrapperRequestActionFailed(createFailedGithubRequestMessage(err), apiAction, actionType) ])); })); @@ -180,7 +178,7 @@ export class DeployAppEffects { ]; }), catchError(err => [ - new WrapperRequestActionFailed(createFailedGithubRequestMessage(err, this.logger), apiAction, actionType) + new WrapperRequestActionFailed(createFailedGithubRequestMessage(err), apiAction, actionType) ])); })); @@ -211,7 +209,7 @@ export class DeployAppEffects { ]; }), catchError(err => [ - new WrapperRequestActionFailed(createFailedGithubRequestMessage(err, this.logger), apiAction, actionType) + new WrapperRequestActionFailed(createFailedGithubRequestMessage(err), apiAction, actionType) ])); })); diff --git a/src/frontend/packages/cloud-foundry/src/store/effects/github.effects.ts b/src/frontend/packages/cloud-foundry/src/store/effects/github.effects.ts index 0864d7b8da..cf534bc46a 100644 --- a/src/frontend/packages/cloud-foundry/src/store/effects/github.effects.ts +++ b/src/frontend/packages/cloud-foundry/src/store/effects/github.effects.ts @@ -4,7 +4,6 @@ import { Actions, Effect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; import { catchError, mergeMap } from 'rxjs/operators'; -import { LoggerService } from '../../../../core/src/core/logger.service'; import { NormalizedResponse } from '../../../../store/src/types/api.types'; import { StartRequestAction, @@ -29,7 +28,6 @@ export class GithubEffects { private actions$: Actions, private store: Store<CFAppState>, private scmService: GitSCMService, - private logger: LoggerService, private httpClient: HttpClient ) { } @Effect() @@ -57,7 +55,7 @@ export class GithubEffects { ]; }), catchError(err => [ - new WrapperRequestActionFailed(createFailedGithubRequestMessage(err, this.logger), apiAction, actionType) + new WrapperRequestActionFailed(createFailedGithubRequestMessage(err), apiAction, actionType) ] )); })); diff --git a/src/frontend/packages/cloud-foundry/src/store/effects/request.effects.ts b/src/frontend/packages/cloud-foundry/src/store/effects/request.effects.ts index 9cd7107c35..ed653a0ddd 100644 --- a/src/frontend/packages/cloud-foundry/src/store/effects/request.effects.ts +++ b/src/frontend/packages/cloud-foundry/src/store/effects/request.effects.ts @@ -3,7 +3,6 @@ import { Actions, Effect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; import { catchError, first, map, mergeMap, withLatestFrom } from 'rxjs/operators'; -import { LoggerService } from '../../../../core/src/core/logger.service'; import { SET_PAGE_BUSY } from '../../../../store/src/actions/pagination.actions'; import { rootUpdatingKey } from '../../../../store/src/reducers/api-request-reducer/types'; import { getAPIRequestDataState } from '../../../../store/src/selectors/api.selectors'; @@ -26,7 +25,6 @@ export class CfValidateEffects { constructor( private actions$: Actions, private store: Store<CFAppState>, - private logger: LoggerService, ) { } /** @@ -89,7 +87,7 @@ export class CfValidateEffects { }) ) .pipe(catchError(error => { - this.logger.warn(`Entity validation process failed`, error); + console.warn(`Entity validation process failed`, error); this.update(apiAction, false, error.message); return []; })); diff --git a/src/frontend/packages/cloud-foundry/src/store/effects/users-roles.effects.ts b/src/frontend/packages/cloud-foundry/src/store/effects/users-roles.effects.ts index c75fb997b0..789eeb837e 100644 --- a/src/frontend/packages/cloud-foundry/src/store/effects/users-roles.effects.ts +++ b/src/frontend/packages/cloud-foundry/src/store/effects/users-roles.effects.ts @@ -2,9 +2,6 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Actions, Effect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; -import { - ManageUsersSetUsernamesHelper, -} from 'frontend/packages/cloud-foundry/src/features/cf/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component'; import { combineLatest as observableCombineLatest, combineLatest, Observable, of as observableOf, of } from 'rxjs'; import { catchError, filter, first, map, mergeMap, pairwise, switchMap, tap, withLatestFrom } from 'rxjs/operators'; @@ -21,6 +18,9 @@ import { AddCfUserRole, ChangeCfUserRole, RemoveCfUserRole } from '../../actions import { CFAppState } from '../../cf-app-state'; import { organizationEntityType, spaceEntityType } from '../../cf-entity-types'; import { CF_ENDPOINT_TYPE } from '../../cf-types'; +import { + ManageUsersSetUsernamesHelper, +} from '../../features/cf/users/manage-users/manage-users-set-usernames/manage-users-set-usernames.component'; import { CfUserService } from '../../shared/data-services/cf-user.service'; import { fetchCfUserRole } from '../../user-permissions/cf-user-roles-fetch'; import { selectCfUsersRoles } from '../selectors/cf-users-roles.selector'; diff --git a/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-permissions-checkers.ts b/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-permissions-checkers.ts index 1aead1cd0f..f30b7697b4 100644 --- a/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-permissions-checkers.ts +++ b/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-permissions-checkers.ts @@ -39,7 +39,7 @@ export const cfCurrentUserPermissionsService = [ deps: [Store] }, CurrentUserPermissionsService, -] +]; export enum CfCurrentUserPermissions { APPLICATION_VIEW = 'view.application', @@ -164,7 +164,10 @@ export const cfPermissionConfigs: IPermissionConfigs = { [CfCurrentUserPermissions.ORGANIZATION_DELETE]: new PermissionConfig(CfPermissionTypes.ENDPOINT_SCOPE, CfScopeStrings.CF_ADMIN_GROUP), [CfCurrentUserPermissions.ORGANIZATION_EDIT]: new PermissionConfigLink(CfCurrentUserPermissions.ORGANIZATION_DELETE), [CfCurrentUserPermissions.ORGANIZATION_SUSPEND]: new PermissionConfig(CfPermissionTypes.ENDPOINT_SCOPE, CfScopeStrings.CF_ADMIN_GROUP), - [CfCurrentUserPermissions.ORGANIZATION_CHANGE_ROLES]: new PermissionConfig(CfPermissionTypes.ORGANIZATION, CfPermissionStrings.ORG_MANAGER), + [CfCurrentUserPermissions.ORGANIZATION_CHANGE_ROLES]: new PermissionConfig( + CfPermissionTypes.ORGANIZATION, + CfPermissionStrings.ORG_MANAGER + ), [CfCurrentUserPermissions.SERVICE_INSTANCE_DELETE]: new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_DEVELOPER), [CfCurrentUserPermissions.SERVICE_INSTANCE_CREATE]: new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_DEVELOPER), [CfCurrentUserPermissions.SERVICE_INSTANCE_EDIT]: new PermissionConfig(CfPermissionTypes.SPACE, CfPermissionStrings.SPACE_DEVELOPER), @@ -191,7 +194,9 @@ export class CfUserPermissionsChecker extends BaseCurrentUserPermissionsChecker endpointGuid?: string, orgOrSpaceGuid?: string, allSpacesWithinOrg = false - ) { + ): Observable<boolean> { + // In some situations the observable returned here is not subscribed to (for example due to applyAdminCheck). + // This is bad (we should skip this function entirely) and should be fixed. This would require a thorough appraisal and overhaul. if (type === CfPermissionTypes.ENDPOINT_SCOPE) { if (!endpointGuid) { return of(false); @@ -217,8 +222,8 @@ export class CfUserPermissionsChecker extends BaseCurrentUserPermissionsChecker }; /** - * @param permissionConfig Single permission to be checked - */ + * @param permissionConfig Single permission to be checked + */ public getSimpleCheck(permissionConfig: PermissionConfig, endpointGuid?: string, orgOrSpaceGuid?: string, spaceGuid?: string) { const check$ = this.getBaseSimpleCheck(permissionConfig, endpointGuid, orgOrSpaceGuid, spaceGuid); if (permissionConfig.type === CfPermissionTypes.ORGANIZATION || permissionConfig.type === CfPermissionTypes.SPACE) { @@ -327,8 +332,8 @@ export class CfUserPermissionsChecker extends BaseCurrentUserPermissionsChecker } /** - * Includes read only admins, global auditors and users that don't have the cloud_controller.write scope - */ + * Includes read only admins, global auditors and users that don't have the cloud_controller.write scope + */ private getReadOnlyCheck(endpointGuid: string) { return this.getCfEndpointState(endpointGuid).pipe( map( @@ -357,6 +362,7 @@ export class CfUserPermissionsChecker extends BaseCurrentUserPermissionsChecker return of(true); } if (isReadOnly) { + // This is bad, we should not assume that the check type wants a negative result if the user only has 'read only' rights. return of(false); } return check$; @@ -365,8 +371,8 @@ export class CfUserPermissionsChecker extends BaseCurrentUserPermissionsChecker } /** - * If no endpoint is passed, check them all - */ + * If no endpoint is passed, check them all + */ private getReadOnlyChecks(endpointGuid?: string) { const endpointGuids$ = this.getEndpointGuidObservable(endpointGuid); return endpointGuids$.pipe( @@ -431,9 +437,9 @@ export class CfUserPermissionsChecker extends BaseCurrentUserPermissionsChecker const groupedChecks = this.groupConfigs(permissionConfigs); return Object.keys(groupedChecks).map((permission: PermissionTypes) => { const configGroup = groupedChecks[permission]; - const checkCombiner = this.getBaseCheckFromConfig(configGroup, permission, endpointGuid, orgOrSpaceGuid, spaceGuid) + const checkCombiner = this.getBaseCheckFromConfig(configGroup, permission, endpointGuid, orgOrSpaceGuid, spaceGuid); if (checkCombiner) { - checkCombiner.checks = checkCombiner.checks.map(check$ => this.applyAdminCheck(check$, endpointGuid)) + checkCombiner.checks = checkCombiner.checks.map(check$ => this.applyAdminCheck(check$, endpointGuid)); } return checkCombiner; }); diff --git a/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-roles-fetch.ts b/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-roles-fetch.ts index 06340d5496..e26807ae1b 100644 --- a/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-roles-fetch.ts +++ b/src/frontend/packages/cloud-foundry/src/user-permissions/cf-user-roles-fetch.ts @@ -31,7 +31,10 @@ import { cfEntityCatalog } from '../cf-entity-catalog'; import { CF_ENDPOINT_TYPE } from '../cf-types'; import { CFResponse } from '../store/types/cf-api.types'; -const createEndpointArray = (store: Store<AppState>, endpoints: string[] | EntityUserRolesEndpoint[]): Observable<EntityUserRolesEndpoint[]> => { +const createEndpointArray = ( + store: Store<AppState>, + endpoints: string[] | EntityUserRolesEndpoint[] +): Observable<EntityUserRolesEndpoint[]> => { // If there's no endpoints get all from store. Alternatively fetch specific endpoint id's from store if (!endpoints || !endpoints.length || typeof (endpoints[0]) === 'string') { const endpointIds = endpoints as string[]; @@ -44,7 +47,7 @@ const createEndpointArray = (store: Store<AppState>, endpoints: string[] | Entit ); } return of(endpoints as EntityUserRolesEndpoint[]); -} +}; export const cfUserRolesFetch: EntityUserRolesFetch = ( endpoints: string[] | EntityUserRolesEndpoint[], @@ -56,7 +59,7 @@ export const cfUserRolesFetch: EntityUserRolesFetch = ( const isAllAdmins = cfEndpoints.every(endpoint => !!endpoint.user.admin); // If all endpoints are connected as admin, there's no permissions to fetch. So only update the permission state to initialised if (isAllAdmins) { - cfEndpoints.forEach(endpoint => store.dispatch(new GetCfUserRelations(endpoint.guid, GET_CURRENT_CF_USER_RELATIONS_SUCCESS))) + cfEndpoints.forEach(endpoint => store.dispatch(new GetCfUserRelations(endpoint.guid, GET_CURRENT_CF_USER_RELATIONS_SUCCESS))); } else { // If some endpoints are not connected as admin, go out and fetch the current user's specific roles const flagsAndRoleRequests = dispatchRoleRequests(cfEndpoints, store, httpClient); @@ -67,8 +70,8 @@ export const cfUserRolesFetch: EntityUserRolesFetch = ( } return of(true); }) - ) -} + ); +}; interface CfsRequestState { [cfGuid: string]: Observable<boolean>[]; @@ -97,7 +100,7 @@ function dispatchRoleRequests( store.dispatch(new GetCfUserRelations(endpoint.guid, GET_CURRENT_CF_USER_RELATIONS)); // Dispatch feature flags fetch actions - const ffAction = cfEntityCatalog.featureFlag.actions.getMultiple(endpoint.guid) + const ffAction = cfEntityCatalog.featureFlag.actions.getMultiple(endpoint.guid); requests[endpoint.guid] = [createPaginationCompleteWatcher(store, ffAction)]; store.dispatch(ffAction); @@ -142,7 +145,7 @@ function fetchCfUserRoles(endpoint: IEndpointConnectionInfo, store: Store<AppSta class PermissionFlattener extends BaseHttpClientFetcher<CFResponse> implements PaginationFlattener<CFResponse, CFResponse> { - constructor(httpClient: HttpClient, public url, public requestOptions: { [key: string]: any }) { + constructor(httpClient: HttpClient, public url, public requestOptions: { [key: string]: any; }) { super(httpClient, url, requestOptions, 'page'); } public getTotalPages = (res: CFResponse) => res.total_pages; @@ -156,7 +159,7 @@ class PermissionFlattener extends BaseHttpClientFetcher<CFResponse> implements P return finalRes; }, firstRes); return final; - } + }; public getTotalResults = (res: CFResponse): number => res.total_results; public clearResults = (res: CFResponse) => of(res); } diff --git a/src/frontend/packages/core/sass/components/json-schema-form.scss b/src/frontend/packages/core/sass/components/json-schema-form.scss new file mode 100644 index 0000000000..9f004b481a --- /dev/null +++ b/src/frontend/packages/core/sass/components/json-schema-form.scss @@ -0,0 +1,5 @@ +json-schema-form { + mat-error.mat-error { + margin-bottom: .05rem; + } +} diff --git a/src/frontend/packages/core/src/api-driven-views/api-drive-views.types.ts b/src/frontend/packages/core/src/api-driven-views/api-drive-views.types.ts deleted file mode 100644 index 7e902d54dd..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/api-drive-views.types.ts +++ /dev/null @@ -1,7 +0,0 @@ -export class ApiEntityType { - constructor( - public type: string, - public title: string, - public imageUrl?: string - ) { } -} diff --git a/src/frontend/packages/core/src/api-driven-views/api-driven-views-routing.module.ts b/src/frontend/packages/core/src/api-driven-views/api-driven-views-routing.module.ts deleted file mode 100644 index 1f7e0d4da2..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/api-driven-views-routing.module.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; - -import { ApiEndpointTypeSelectPageComponent } from './features/api-endpoint-type-select-page/api-endpoint-type-select-page.component'; -import { ApiEntityTypeSelectPageComponent } from './features/api-entity-type-select-page/api-entity-type-select-page.component'; -import { ApiEndpointSelectPageComponent } from './features/api-endpoint-select-page/api-endpoint-select-page.component'; -import { ApiEntityListPageComponent } from './features/api-entity-list-page/api-entity-list-page.component'; - -const routes: Routes = [ - { - path: '', - component: ApiEndpointTypeSelectPageComponent, - // children: [ - // { - // path: ':endpointType', - // component: ApiEntityTypeSelectPageComponent, - // children: [{ - // path: '/:endpointGuid', - // children: [{ - // path: '/:entityType' - // }] - // }] - // } - // ] - - }, - { - path: ':endpointType', - component: ApiEndpointSelectPageComponent - }, - { - path: ':endpointType/:endpointId', - component: ApiEntityTypeSelectPageComponent, - children: [{ - path: ':entityType', - component: ApiEntityListPageComponent - }] - } -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class ApiDrivenViewsRoutingModule { } diff --git a/src/frontend/packages/core/src/api-driven-views/api-driven-views.module.ts b/src/frontend/packages/core/src/api-driven-views/api-driven-views.module.ts deleted file mode 100644 index 351b6eb173..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/api-driven-views.module.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; - -import { ApiDrivenViewsRoutingModule } from './api-driven-views-routing.module'; -import { ApiEndpointSelectPageComponent } from './features/api-endpoint-select-page/api-endpoint-select-page.component'; -import { ApiEndpointTypeSelectPageComponent } from './features/api-endpoint-type-select-page/api-endpoint-type-select-page.component'; -import { ApiEntityTypeSelectPageComponent } from './features/api-entity-type-select-page/api-entity-type-select-page.component'; -import { ApiEntityListPageComponent } from './features/api-entity-list-page/api-entity-list-page.component'; -import { SharedModule } from '../shared/shared.module'; -import { ApiEntityListComponent } from './components/api-entity-list/api-entity-list.component'; -import { ApiEntityTypeSelectorComponent } from './components/api-type-selector/api-entity-type-selector.component'; - -@NgModule({ - declarations: [ - ApiEndpointSelectPageComponent, - ApiEndpointTypeSelectPageComponent, - ApiEntityTypeSelectPageComponent, - ApiEntityListPageComponent, - ApiEntityListComponent, - ApiEntityTypeSelectorComponent - ], - imports: [ - CommonModule, - SharedModule, - ApiDrivenViewsRoutingModule - ] -}) -export class ApiDrivenViewsModule { } diff --git a/src/frontend/packages/core/src/api-driven-views/components/api-entity-list/api-entity-list.component.html b/src/frontend/packages/core/src/api-driven-views/components/api-entity-list/api-entity-list.component.html deleted file mode 100644 index c987a3c5a9..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/components/api-entity-list/api-entity-list.component.html +++ /dev/null @@ -1 +0,0 @@ -<p>api-entity-list works!</p> diff --git a/src/frontend/packages/core/src/api-driven-views/components/api-entity-list/api-entity-list.component.scss b/src/frontend/packages/core/src/api-driven-views/components/api-entity-list/api-entity-list.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/frontend/packages/core/src/api-driven-views/components/api-entity-list/api-entity-list.component.spec.ts b/src/frontend/packages/core/src/api-driven-views/components/api-entity-list/api-entity-list.component.spec.ts deleted file mode 100644 index f3913cbda0..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/components/api-entity-list/api-entity-list.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ApiEntityListComponent } from './api-entity-list.component'; - -describe('ApiEntityListComponent', () => { - let component: ApiEntityListComponent; - let fixture: ComponentFixture<ApiEntityListComponent>; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ApiEntityListComponent] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(ApiEntityListComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/frontend/packages/core/src/api-driven-views/components/api-entity-list/api-entity-list.component.ts b/src/frontend/packages/core/src/api-driven-views/components/api-entity-list/api-entity-list.component.ts deleted file mode 100644 index 45a254629d..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/components/api-entity-list/api-entity-list.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -@Component({ - selector: 'app-api-entity-list', - templateUrl: './api-entity-list.component.html', - styleUrls: ['./api-entity-list.component.scss'] -}) -export class ApiEntityListComponent implements OnInit { - - constructor() { } - - ngOnInit() { - } - -} diff --git a/src/frontend/packages/core/src/api-driven-views/components/api-type-selector/api-entity-type-selector.component.html b/src/frontend/packages/core/src/api-driven-views/components/api-type-selector/api-entity-type-selector.component.html deleted file mode 100644 index e18ecb279a..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/components/api-type-selector/api-entity-type-selector.component.html +++ /dev/null @@ -1,2 +0,0 @@ -<app-tile-selector *ngIf="entityTiles" [options]="entityTiles" (selection)="emitEntitySelection($event)"> -</app-tile-selector> \ No newline at end of file diff --git a/src/frontend/packages/core/src/api-driven-views/components/api-type-selector/api-entity-type-selector.component.scss b/src/frontend/packages/core/src/api-driven-views/components/api-type-selector/api-entity-type-selector.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/frontend/packages/core/src/api-driven-views/components/api-type-selector/api-entity-type-selector.component.spec.ts b/src/frontend/packages/core/src/api-driven-views/components/api-type-selector/api-entity-type-selector.component.spec.ts deleted file mode 100644 index b54398c933..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/components/api-type-selector/api-entity-type-selector.component.spec.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ApiEntityType } from '../../api-drive-views.types'; -import { SharedModule } from '../../../shared/shared.module'; -import { ApiEntityTypeSelectorComponent } from './api-entity-type-selector.component'; -import { ApiDrivenViewsModule } from '../../api-driven-views.module'; - -describe('ApiEntityTypeSelectorComponent', () => { - let component: ApiEntityTypeSelectorComponent; - let fixture: ComponentFixture<ApiEntityTypeSelectorComponent>; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [SharedModule, ApiDrivenViewsModule] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(ApiEntityTypeSelectorComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should render endpoint', () => { - const endpointTypes: ApiEntityType[] = [{ - title: 'Endpoint1', - imageUrl: 'image1', - type: 'endpoint1Type' - }, - { - title: 'Endpoint2', - imageUrl: 'image2', - type: 'endpoint2Type' - }]; - component.entityTypes = endpointTypes; - fixture.detectChanges(); - const element: HTMLElement = fixture.nativeElement; - const tileElements = Array.from(element.getElementsByClassName('tile-selector')); - expect(tileElements.length).toBe(2); - expect(tileElements.length).toBe(2); - tileElements.forEach((tile, index) => { - expect(tile.getElementsByClassName('tile-selector__content')[0].textContent) - .toBe(endpointTypes[index].title); - expect( - tile.getElementsByClassName('tile-selector__header')[0] - .getElementsByTagName('img')[0] - .attributes.getNamedItem('src') - .value - ) - .toBe(endpointTypes[index].imageUrl); - - }); - }); -}); diff --git a/src/frontend/packages/core/src/api-driven-views/components/api-type-selector/api-entity-type-selector.component.ts b/src/frontend/packages/core/src/api-driven-views/components/api-type-selector/api-entity-type-selector.component.ts deleted file mode 100644 index eb61fb957d..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/components/api-type-selector/api-entity-type-selector.component.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Component, OnInit, Input, Output } from '@angular/core'; -import { ApiEntityType } from '../../api-drive-views.types'; -import { ITileConfig, ITileImgConfig } from '../../../shared/components/tile/tile-selector.types'; -import { Subject } from 'rxjs'; - -@Component({ - selector: 'app-api-entity-type-selector', - templateUrl: './api-entity-type-selector.component.html', - styleUrls: ['./api-entity-type-selector.component.scss'] -}) -export class ApiEntityTypeSelectorComponent implements OnInit { - public entityTiles: ITileConfig[]; - @Input() set entityTypes(types: ApiEntityType[]) { - this.entityTiles = types.map(type => { - return new ITileConfig(type.title, { - location: type.imageUrl - }, { - type: type.type - } - ); - }); - } - @Output() public entitySelected = new Subject<ApiEntityType>(); - - public emitEntitySelection(tile: ITileConfig) { - if (tile) { - this.entitySelected.next( - new ApiEntityType(tile.data.type as string, tile.label as string, (tile.graphic as ITileImgConfig).location) - ); - } else { - this.entitySelected.next(null); - } - } - - constructor() { } - - ngOnInit() { - } - -} diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.html b/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.html deleted file mode 100644 index 52f7688c92..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.html +++ /dev/null @@ -1,6 +0,0 @@ -<app-page-header> - Select Endpoint -</app-page-header> -<app-api-entity-type-selector [entityTypes]="connectedEndpointsOfType$ | async" - (entitySelected)="endpointSelected($event)"> -</app-api-entity-type-selector> \ No newline at end of file diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.scss b/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.spec.ts b/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.spec.ts deleted file mode 100644 index e8e2769c61..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { TabNavService } from 'frontend/packages/core/tab-nav.service'; -import { generateBaseTestStoreModules } from 'frontend/packages/core/test-framework/core-test.helper'; - -import { CoreModule } from '../../../core/core.module'; -import { SharedModule } from '../../../shared/shared.module'; -import { ApiDrivenViewsModule } from '../../api-driven-views.module'; -import { ApiEndpointSelectPageComponent } from './api-endpoint-select-page.component'; - -describe('ApiEndpointSelectPageComponent', () => { - let component: ApiEndpointSelectPageComponent; - let fixture: ComponentFixture<ApiEndpointSelectPageComponent>; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - ...generateBaseTestStoreModules(), - CoreModule, - RouterTestingModule, - SharedModule, - ApiDrivenViewsModule, - ], - providers: [ - TabNavService, - ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(ApiEndpointSelectPageComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.ts b/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.ts deleted file mode 100644 index 01b3c5f54e..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-select-page/api-endpoint-select-page.component.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { GeneralAppState } from '../../../../../store/src/app-state'; -import { Observable } from 'rxjs'; -import { ApiEntityType } from '../../api-drive-views.types'; -import { Store } from '@ngrx/store'; -import { connectedEndpointsOfTypesSelector } from '../../../../../store/src/selectors/endpoint.selectors'; -import { ActivatedRoute, Router } from '@angular/router'; -import { map, filter } from 'rxjs/operators'; - -@Component({ - selector: 'app-api-endpoint-select-page', - templateUrl: './api-endpoint-select-page.component.html', - styleUrls: ['./api-endpoint-select-page.component.scss'] -}) -export class ApiEndpointSelectPageComponent implements OnInit { - public connectedEndpointsOfType$: Observable<ApiEntityType[]>; - constructor( - private store: Store<GeneralAppState>, - private route: ActivatedRoute, - private router: Router - ) { } - public endpointSelected(endpoint: ApiEntityType) { - this.router.navigate([endpoint.type], { relativeTo: this.route }); - } - ngOnInit() { - const endpointType = this.route.snapshot.params.endpointType; - - this.connectedEndpointsOfType$ = this.store.select(connectedEndpointsOfTypesSelector(endpointType)).pipe( - map(endpointsMap => Object.values(endpointsMap)), - filter(endpoints => !!endpoints || !endpoints.length), - map(endpoints => endpoints.map(endpoint => new ApiEntityType( - endpoint.guid, - endpoint.name - // TODO Get icon from entity catalog - ))) - ); - } - -} diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-type-select-page/api-endpoint-type-select-page.component.html b/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-type-select-page/api-endpoint-type-select-page.component.html deleted file mode 100644 index 63099672f9..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-type-select-page/api-endpoint-type-select-page.component.html +++ /dev/null @@ -1,6 +0,0 @@ -<app-page-header> - Select Endpoint Type -</app-page-header> -<app-api-entity-type-selector [entityTypes]="connectedEndpointTypes$ | async" - (entitySelected)="endpointSelected($event)"> -</app-api-entity-type-selector> \ No newline at end of file diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-type-select-page/api-endpoint-type-select-page.component.scss b/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-type-select-page/api-endpoint-type-select-page.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-type-select-page/api-endpoint-type-select-page.component.spec.ts b/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-type-select-page/api-endpoint-type-select-page.component.spec.ts deleted file mode 100644 index 960cbecc80..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-type-select-page/api-endpoint-type-select-page.component.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { TabNavService } from 'frontend/packages/core/tab-nav.service'; - -import { generateBaseTestStoreModules } from '../../../../test-framework/core-test.helper'; -import { CoreModule } from '../../../core/core.module'; -import { SharedModule } from '../../../shared/shared.module'; -import { ApiDrivenViewsModule } from '../../api-driven-views.module'; -import { ApiEndpointTypeSelectPageComponent } from './api-endpoint-type-select-page.component'; - -describe('ApiEndpointTypeSelectPageComponent', () => { - let component: ApiEndpointTypeSelectPageComponent; - let fixture: ComponentFixture<ApiEndpointTypeSelectPageComponent>; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - ...generateBaseTestStoreModules(), - CoreModule, - RouterTestingModule, - SharedModule, - ApiDrivenViewsModule, - ], - providers: [TabNavService] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(ApiEndpointTypeSelectPageComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-type-select-page/api-endpoint-type-select-page.component.ts b/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-type-select-page/api-endpoint-type-select-page.component.ts deleted file mode 100644 index c897ea9c69..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/features/api-endpoint-type-select-page/api-endpoint-type-select-page.component.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { Store } from '@ngrx/store'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; - -import { GeneralAppState } from '../../../../../store/src/app-state'; -import { endpointEntitiesSelector } from '../../../../../store/src/selectors/endpoint.selectors'; -import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; -import { ApiEntityType } from '../../api-drive-views.types'; - -@Component({ - selector: 'app-api-endpoint-type-select-page', - templateUrl: './api-endpoint-type-select-page.component.html', - styleUrls: ['./api-endpoint-type-select-page.component.scss'] -}) -export class ApiEndpointTypeSelectPageComponent implements OnInit { - public connectedEndpointTypes$: Observable<ApiEntityType[]>; - constructor( - public store: Store<GeneralAppState>, - public router: Router, - public activeRoute: ActivatedRoute - ) { } - public endpointSelected(endpoint: ApiEntityType) { - this.router.navigate([endpoint.type], { relativeTo: this.activeRoute }); - } - ngOnInit() { - const endpointTypes = entityCatalog.getAllEndpointTypes(); - const endpointTypesWithEntities = endpointTypes - .filter(endpointType => entityCatalog.getAllEntitiesForEndpointType(endpointType.type).length > 0); - this.connectedEndpointTypes$ = this.store.select(endpointEntitiesSelector).pipe( - map(endpoints => { - const endpointTypeSet = new Set<string>(); - Object.values(endpoints).forEach(endpoint => { - if (endpoint.connectionStatus === 'connected') { - endpointTypeSet.add(endpoint.cnsi_type); - } - }); - return Array.from(endpointTypeSet) - .map(type => endpointTypesWithEntities.find(endpoint => endpoint.type === type)) - .filter(endpoint => !!endpoint) - .map(endpoint => new ApiEntityType(endpoint.type, endpoint.definition.label, endpoint.definition.logoUrl)); - }) - ); - } - -} diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.html b/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.html deleted file mode 100644 index e3a7a3b7f3..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.html +++ /dev/null @@ -1 +0,0 @@ -<app-simple-list *ngIf="catalogEntity" [catalogEntity]="catalogEntity"></app-simple-list> diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.scss b/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.spec.ts b/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.spec.ts deleted file mode 100644 index b06b48139e..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; - -import { generateBaseTestStoreModules } from '../../../../test-framework/core-test.helper'; -import { CoreModule } from '../../../core/core.module'; -import { SharedModule } from '../../../shared/shared.module'; -import { ApiDrivenViewsModule } from '../../api-driven-views.module'; -import { ApiEntityListPageComponent } from './api-entity-list-page.component'; - -describe('ApiEntityListPageComponent', () => { - let component: ApiEntityListPageComponent; - let fixture: ComponentFixture<ApiEntityListPageComponent>; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - ...generateBaseTestStoreModules(), - CoreModule, - RouterTestingModule, - SharedModule, - ApiDrivenViewsModule, - ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(ApiEntityListPageComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.ts b/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.ts deleted file mode 100644 index eff169381f..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/features/api-entity-list-page/api-entity-list-page.component.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; - -import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; -import { - StratosBaseCatalogEntity, -} from '../../../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; - -@Component({ - selector: 'app-api-entity-list-page', - templateUrl: './api-entity-list-page.component.html', - styleUrls: ['./api-entity-list-page.component.scss'] -}) -export class ApiEntityListPageComponent implements OnInit { - public catalogEntity: StratosBaseCatalogEntity; - constructor( - public route: ActivatedRoute - ) { } - - ngOnInit() { - const endpointType = this.route.parent ? this.route.parent.snapshot.params.endpointType : null; - const entityType = this.route.snapshot.params.entityType; - this.catalogEntity = entityCatalog.getEntity(endpointType, entityType); - } - -} diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.html b/src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.html deleted file mode 100644 index cb561c92a3..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.html +++ /dev/null @@ -1,4 +0,0 @@ -<app-page-header [tabs]="tabs"> - {{ connectedEndpointsOfType$ | async }} -</app-page-header> -<router-outlet></router-outlet> \ No newline at end of file diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.scss b/src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.spec.ts b/src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.spec.ts deleted file mode 100644 index 12963030bf..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { TabNavService } from 'frontend/packages/core/tab-nav.service'; - -import { generateBaseTestStoreModules } from '../../../../test-framework/core-test.helper'; -import { CoreModule } from '../../../core/core.module'; -import { SharedModule } from '../../../shared/shared.module'; -import { ApiDrivenViewsModule } from '../../api-driven-views.module'; -import { ApiEntityTypeSelectPageComponent } from './api-entity-type-select-page.component'; - -describe('ApiEntityTypeSelectPageComponent', () => { - let component: ApiEntityTypeSelectPageComponent; - let fixture: ComponentFixture<ApiEntityTypeSelectPageComponent>; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - ...generateBaseTestStoreModules(), - CoreModule, - RouterTestingModule, - SharedModule, - ApiDrivenViewsModule, - ], - providers: [TabNavService] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(ApiEntityTypeSelectPageComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.ts b/src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.ts deleted file mode 100644 index 69ce1bc3f9..0000000000 --- a/src/frontend/packages/core/src/api-driven-views/features/api-entity-type-select-page/api-entity-type-select-page.component.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { Store } from '@ngrx/store'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; - -import { GeneralAppState } from '../../../../../store/src/app-state'; -import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; -import { connectedEndpointsOfTypesSelector } from '../../../../../store/src/selectors/endpoint.selectors'; -import { TabNavService } from '../../../../tab-nav.service'; -import { IPageSideNavTab } from '../../../features/dashboard/page-side-nav/page-side-nav.component'; - -@Component({ - selector: 'app-api-entity-type-select-page', - templateUrl: './api-entity-type-select-page.component.html', - styleUrls: ['./api-entity-type-select-page.component.scss'] -}) -export class ApiEntityTypeSelectPageComponent implements OnInit { - public connectedEndpointsOfType$: Observable<string>; - public tabs: IPageSideNavTab[]; - constructor( - private route: ActivatedRoute, - private router: Router, - private tabNavService: TabNavService, - private store: Store<GeneralAppState> - ) { } - - ngOnInit() { - const endpointGuid = this.route.snapshot.params.endpointId; - const endpointType = this.route.snapshot.params.endpointType; - const endpointEntity = entityCatalog.getEndpoint(endpointType); - const endpointEntities = entityCatalog.getAllEntitiesForEndpointType(endpointType); - const entitiesWithGetMultiple = endpointEntities.filter( - entity => entity && entity.definition.tableConfig && entity.actionOrchestrator.hasActionBuilder('getMultiple') - ); - this.connectedEndpointsOfType$ = this.store.select(connectedEndpointsOfTypesSelector(endpointType)).pipe( - map(endpoints => endpoints[endpointGuid] ? endpoints[endpointGuid].name : 'Entities') - ); - if (endpointEntity) { - this.tabNavService.setHeader(endpointEntity.definition.label); - } - this.tabs = entitiesWithGetMultiple.map(entity => { - return { - link: entity.type, - label: entity.definition.labelPlural, - icon: entity.definition.icon, - iconFont: entity.definition.iconFont - }; - }); - } - -} diff --git a/src/frontend/packages/core/src/app.module.ts b/src/frontend/packages/core/src/app.module.ts index 64f5ca665b..0ed0d1b159 100644 --- a/src/frontend/packages/core/src/app.module.ts +++ b/src/frontend/packages/core/src/app.module.ts @@ -24,8 +24,6 @@ import { generateStratosEntities } from '../../store/src/stratos-entity-generato import { EndpointModel } from '../../store/src/types/endpoint.types'; import { IFavoriteMetadata, UserFavorite } from '../../store/src/types/user-favorites.types'; import { UserFavoriteManager } from '../../store/src/user-favorite-manager'; -import { TabNavService } from '../tab-nav.service'; -import { XSRFModule } from '../xsrf.module'; import { AppComponent } from './app.component'; import { RouteModule } from './app.routing'; import { CoreModule } from './core/core.module'; @@ -47,6 +45,8 @@ import { CustomReuseStrategy } from './route-reuse-stragegy'; import { endpointEventKey, GlobalEventData, GlobalEventService } from './shared/global-events.service'; import { SidePanelService } from './shared/services/side-panel.service'; import { SharedModule } from './shared/shared.module'; +import { TabNavService } from './tab-nav.service'; +import { XSRFModule } from './xsrf.module'; // Create action for router navigation. See // - https://github.com/ngrx/platform/issues/68 diff --git a/src/frontend/packages/core/src/app.routing.ts b/src/frontend/packages/core/src/app.routing.ts index 2020929d92..a54b1c30d5 100644 --- a/src/frontend/packages/core/src/app.routing.ts +++ b/src/frontend/packages/core/src/app.routing.ts @@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { APIKeyAuthGuardService } from './core/apiKey-auth-guard.service'; import { AuthGuardService } from './core/auth-guard.service'; import { CoreModule } from './core/core.module'; import { EndpointsService } from './core/endpoints.service'; @@ -72,7 +73,6 @@ const appRoutes: Routes = [ } } }, - { path: 'entity-list', loadChildren: () => import('./api-driven-views/api-driven-views.module').then(m => m.ApiDrivenViewsModule) }, { path: 'endpoints', data: { @@ -95,6 +95,11 @@ const appRoutes: Routes = [ }, { path: 'about', loadChildren: () => import('./features/about/about.module').then(m => m.AboutModule) }, { path: 'user-profile', loadChildren: () => import('./features/user-profile/user-profile.module').then(m => m.UserProfileModule) }, + { + path: 'api-keys', + loadChildren: () => import('./features/api-keys/api-keys.module').then(m => m.ApiKeysModule), + canActivate: [APIKeyAuthGuardService] + }, { path: 'events', loadChildren: () => import('./features/event-page/event-page.module').then(m => m.EventPageModule) }, { path: 'errors/:endpointId', diff --git a/src/frontend/packages/core/src/core/apiKey-auth-guard.service.ts b/src/frontend/packages/core/src/core/apiKey-auth-guard.service.ts new file mode 100644 index 0000000000..70e965da23 --- /dev/null +++ b/src/frontend/packages/core/src/core/apiKey-auth-guard.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { CanActivate } from '@angular/router'; +import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { RouterNav } from '../../../store/src/actions/router.actions'; +import { AppState } from '../../../store/src/app-state'; +import { CurrentUserPermissionsService } from './permissions/current-user-permissions.service'; +import { StratosCurrentUserPermissions } from './permissions/stratos-user-permissions.checker'; + +@Injectable() +export class APIKeyAuthGuardService implements CanActivate { + + constructor( + private store: Store<AppState>, + private cups: CurrentUserPermissionsService, + ) { } + + canActivate(): Observable<boolean> { + return this.cups.can(StratosCurrentUserPermissions.API_KEYS).pipe( + map(can => { + if (!can) { + this.store.dispatch(new RouterNav({ path: ['/'] })); + } + return can; + }) + ); + } +} \ No newline at end of file diff --git a/src/frontend/packages/core/src/core/core.module.ts b/src/frontend/packages/core/src/core/core.module.ts index ff8b4eb280..792f489d5c 100644 --- a/src/frontend/packages/core/src/core/core.module.ts +++ b/src/frontend/packages/core/src/core/core.module.ts @@ -12,6 +12,7 @@ import { PaginationMonitorFactory } from '../../../store/src/monitors/pagination import { NoContentMessageComponent } from '../shared/components/no-content-message/no-content-message.component'; import { RecentEntitiesComponent } from '../shared/components/recent-entities/recent-entities.component'; import { UserAvatarComponent } from './../shared/components/user-avatar/user-avatar.component'; +import { APIKeyAuthGuardService } from './apiKey-auth-guard.service'; import { AuthGuardService } from './auth-guard.service'; import { ButtonBlurOnClickDirective } from './button-blur-on-click.directive'; import { BytesToHumanSize, MegaBytesToHumanSize } from './byte-formatters.pipe'; @@ -24,7 +25,6 @@ import { EntityFavoriteStarComponent } from './entity-favorite-star/entity-favor import { EventWatcherService } from './event-watcher/event-watcher.service'; import { InfinityPipe } from './infinity.pipe'; import { LogOutDialogComponent } from './log-out-dialog/log-out-dialog.component'; -import { LoggerService } from './logger.service'; import { MDAppModule } from './md.module'; import { NotSetupGuardService } from './not-setup-guard.service'; import { PageHeaderService } from './page-header-service/page-header.service'; @@ -70,12 +70,12 @@ import { WindowRef } from './window-ref/window-ref.service'; ], providers: [ AuthGuardService, + APIKeyAuthGuardService, NotSetupGuardService, PageHeaderService, EventWatcherService, WindowRef, UtilsService, - LoggerService, EndpointsService, UserService, EntityServiceFactory, diff --git a/src/frontend/packages/core/endpoints-health-checks.ts b/src/frontend/packages/core/src/core/endpoints-health-checks.ts similarity index 74% rename from src/frontend/packages/core/endpoints-health-checks.ts rename to src/frontend/packages/core/src/core/endpoints-health-checks.ts index 11e46ae3fc..37a5c3164b 100644 --- a/src/frontend/packages/core/endpoints-health-checks.ts +++ b/src/frontend/packages/core/src/core/endpoints-health-checks.ts @@ -1,8 +1,8 @@ import { Injectable } from '@angular/core'; -import { entityCatalog } from '../store/src/entity-catalog/entity-catalog'; -import { EndpointHealthCheck } from '../store/src/entity-catalog/entity-catalog.types'; -import { EndpointModel } from '../store/src/types/endpoint.types'; +import { entityCatalog } from '../../../store/src/entity-catalog/entity-catalog'; +import { EndpointHealthCheck } from '../../../store/src/entity-catalog/entity-catalog.types'; +import { EndpointModel } from '../../../store/src/types/endpoint.types'; @Injectable({ providedIn: 'root' diff --git a/src/frontend/packages/core/src/core/endpoints.service.ts b/src/frontend/packages/core/src/core/endpoints.service.ts index 6354590f8a..8da0f1d610 100644 --- a/src/frontend/packages/core/src/core/endpoints.service.ts +++ b/src/frontend/packages/core/src/core/endpoints.service.ts @@ -11,8 +11,8 @@ import { EndpointHealthCheck } from '../../../store/src/entity-catalog/entity-ca import { AuthState } from '../../../store/src/reducers/auth.reducer'; import { endpointEntitiesSelector, endpointStatusSelector } from '../../../store/src/selectors/endpoint.selectors'; import { EndpointModel, EndpointState } from '../../../store/src/types/endpoint.types'; -import { EndpointHealthChecks } from '../../endpoints-health-checks'; import { endpointHasMetricsByAvailable } from '../features/endpoints/endpoint-helpers'; +import { EndpointHealthChecks } from './endpoints-health-checks'; import { UserService } from './user.service'; diff --git a/src/frontend/packages/core/src/core/extension/extension-service.ts b/src/frontend/packages/core/src/core/extension/extension-service.ts index c2d198c7d6..495dd76d8e 100644 --- a/src/frontend/packages/core/src/core/extension/extension-service.ts +++ b/src/frontend/packages/core/src/core/extension/extension-service.ts @@ -66,9 +66,9 @@ export interface StratosExtensionRoutes { // Stores the extension metadata as defined by the decorators const extensionMetadata = { loginComponent: null, - extensionRoutes: {} as { [key: string]: StratosExtensionRoutes[] }, - tabs: {} as { [key: string]: IPageSideNavTab[] }, - actions: {} as { [key: string]: StratosActionMetadata[] }, + extensionRoutes: {} as { [key: string]: StratosExtensionRoutes[], }, + tabs: {} as { [key: string]: IPageSideNavTab[], }, + actions: {} as { [key: string]: StratosActionMetadata[], }, }; /** @@ -118,6 +118,10 @@ function addExtensionAction(action: StratosActionType, target: any, props: Strat extensionMetadata.actions[action].push(props); } +// Empty module used to support the registration of Extension Components +@NgModule() +export class ExtEmptyModule { } + // Injectable Extension Service @Injectable({ providedIn: 'root', @@ -201,7 +205,3 @@ export function getTabsFromExtensions(tabType: StratosTabType): IPageSideNavTab[ export function getActionsFromExtensions(actionType: StratosActionType): StratosActionMetadata[] { return extensionMetadata.actions[actionType] || []; } - -// Empty module used to support the registration of Extension Components -@NgModule() -export class ExtEmptyModule { } diff --git a/src/frontend/packages/core/src/core/logger.service.spec.ts b/src/frontend/packages/core/src/core/logger.service.spec.ts deleted file mode 100644 index 1720fe0b84..0000000000 --- a/src/frontend/packages/core/src/core/logger.service.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { inject, TestBed } from '@angular/core/testing'; -import { createBasicStoreModule } from '@stratosui/store/testing'; - -import { CoreTestingModule } from '../../test-framework/core-test.modules'; -import { LoggerService } from './logger.service'; - -describe('LoggerService', () => { - - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - LoggerService - ], - imports: [ - CoreTestingModule, - createBasicStoreModule(), - ] - }); - }); - - it('should be created', inject([LoggerService], (service: LoggerService) => { - expect(service).toBeTruthy(); - })); -}); diff --git a/src/frontend/packages/core/src/core/logger.service.ts b/src/frontend/packages/core/src/core/logger.service.ts deleted file mode 100644 index 2a98c6cbf4..0000000000 --- a/src/frontend/packages/core/src/core/logger.service.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Store } from '@ngrx/store'; - -import { - LoggerDebugAction, - LoggerErrorAction, - LoggerInfoAction, - LoggerWarnAction, - LogLevel, -} from '../../../store/src/actions/log.actions'; -import { GeneralEntityAppState } from '../../../store/src/app-state'; -import { environment } from '../environments/environment.prod'; - -export enum LogLevelStringToNumber { - DEBUG = 0, - INFO = 1, - WARN = 2, - ERROR = 3 -} - -@Injectable() -export class LoggerService { - - constructor(private store: Store<GeneralEntityAppState>) { } - - private log(level: LogLevel, message: string, arg: any) { - if (LogLevelStringToNumber[level] < LogLevelStringToNumber[environment.logLevel] || !environment.logToConsole) { - return; - } - const date = new Date(); - message = `${date.toUTCString()}- ${message}`; - - let func = console.log; - switch (level) { - case LogLevel.ERROR: - func = console.error; - this.store.dispatch(new LoggerErrorAction(message)); - break; - case LogLevel.WARN: - func = console.warn; - this.store.dispatch(new LoggerWarnAction(message)); - break; - case LogLevel.INFO: - // tslint:disable-next-line:no-console - func = console.info; - this.store.dispatch(new LoggerInfoAction(message)); - break; - case LogLevel.DEBUG: - func = console.log; - this.store.dispatch(new LoggerDebugAction(message)); - break; - } - - if (arg) { - func(message, arg); - } else { - func(message); - } - } - - debug(message: string, arg?: any) { - this.log(LogLevel.DEBUG, message, arg); - } - - info(message: string, arg?: any) { - this.log(LogLevel.INFO, message, arg); - } - - warn(message: string, arg?: any) { - this.log(LogLevel.WARN, message, arg); - } - - error(message: string, arg?: any) { - this.log(LogLevel.ERROR, message, arg); - } - -} diff --git a/src/frontend/packages/core/src/core/permissions/current-user-permissions.service.ts b/src/frontend/packages/core/src/core/permissions/current-user-permissions.service.ts index 4ad096b7c5..c7cf116b80 100644 --- a/src/frontend/packages/core/src/core/permissions/current-user-permissions.service.ts +++ b/src/frontend/packages/core/src/core/permissions/current-user-permissions.service.ts @@ -5,7 +5,6 @@ import { distinctUntilChanged, map, switchMap } from 'rxjs/operators'; import { InternalAppState } from '../../../../store/src/app-state'; import { stratosEntityCatalog } from '../../../../store/src/stratos-entity-catalog'; -import { LoggerService } from '../logger.service'; import { CurrentUserPermissions, PermissionConfig, @@ -28,7 +27,6 @@ export class CurrentUserPermissionsService { constructor( private store: Store<InternalAppState>, @Optional() @Inject(CUSTOM_USER_PERMISSION_CHECKERS) customCheckers: ICurrentUserPermissionsChecker[], - private logger: LoggerService ) { // Cannot set default value for parameter as the Optional decorator sets it to null const nullSafeCustomCheckers = customCheckers || []; @@ -136,7 +134,9 @@ export class CurrentUserPermissionsService { private combineChecks( checkCombiners: IPermissionCheckCombiner[], ) { - const reducedChecks = checkCombiners.map(combiner => BaseCurrentUserPermissionsChecker.reduceChecks(combiner.checks, combiner.combineType)); + const reducedChecks = checkCombiners.map(combiner => + BaseCurrentUserPermissionsChecker.reduceChecks(combiner.checks, combiner.combineType) + ); return combineLatest(reducedChecks).pipe( map(checks => checks.every(check => check)) ); @@ -172,21 +172,21 @@ export class CurrentUserPermissionsService { failureValue: T ): T { const res: T[] = []; - for (let i = 0; i < this.allCheckers.length; i++) { - const checkerRes = checkFn(this.allCheckers[i]); + for (const checker of this.allCheckers) { + const checkerRes = checkFn(checker); if (checkerRes) { res.push(checkerRes); } } - if (res.length == 0) { - this.logger.warn(`Permissions: Failed to find a '${checkNoun}' for '${checkType}'. Permission Denied.`); + if (res.length === 0) { + console.warn(`Permissions: Failed to find a '${checkNoun}' for '${checkType}'. Permission Denied.`); return failureValue; } if (res.length === 1) { return res[0]; } if (res.length > 1) { - this.logger.warn(`Permissions: Found too many '${checkNoun}' for '${checkType}'. Permission Denied.`); + console.warn(`Permissions: Found too many '${checkNoun}' for '${checkType}'. Permission Denied.`); return failureValue; } } diff --git a/src/frontend/packages/core/src/core/permissions/stratos-user-permissions.checker.ts b/src/frontend/packages/core/src/core/permissions/stratos-user-permissions.checker.ts index db4c382b26..181f62a1a0 100644 --- a/src/frontend/packages/core/src/core/permissions/stratos-user-permissions.checker.ts +++ b/src/frontend/packages/core/src/core/permissions/stratos-user-permissions.checker.ts @@ -1,12 +1,15 @@ import { Store } from '@ngrx/store'; -import { Observable } from 'rxjs'; +import { Observable, of } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; import { GeneralEntityAppState } from '../../../../store/src/app-state'; +import { selectSessionData } from '../../../../store/src/reducers/auth.reducer'; import { getCurrentUserStratosHasScope, getCurrentUserStratosRole, PermissionValues, } from '../../../../store/src/selectors/current-user-role.selectors'; +import { APIKeysEnabled } from '../../../../store/src/types/auth.types'; import { IPermissionConfigs, PermissionConfig, PermissionTypes } from './current-user-permissions.config'; import { BaseCurrentUserPermissionsChecker, @@ -19,6 +22,10 @@ import { export enum StratosCurrentUserPermissions { ENDPOINT_REGISTER = 'register.endpoint', PASSWORD_CHANGE = 'change-password', + /** + * Does the user have permission to view/create/delete their own API Keys? + */ + API_KEYS = 'api-keys' } export enum StratosPermissionStrings { @@ -34,20 +41,28 @@ export enum StratosScopeStrings { export enum StratosPermissionTypes { STRATOS = 'internal', - STRATOS_SCOPE = 'internal-scope' + STRATOS_SCOPE = 'internal-scope', + API_KEY = 'api-key' } // For each set permissions are checked by permission types of ENDPOINT, ENDPOINT_SCOPE, STRATOS_SCOPE, FEATURE_FLAG or a random bag. // Every group result must be true in order for the permission to be true. A group result is true if all or some of it's permissions are // true (see `getCheckFromConfig`). export const stratosPermissionConfigs: IPermissionConfigs = { - [StratosCurrentUserPermissions.ENDPOINT_REGISTER]: new PermissionConfig(StratosPermissionTypes.STRATOS, StratosPermissionStrings.STRATOS_ADMIN), - [StratosCurrentUserPermissions.PASSWORD_CHANGE]: new PermissionConfig(StratosPermissionTypes.STRATOS_SCOPE, StratosScopeStrings.STRATOS_CHANGE_PASSWORD), + [StratosCurrentUserPermissions.ENDPOINT_REGISTER]: new PermissionConfig( + StratosPermissionTypes.STRATOS, + StratosPermissionStrings.STRATOS_ADMIN + ), + [StratosCurrentUserPermissions.PASSWORD_CHANGE]: new PermissionConfig( + StratosPermissionTypes.STRATOS_SCOPE, + StratosScopeStrings.STRATOS_CHANGE_PASSWORD + ), + [StratosCurrentUserPermissions.API_KEYS]: new PermissionConfig(StratosPermissionTypes.API_KEY, '') }; export class StratosUserPermissionsChecker extends BaseCurrentUserPermissionsChecker implements ICurrentUserPermissionsChecker { constructor(private store: Store<GeneralEntityAppState>, ) { - super() + super(); } getPermissionConfig(action: string) { @@ -75,6 +90,8 @@ export class StratosUserPermissionsChecker extends BaseCurrentUserPermissionsChe return this.getInternalCheck(permissionConfig.permission as StratosPermissionStrings); case (StratosPermissionTypes.STRATOS_SCOPE): return this.getInternalScopesCheck(permissionConfig.permission as StratosScopeStrings); + case (StratosPermissionTypes.API_KEY): + return this.apiKeyCheck(); } } @@ -95,6 +112,20 @@ export class StratosUserPermissionsChecker extends BaseCurrentUserPermissionsChe return this.check(StratosPermissionTypes.STRATOS_SCOPE, permission); } + private apiKeyCheck(): Observable<boolean> { + return this.store.select(selectSessionData()).pipe( + switchMap(sessionData => { + switch (sessionData.config.APIKeysEnabled) { + case APIKeysEnabled.ADMIN_ONLY: + return this.store.select(getCurrentUserStratosRole(StratosPermissionStrings.STRATOS_ADMIN)); + case APIKeysEnabled.ALL_USERS: + return of(true); + } + return of(false); + }) + ); + } + public getComplexCheck( permissionConfig: PermissionConfig[], ...args: any[] @@ -108,7 +139,7 @@ export class StratosUserPermissionsChecker extends BaseCurrentUserPermissionsChe checks: this.getInternalScopesChecks(configGroup), }; } - }) + }); // Checker must handle all configs return res.every(check => !!check) ? res : null; } diff --git a/src/frontend/packages/core/src/core/utils.service.ts b/src/frontend/packages/core/src/core/utils.service.ts index d3d1c3f837..cd40db21ed 100644 --- a/src/frontend/packages/core/src/core/utils.service.ts +++ b/src/frontend/packages/core/src/core/utils.service.ts @@ -293,10 +293,13 @@ export const arraysEqual = (a: any[], b: any[]): boolean => { return false; }; + +/* tslint:disable:no-bitwise */ export const createGuid = (): string => { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { - var r = Math.random() * 16 | 0, - v = c == 'x' ? r : (r & 0x3 | 0x8); + const r = Math.random() * 16 | 0; + const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); -} +}; +/* tslint:enable */ diff --git a/src/frontend/packages/core/src/environments/environment.prod.ts b/src/frontend/packages/core/src/environments/environment.prod.ts index da8e7d8dbd..61c616a7fe 100644 --- a/src/frontend/packages/core/src/environments/environment.prod.ts +++ b/src/frontend/packages/core/src/environments/environment.prod.ts @@ -1,13 +1,9 @@ import { cfAPIVersion, proxyAPIVersion } from '../../../store/src/jetstream'; -import { LogLevel } from './../../../store/src/actions/log.actions'; export const environment = { production: true, - logLevel: LogLevel.WARN, proxyAPIVersion, cfAPIVersion, - logToConsole: true, - logEnableConsoleActions: false, showObsDebug: false, disablePolling: false }; diff --git a/src/frontend/packages/core/src/environments/environment.ts b/src/frontend/packages/core/src/environments/environment.ts index e10f7527a5..ee77d9adb8 100644 --- a/src/frontend/packages/core/src/environments/environment.ts +++ b/src/frontend/packages/core/src/environments/environment.ts @@ -1,4 +1,3 @@ -import { LogLevel } from '../../../store/src/actions/log.actions'; import { cfAPIVersion, proxyAPIVersion } from '../../../store/src/jetstream'; // The file contents for the current environment will overwrite these during build. @@ -10,9 +9,6 @@ export const environment = { production: false, proxyAPIVersion, cfAPIVersion, - logLevel: LogLevel.DEBUG, - logToConsole: true, - logEnableConsoleActions: false, showObsDebug: false, disablePolling: false }; diff --git a/src/frontend/packages/core/src/features/about/about-page/about-page.component.spec.ts b/src/frontend/packages/core/src/features/about/about-page/about-page.component.spec.ts index 3aea3c6790..69f0863602 100644 --- a/src/frontend/packages/core/src/features/about/about-page/about-page.component.spec.ts +++ b/src/frontend/packages/core/src/features/about/about-page/about-page.component.spec.ts @@ -2,10 +2,11 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { createBasicStoreModule } from '@stratosui/store/testing'; -import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; import { CoreModule } from '../../../core/core.module'; +import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; import { SharedModule } from '../../../shared/shared.module'; +import { TabNavService } from '../../../tab-nav.service'; import { AboutPageComponent } from './about-page.component'; describe('AboutPageComponent', () => { @@ -22,7 +23,10 @@ describe('AboutPageComponent', () => { CoreTestingModule, createBasicStoreModule(), ], - providers: [TabNavService] + providers: [ + TabNavService, + CurrentUserPermissionsService + ] }) .compileComponents(); })); diff --git a/src/frontend/packages/core/src/features/about/diagnostics-page/diagnostics-page.component.spec.ts b/src/frontend/packages/core/src/features/about/diagnostics-page/diagnostics-page.component.spec.ts index c65bac7fc7..16786352e6 100644 --- a/src/frontend/packages/core/src/features/about/diagnostics-page/diagnostics-page.component.spec.ts +++ b/src/frontend/packages/core/src/features/about/diagnostics-page/diagnostics-page.component.spec.ts @@ -2,10 +2,11 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { createBasicStoreModule } from '@stratosui/store/testing'; -import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; import { CoreModule } from '../../../core/core.module'; +import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; import { SharedModule } from '../../../shared/shared.module'; +import { TabNavService } from '../../../tab-nav.service'; import { DiagnosticsPageComponent } from './diagnostics-page.component'; describe('DiagnosticsPageComponent', () => { @@ -22,7 +23,10 @@ describe('DiagnosticsPageComponent', () => { CoreTestingModule, createBasicStoreModule(), ], - providers: [TabNavService] + providers: [ + TabNavService, + CurrentUserPermissionsService + ] }) .compileComponents(); })); diff --git a/src/frontend/packages/core/src/features/about/eula-page/eula-page.component.spec.ts b/src/frontend/packages/core/src/features/about/eula-page/eula-page.component.spec.ts index 113aac37d3..cd45da187e 100644 --- a/src/frontend/packages/core/src/features/about/eula-page/eula-page.component.spec.ts +++ b/src/frontend/packages/core/src/features/about/eula-page/eula-page.component.spec.ts @@ -4,9 +4,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { createBasicStoreModule } from '@stratosui/store/testing'; -import { TabNavService } from '../../../../tab-nav.service'; import { CoreModule } from '../../../core/core.module'; +import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; import { SharedModule } from '../../../shared/shared.module'; +import { TabNavService } from '../../../tab-nav.service'; import { EulaPageComponent } from './eula-page.component'; describe('EulaPageComponent', () => { @@ -24,7 +25,10 @@ describe('EulaPageComponent', () => { HttpClientTestingModule, createBasicStoreModule(), ], - providers: [TabNavService] + providers: [ + TabNavService, + CurrentUserPermissionsService + ] }) .compileComponents(); })); diff --git a/src/frontend/packages/core/src/features/api-keys/add-api-key-dialog/add-api-key-dialog.component.html b/src/frontend/packages/core/src/features/api-keys/add-api-key-dialog/add-api-key-dialog.component.html new file mode 100644 index 0000000000..bf044a85bf --- /dev/null +++ b/src/frontend/packages/core/src/features/api-keys/add-api-key-dialog/add-api-key-dialog.component.html @@ -0,0 +1,28 @@ +<div class="key-dialog__loading-wrapper"> + <mat-progress-bar class="key-dialog__loading" + [color]="(hasErrored$ | async) && !(isBusy$ | async) ? 'warn' : 'primary'" + [mode]="(isBusy$ | async) ? 'indeterminate' : 'solid'"> + </mat-progress-bar> +</div> +<div class="key-dialog"> + <div class="key-dialog__title"> + <h2 mat-dialog-title> + Create an API Key + </h2> + </div> + <div> + </div> + <div [formGroup]="formGroup"> + <mat-form-field> + <input matInput placeholder="Description" formControlName="comment" required> + </mat-form-field> + </div> + + <app-dialog-error message="{{hasErrored$ | async}}" [show]="(hasErrored$ | async) && !(isBusy$ | async)"> + </app-dialog-error> + <mat-dialog-actions class="key-dialog__actions"> + <button [mat-dialog-close]="true" mat-button color="warn" [disabled]="(isBusy$ | async)">Cancel</button> + <button (click)="submit()" [disabled]="(isBusy$ | async) || !formGroup.valid" mat-button + color="primary">Create</button> + </mat-dialog-actions> +</div> \ No newline at end of file diff --git a/src/frontend/packages/core/src/features/api-keys/add-api-key-dialog/add-api-key-dialog.component.scss b/src/frontend/packages/core/src/features/api-keys/add-api-key-dialog/add-api-key-dialog.component.scss new file mode 100644 index 0000000000..a235091e2d --- /dev/null +++ b/src/frontend/packages/core/src/features/api-keys/add-api-key-dialog/add-api-key-dialog.component.scss @@ -0,0 +1,28 @@ +.key-dialog { + &__loading { + left: 0; + position: absolute; + right: 0; + top: 0; + &-wrapper { + position: relative; + margin: 0 -24px; + transform: translateY(-24px); + } + } + + &__title { + display: flex; + h2 { + flex: 1; + } + } + + &__actions { + justify-content: flex-end; + } + + mat-form-field { + width: 100%; + } +} diff --git a/src/frontend/packages/core/src/features/api-keys/add-api-key-dialog/add-api-key-dialog.component.spec.ts b/src/frontend/packages/core/src/features/api-keys/add-api-key-dialog/add-api-key-dialog.component.spec.ts new file mode 100644 index 0000000000..e27047ec08 --- /dev/null +++ b/src/frontend/packages/core/src/features/api-keys/add-api-key-dialog/add-api-key-dialog.component.spec.ts @@ -0,0 +1,40 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialogRef } from '@angular/material/dialog'; + +import { BaseTestModules } from '../../../../test-framework/core-test.helper'; +import { AddApiKeyDialogComponent } from './add-api-key-dialog.component'; + +describe('AddApiKeyDialogComponent', () => { + let component: AddApiKeyDialogComponent; + let fixture: ComponentFixture<AddApiKeyDialogComponent>; + + const mockDialogRef = { + close: () => { } + }; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + ...BaseTestModules, + ], + declarations: [AddApiKeyDialogComponent], + providers: [ + { + provide: MatDialogRef, + useValue: mockDialogRef + } + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AddApiKeyDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/core/src/features/api-keys/add-api-key-dialog/add-api-key-dialog.component.ts b/src/frontend/packages/core/src/features/api-keys/add-api-key-dialog/add-api-key-dialog.component.ts new file mode 100644 index 0000000000..eea0b54086 --- /dev/null +++ b/src/frontend/packages/core/src/features/api-keys/add-api-key-dialog/add-api-key-dialog.component.ts @@ -0,0 +1,66 @@ +import { Component, OnDestroy } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { MatDialogRef } from '@angular/material/dialog'; +import { BehaviorSubject, Subscription } from 'rxjs'; +import { filter, first, map, pairwise, tap } from 'rxjs/operators'; + +import { ApiKey } from '../../../../../store/src/apiKey.types'; +import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; +import { RequestInfoState } from '../../../../../store/src/reducers/api-request-reducer/types'; +import { stratosEntityCatalog } from '../../../../../store/src/stratos-entity-catalog'; +import { NormalizedResponse } from '../../../../../store/src/types/api.types'; +import { safeUnsubscribe } from '../../../core/utils.service'; + +@Component({ + selector: 'app-add-api-key-dialog', + templateUrl: './add-api-key-dialog.component.html', + styleUrls: ['./add-api-key-dialog.component.scss'] +}) +export class AddApiKeyDialogComponent implements OnDestroy { + + private hasErrored = new BehaviorSubject(null); + public hasErrored$ = this.hasErrored.asObservable(); + private isBusy = new BehaviorSubject(false) + public isBusy$ = this.isBusy.asObservable(); + + private sub: Subscription; + + public formGroup: FormGroup; + + constructor( + private fb: FormBuilder, + public dialogRef: MatDialogRef<ApiKey>, + ) { + this.formGroup = this.fb.group({ + comment: ['', Validators.required], + }); + } + + ngOnDestroy(): void { + safeUnsubscribe(this.sub); + } + + submit() { + this.sub = stratosEntityCatalog.apiKey.api.create<RequestInfoState>(this.formGroup.controls.comment.value).pipe( + tap(() => { + this.isBusy.next(true); + this.hasErrored.next(null); + }), + pairwise(), + filter(([oldR, newR]) => oldR.creating && !newR.creating), + map(([, newR]) => newR), + tap(state => { + if (state.error) { + this.hasErrored.next(`Failed to create key: ${state.message}`); + this.isBusy.next(false); + } else { + const response: NormalizedResponse<ApiKey> = state.response; + const entityKey = entityCatalog.getEntityKey(stratosEntityCatalog.apiKey.actions.create('')); + this.dialogRef.close(response.entities[entityKey][response.result[0]]) + } + }), + first() + ).subscribe() + } + +} diff --git a/src/frontend/packages/core/src/features/api-keys/api-keys-page/api-keys-page.component.html b/src/frontend/packages/core/src/features/api-keys/api-keys-page/api-keys-page.component.html new file mode 100644 index 0000000000..0cacc03903 --- /dev/null +++ b/src/frontend/packages/core/src/features/api-keys/api-keys-page/api-keys-page.component.html @@ -0,0 +1,33 @@ +<app-page-header> + <h1>API Keys</h1> + <div class="page-header-right"> + <button id="stratos-api-key" mat-icon-button (click)="addApiKey()" matTooltip="Create API Key"> + <mat-icon>add</mat-icon> + </button> + </div> +</app-page-header> + +<div class="keys-page__new" *ngIf="keyDetails$ | async as keyDetails"> + + <mat-card class="keys-page__card"> + <mat-card-header> + <mat-icon>vpn_key</mat-icon>New API Key + </mat-card-header> + <mat-card-content> + <p>Your API Key has been successfully created. Use the following information to connect to Stratos.</p> + <p><i>Please safely record these details, there is no later way to view them</i></p> + <ul> + <li id="apikey-secret">Secret: {{keyDetails.secret}}</li> + </ul> + <button (click)="clearKeyDetails()" mat-button color="warn">Close</button> + </mat-card-content> + </mat-card> +</div> + +<app-list *ngIf="(hasKeys$ | async) === true"></app-list> +<app-no-content-message *ngIf="(hasKeys$ | async) === false" icon="vpn_key" [firstLine]="'You have no API keys'" + [secondLine]="{ + text: '' + }" [toolbarLink]="{ + text: 'Create an API key' + }" toolbarAlign="stratos-api-key"></app-no-content-message> \ No newline at end of file diff --git a/src/frontend/packages/core/src/features/api-keys/api-keys-page/api-keys-page.component.scss b/src/frontend/packages/core/src/features/api-keys/api-keys-page/api-keys-page.component.scss new file mode 100644 index 0000000000..291e1522e1 --- /dev/null +++ b/src/frontend/packages/core/src/features/api-keys/api-keys-page/api-keys-page.component.scss @@ -0,0 +1,15 @@ +.keys-page { + &__new { + mat-card { + margin-bottom: 24px; + mat-card-header { + align-items: center; + display: flex; + margin-bottom: 15px; + mat-icon { + margin-right: 5px; + } + } + } + } +} \ No newline at end of file diff --git a/src/frontend/packages/core/src/features/api-keys/api-keys-page/api-keys-page.component.spec.ts b/src/frontend/packages/core/src/features/api-keys/api-keys-page/api-keys-page.component.spec.ts new file mode 100644 index 0000000000..1c2318bba0 --- /dev/null +++ b/src/frontend/packages/core/src/features/api-keys/api-keys-page/api-keys-page.component.spec.ts @@ -0,0 +1,42 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialogRef } from '@angular/material/dialog'; + +import { BaseTestModules } from '../../../../test-framework/core-test.helper'; +import { TabNavService } from '../../../tab-nav.service'; +import { ApiKeysPageComponent } from './api-keys-page.component'; + +describe('ApiKeysPageComponent', () => { + let component: ApiKeysPageComponent; + let fixture: ComponentFixture<ApiKeysPageComponent>; + + const mockDialogRef = { + close: () => { } + }; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + ...BaseTestModules, + ], + declarations: [ApiKeysPageComponent], + providers: [ + { + provide: MatDialogRef, + useValue: mockDialogRef + }, + TabNavService + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ApiKeysPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/core/src/features/api-keys/api-keys-page/api-keys-page.component.ts b/src/frontend/packages/core/src/features/api-keys/api-keys-page/api-keys-page.component.ts new file mode 100644 index 0000000000..4f130201b6 --- /dev/null +++ b/src/frontend/packages/core/src/features/api-keys/api-keys-page/api-keys-page.component.ts @@ -0,0 +1,63 @@ +import { Component } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { Observable, Subject } from 'rxjs'; +import { first, map, startWith } from 'rxjs/operators'; + +import { stratosEntityCatalog } from '../../../../../store/src/stratos-entity-catalog'; +import { ApiKeyListConfigService } from '../../../shared/components/list/list-types/apiKeys/apiKey-list-config.service'; +import { ListConfig } from '../../../shared/components/list/list.component.types'; +import { AddApiKeyDialogComponent } from '../add-api-key-dialog/add-api-key-dialog.component'; + +@Component({ + selector: 'app-api-keys-page', + templateUrl: './api-keys-page.component.html', + styleUrls: ['./api-keys-page.component.scss'], + providers: [{ + provide: ListConfig, + useClass: ApiKeyListConfigService, + }] +}) +export class ApiKeysPageComponent { + + public keyDetails = new Subject<string>(); + public keyDetails$ = this.keyDetails.asObservable(); + + /* tslint:disable:ban-types */ + // This is intentionally typed, property can be null and there's different logic associated with it + public hasKeys$: Observable<Boolean>; + /* tslint:enable */ + + constructor( + private dialog: MatDialog, + ) { + this.hasKeys$ = stratosEntityCatalog.apiKey.store.getPaginationService().entities$.pipe( + map(entities => entities && !!entities.length), + startWith(null), + ); + } + + addApiKey() { + this.showDialog().pipe(first()).subscribe(key => { + this.keyDetails.next(key); + }); + } + + clearKeyDetails() { + this.keyDetails.next(); + } + + private showDialog(): Observable<string> { + return this.dialog.open(AddApiKeyDialogComponent, { + disableClose: true, + }).afterClosed().pipe( + map(newApiKey => { + if (newApiKey && newApiKey.guid) { + stratosEntityCatalog.apiKey.api.getMultiple(); + return newApiKey; + } + return null; + }) + ); + } + +} diff --git a/src/frontend/packages/core/src/features/api-keys/api-keys.module.ts b/src/frontend/packages/core/src/features/api-keys/api-keys.module.ts new file mode 100644 index 0000000000..c2311b97d4 --- /dev/null +++ b/src/frontend/packages/core/src/features/api-keys/api-keys.module.ts @@ -0,0 +1,22 @@ +import { NgModule } from '@angular/core'; + +import { CoreModule } from '../../core/core.module'; +import { SharedModule } from '../../shared/shared.module'; +import { ApiKeysPageComponent } from './api-keys-page/api-keys-page.component'; +import { ApiKeysRoutingModule } from './api-keys.routing'; +import { AddApiKeyDialogComponent } from './add-api-key-dialog/add-api-key-dialog.component'; + + +@NgModule({ + imports: [ + CoreModule, + SharedModule, + ApiKeysRoutingModule, + ], + declarations: [ + ApiKeysPageComponent, + AddApiKeyDialogComponent + ] +}) +export class ApiKeysModule { } + diff --git a/src/frontend/packages/core/src/features/api-keys/api-keys.routing.ts b/src/frontend/packages/core/src/features/api-keys/api-keys.routing.ts new file mode 100644 index 0000000000..395c58188b --- /dev/null +++ b/src/frontend/packages/core/src/features/api-keys/api-keys.routing.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { ApiKeysPageComponent } from './api-keys-page/api-keys-page.component'; + +const apiKeys: Routes = [ + { + path: '', + component: ApiKeysPageComponent + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(apiKeys), + ] +}) +export class ApiKeysRoutingModule { } diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.spec.ts b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.spec.ts index 395bdf628c..f448246a7d 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.spec.ts +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.spec.ts @@ -7,11 +7,11 @@ import { RouterTestingModule } from '@angular/router/testing'; import { StoreModule } from '@ngrx/store'; import { appReducers } from '../../../../../store/src/reducers.module'; -import { TabNavService } from '../../../../tab-nav.service'; import { CoreModule } from '../../../core/core.module'; import { PageHeaderService } from '../../../core/page-header-service/page-header.service'; import { SidePanelService } from '../../../shared/services/side-panel.service'; import { SharedModule } from '../../../shared/shared.module'; +import { TabNavService } from '../../../tab-nav.service'; import { MetricsService } from '../../metrics/services/metrics-service'; import { PageSideNavComponent } from '../page-side-nav/page-side-nav.component'; import { SideNavComponent } from '../side-nav/side-nav.component'; diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts index 42a8998120..b7da02c398 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts @@ -14,11 +14,11 @@ import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-ca import { DashboardState } from '../../../../../store/src/reducers/dashboard-reducer'; import { selectDashboardState } from '../../../../../store/src/selectors/dashboard.selectors'; import { stratosEntityCatalog } from '../../../../../store/src/stratos-entity-catalog'; -import { TabNavService } from '../../../../tab-nav.service'; import { CustomizationService } from '../../../core/customizations.types'; import { EndpointsService } from '../../../core/endpoints.service'; import { IHeaderBreadcrumbLink } from '../../../shared/components/page-header/page-header.types'; import { SidePanelService } from '../../../shared/services/side-panel.service'; +import { TabNavService } from '../../../tab-nav.service'; import { IPageSideNavTab } from '../page-side-nav/page-side-nav.component'; import { PageHeaderService } from './../../../core/page-header-service/page-header.service'; import { SideNavItem } from './../side-nav/side-nav.component'; @@ -36,8 +36,8 @@ export class DashboardBaseComponent implements OnInit, OnDestroy, AfterViewInit public isMobile$: Observable<boolean>; public sideNavMode$: Observable<string>; public sideNavMode: string; - public mainNavState$: Observable<{ mode: string; opened: boolean; iconMode: boolean }>; - public rightNavState$: Observable<{ opened: boolean, component?: object, props?: object }>; + public mainNavState$: Observable<{ mode: string; opened: boolean; iconMode: boolean; }>; + public rightNavState$: Observable<{ opened: boolean, component?: object, props?: object; }>; private dashboardState$: Observable<DashboardState>; public noMargin$: Observable<boolean>; private closeSub: Subscription; diff --git a/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.spec.ts b/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.spec.ts index 58b9e971e8..871b3dd00c 100644 --- a/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.spec.ts +++ b/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.spec.ts @@ -1,8 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../tab-nav.service'; -import { BaseTestModulesNoShared } from '../../../../test-framework/core-test.helper'; import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; +import { BaseTestModulesNoShared } from '../../../../test-framework/core-test.helper'; +import { TabNavService } from '../../../tab-nav.service'; import { PageSideNavComponent } from './page-side-nav.component'; describe('PageSideNavComponent', () => { diff --git a/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.ts b/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.ts index bab4cd21dc..aa0fb03459 100644 --- a/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.ts +++ b/src/frontend/packages/core/src/features/dashboard/page-side-nav/page-side-nav.component.ts @@ -2,15 +2,15 @@ import { Component, Input, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; import { Observable, of } from 'rxjs'; +import { map } from 'rxjs/operators'; import { AppState } from '../../../../../store/src/app-state'; import { EntityServiceFactory } from '../../../../../store/src/entity-service-factory.service'; import { selectIsMobile } from '../../../../../store/src/selectors/dashboard.selectors'; -import { TabNavService } from '../../../../tab-nav.service'; import { StratosTabMetadata } from '../../../core/extension/extension-service'; import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; import { IBreadcrumb } from '../../../shared/components/breadcrumbs/breadcrumbs.types'; -import { map } from 'rxjs/operators'; +import { TabNavService } from '../../../tab-nav.service'; diff --git a/src/frontend/packages/core/src/features/dashboard/side-nav/side-nav.component.html b/src/frontend/packages/core/src/features/dashboard/side-nav/side-nav.component.html index 42b179688b..15ec64efb0 100644 --- a/src/frontend/packages/core/src/features/dashboard/side-nav/side-nav.component.html +++ b/src/frontend/packages/core/src/features/dashboard/side-nav/side-nav.component.html @@ -2,7 +2,7 @@ <div class="side-nav__inner"> <div class="side-nav__top"> <div class="side-nav__top-inner"> - <div class="side-nav__logo" (click)="logoClicked.next(true)"> + <div class="side-nav__logo"> <div class="side-nav__logo-img"></div> <div class="side-nav__logo-text">{{ customizations.logoText || '' }} </div> diff --git a/src/frontend/packages/core/src/features/dashboard/side-nav/side-nav.component.ts b/src/frontend/packages/core/src/features/dashboard/side-nav/side-nav.component.ts index aaa4ece632..b6febb4efa 100644 --- a/src/frontend/packages/core/src/features/dashboard/side-nav/side-nav.component.ts +++ b/src/frontend/packages/core/src/features/dashboard/side-nav/side-nav.component.ts @@ -1,13 +1,11 @@ -import { Component, EventEmitter, InjectionToken, Input, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, InjectionToken, Input, Output } from '@angular/core'; import { Store } from '@ngrx/store'; -import { BehaviorSubject, Observable } from 'rxjs'; -import { buffer, debounceTime, filter, map } from 'rxjs/operators'; +import { Observable } from 'rxjs'; -import { ActionHistoryDump } from '../../../../../store/src/actions/action-history.actions'; import { ToggleSideNav } from '../../../../../store/src/actions/dashboard-actions'; import { AppState } from '../../../../../store/src/app-state'; -import { TabNavItem } from '../../../../tab-nav.types'; import { CustomizationService, CustomizationsMetadata } from '../../../core/customizations.types'; +import { TabNavItem } from '../../../tab-nav.types'; export const SIDENAV_COPYRIGHT = new InjectionToken<string>('Optional copyright string for side nav'); @@ -32,7 +30,7 @@ export interface SideNavItem extends TabNavItem { styleUrls: ['./side-nav.component.scss'] }) -export class SideNavComponent implements OnInit { +export class SideNavComponent { public customizations: CustomizationsMetadata; @@ -56,20 +54,7 @@ export class SideNavComponent implements OnInit { @Output() changedMode = new EventEmitter(); private isIconMode = true; - // Button is not always visible on load, so manually push through an event - logoClicked: BehaviorSubject<any> = new BehaviorSubject(true); - public toggleSidenav() { this.store.dispatch(new ToggleSideNav()); } - - ngOnInit() { - const toLength = a => a.length; - const debounced$ = this.logoClicked.pipe(debounceTime(250)); // debounce the click stream - this.logoClicked.pipe( - buffer(debounced$), - map(toLength), - filter(x => x === 3)) - .subscribe(event => this.store.dispatch(new ActionHistoryDump())); - } } diff --git a/src/frontend/packages/core/src/features/endpoints/backup-restore/backup-endpoints/backup-endpoints.component.spec.ts b/src/frontend/packages/core/src/features/endpoints/backup-restore/backup-endpoints/backup-endpoints.component.spec.ts index 3ccb5308ed..5eb19422d8 100644 --- a/src/frontend/packages/core/src/features/endpoints/backup-restore/backup-endpoints/backup-endpoints.component.spec.ts +++ b/src/frontend/packages/core/src/features/endpoints/backup-restore/backup-endpoints/backup-endpoints.component.spec.ts @@ -1,8 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../tab-nav.service'; import { BaseTestModulesNoShared } from '../../../../../test-framework/core-test.helper'; import { SharedModule } from '../../../../shared/shared.module'; +import { TabNavService } from '../../../../tab-nav.service'; import { BackupEndpointsComponent } from './backup-endpoints.component'; describe('BackupEndpointsComponent', () => { diff --git a/src/frontend/packages/core/src/features/endpoints/backup-restore/backup-endpoints/backup-endpoints.component.ts b/src/frontend/packages/core/src/features/endpoints/backup-restore/backup-endpoints/backup-endpoints.component.ts index 9c41ea8a3d..8e09accf47 100644 --- a/src/frontend/packages/core/src/features/endpoints/backup-restore/backup-endpoints/backup-endpoints.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/backup-restore/backup-endpoints/backup-endpoints.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; -import * as moment from 'moment'; +import moment from 'moment'; import { Observable, of, Subject } from 'rxjs'; import { filter, first, map } from 'rxjs/operators'; diff --git a/src/frontend/packages/core/src/features/endpoints/backup-restore/backup-restore-endpoints/backup-restore-endpoints.component.spec.ts b/src/frontend/packages/core/src/features/endpoints/backup-restore/backup-restore-endpoints/backup-restore-endpoints.component.spec.ts index 528a5ee7a9..9e3ebe2233 100644 --- a/src/frontend/packages/core/src/features/endpoints/backup-restore/backup-restore-endpoints/backup-restore-endpoints.component.spec.ts +++ b/src/frontend/packages/core/src/features/endpoints/backup-restore/backup-restore-endpoints/backup-restore-endpoints.component.spec.ts @@ -1,8 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../tab-nav.service'; import { BaseTestModulesNoShared } from '../../../../../test-framework/core-test.helper'; import { SharedModule } from '../../../../shared/shared.module'; +import { TabNavService } from '../../../../tab-nav.service'; import { BackupRestoreEndpointsComponent } from './backup-restore-endpoints.component'; describe('BackupRestoreEndpointsComponent', () => { diff --git a/src/frontend/packages/core/src/features/endpoints/backup-restore/restore-endpoints.service.ts b/src/frontend/packages/core/src/features/endpoints/backup-restore/restore-endpoints.service.ts index 38dee43257..3b9a657097 100644 --- a/src/frontend/packages/core/src/features/endpoints/backup-restore/restore-endpoints.service.ts +++ b/src/frontend/packages/core/src/features/endpoints/backup-restore/restore-endpoints.service.ts @@ -8,7 +8,6 @@ import { GeneralEntityAppState } from '../../../../../store/src/app-state'; import { BrowserStandardEncoder } from '../../../../../store/src/browser-encoder'; import { selectSessionData } from '../../../../../store/src/reducers/auth.reducer'; import { SessionData } from '../../../../../store/src/types/auth.types'; -import { LoggerService } from '../../../core/logger.service'; interface BackupContent { payload: string; @@ -47,7 +46,6 @@ export class RestoreEndpointsService { constructor( private store: Store<GeneralEntityAppState>, private http: HttpClient, - private logger: LoggerService ) { this.setupStep1(); } @@ -97,7 +95,7 @@ export class RestoreEndpointsService { parsedContent = JSON.parse(content); this.unparsableFileContent = null; } catch (err) { - this.logger.warn('Failed to parse file contents: ', err); + console.warn('Failed to parse file contents: ', err); parsedContent = null; this.unparsableFileContent = `${err instanceof Error ? err.message : String(err)}`; } diff --git a/src/frontend/packages/core/src/features/endpoints/backup-restore/restore-endpoints/restore-endpoints.component.spec.ts b/src/frontend/packages/core/src/features/endpoints/backup-restore/restore-endpoints/restore-endpoints.component.spec.ts index ff141f7953..46482bb15e 100644 --- a/src/frontend/packages/core/src/features/endpoints/backup-restore/restore-endpoints/restore-endpoints.component.spec.ts +++ b/src/frontend/packages/core/src/features/endpoints/backup-restore/restore-endpoints/restore-endpoints.component.spec.ts @@ -1,8 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../tab-nav.service'; import { BaseTestModulesNoShared } from '../../../../../test-framework/core-test.helper'; import { SharedModule } from '../../../../shared/shared.module'; +import { TabNavService } from '../../../../tab-nav.service'; import { RestoreEndpointsComponent } from './restore-endpoints.component'; describe('RestoreEndpointsComponent', () => { diff --git a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.spec.ts b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.spec.ts index 752e290c94..3f63c6c7d6 100644 --- a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.spec.ts +++ b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.spec.ts @@ -10,6 +10,7 @@ import { CoreTestingModule } from '../../../../test-framework/core-test.modules' import { CoreModule } from '../../../core/core.module'; import { SidePanelService } from '../../../shared/services/side-panel.service'; import { SharedModule } from '../../../shared/shared.module'; +import { MetricsModule } from '../../metrics/metrics.module'; import { ConnectEndpointComponent } from '../connect-endpoint/connect-endpoint.component'; import { ConnectEndpointConfig } from '../connect.service'; import { CredentialsAuthFormComponent } from './auth-forms/credentials-auth-form.component'; @@ -49,7 +50,8 @@ describe('ConnectEndpointDialogComponent', () => { RouterTestingModule, NoopAnimationsModule, CoreTestingModule, - createBasicStoreModule() + createBasicStoreModule(), + MetricsModule, ] }).overrideModule(BrowserDynamicTestingModule, { set: { diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.spec.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.spec.ts index c0108afa2d..eae2dd40c7 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.spec.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.spec.ts @@ -4,10 +4,11 @@ import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { createBasicStoreModule } from '@stratosui/store/testing'; -import { TabNavService } from '../../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../../test-framework/core-test.modules'; import { CoreModule } from '../../../../core/core.module'; +import { CurrentUserPermissionsService } from '../../../../core/permissions/current-user-permissions.service'; import { SharedModule } from '../../../../shared/shared.module'; +import { TabNavService } from '../../../../tab-nav.service'; import { CreateEndpointBaseStepComponent } from './create-endpoint-base-step.component'; describe('CreateEndpointBaseStepComponent', () => { @@ -27,15 +28,19 @@ describe('CreateEndpointBaseStepComponent', () => { createBasicStoreModule(), NoopAnimationsModule ], - providers: [{ - provide: ActivatedRoute, - useValue: { - snapshot: { - queryParams: {}, - params: { type: 'metrics' } + providers: [ + { + provide: ActivatedRoute, + useValue: { + snapshot: { + queryParams: {}, + params: { type: 'metrics' } + } } - } - }, TabNavService], + }, + TabNavService, + CurrentUserPermissionsService + ], }) .compileComponents(); })); diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint.component.spec.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint.component.spec.ts index 7479b4a0d1..a074af1f7b 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint.component.spec.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint.component.spec.ts @@ -5,11 +5,12 @@ import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { createBasicStoreModule } from '@stratosui/store/testing'; -import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; import { CoreModule } from '../../../core/core.module'; +import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; import { SidePanelService } from '../../../shared/services/side-panel.service'; import { SharedModule } from '../../../shared/shared.module'; +import { TabNavService } from '../../../tab-nav.service'; import { ConnectEndpointComponent } from '../connect-endpoint/connect-endpoint.component'; import { CreateEndpointCfStep1Component } from './create-endpoint-cf-step-1/create-endpoint-cf-step-1.component'; import { CreateEndpointConnectComponent } from './create-endpoint-connect/create-endpoint-connect.component'; @@ -49,7 +50,9 @@ describe('CreateEndpointComponent', () => { } }, TabNavService, - SidePanelService], + SidePanelService, + CurrentUserPermissionsService + ], }) .compileComponents(); })); diff --git a/src/frontend/packages/core/src/features/endpoints/edit-endpoint/edit-endpoint.component.spec.ts b/src/frontend/packages/core/src/features/endpoints/edit-endpoint/edit-endpoint.component.spec.ts index 9b94a55029..6dbdbb0753 100644 --- a/src/frontend/packages/core/src/features/endpoints/edit-endpoint/edit-endpoint.component.spec.ts +++ b/src/frontend/packages/core/src/features/endpoints/edit-endpoint/edit-endpoint.component.spec.ts @@ -4,10 +4,11 @@ import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { createBasicStoreModule } from '../../../../../store/testing/public-api'; -import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; import { CoreModule } from '../../../core/core.module'; +import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; import { SharedModule } from '../../../shared/shared.module'; +import { TabNavService } from '../../../tab-nav.service'; import { EditEndpointStepComponent } from './edit-endpoint-step/edit-endpoint-step.component'; import { EditEndpointComponent } from './edit-endpoint.component'; @@ -36,7 +37,8 @@ describe('EditEndpointComponent', () => { } } }, - TabNavService + TabNavService, + CurrentUserPermissionsService ] }) .compileComponents(); diff --git a/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.html b/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.html index c8e3f991c3..0775265f9d 100644 --- a/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.html +++ b/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.html @@ -5,7 +5,7 @@ <h1>Endpoints</h1> [routerLink]="'/endpoints/new/'" matTooltip="Register Endpoint"> <mat-icon>add</mat-icon> </button> - <button id="stratos-add-endpoint" *appUserPermission="canRegisterEndpoint" mat-icon-button + <button id="stratos-restore-backup" *appUserPermission="canRegisterEndpoint" mat-icon-button [routerLink]="'/endpoints/backup-restore'" matTooltip="Backup/Restore Endpoints"> <mat-icon>settings_backup_restore</mat-icon> </button> diff --git a/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.spec.ts b/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.spec.ts index 2906a26a9d..9773e3ab52 100644 --- a/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.spec.ts +++ b/src/frontend/packages/core/src/features/endpoints/endpoints-page/endpoints-page.component.spec.ts @@ -6,11 +6,11 @@ import { StoreModule } from '@ngrx/store'; import { createBasicStoreModule } from '@stratosui/store/testing'; import { appReducers } from '../../../../../store/src/reducers.module'; -import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; import { CoreModule } from '../../../core/core.module'; import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; import { SharedModule } from '../../../shared/shared.module'; +import { TabNavService } from '../../../tab-nav.service'; import { SidePanelService } from './../../../shared/services/side-panel.service'; import { EndpointsPageComponent } from './endpoints-page.component'; diff --git a/src/frontend/packages/core/src/features/error-page/error-page/error-page.component.spec.ts b/src/frontend/packages/core/src/features/error-page/error-page/error-page.component.spec.ts index 206f02d3d9..f8cd0828ea 100644 --- a/src/frontend/packages/core/src/features/error-page/error-page/error-page.component.spec.ts +++ b/src/frontend/packages/core/src/features/error-page/error-page/error-page.component.spec.ts @@ -2,10 +2,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { createBasicStoreModule } from '@stratosui/store/testing'; -import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; import { CoreModule } from '../../../core/core.module'; import { SharedModule } from '../../../shared/shared.module'; +import { TabNavService } from '../../../tab-nav.service'; import { ErrorPageComponent } from './error-page.component'; describe('ErrorPageComponent', () => { diff --git a/src/frontend/packages/core/src/features/event-page/events-page/events-page.component.spec.ts b/src/frontend/packages/core/src/features/event-page/events-page/events-page.component.spec.ts index 883569abca..c79c2d4d5d 100644 --- a/src/frontend/packages/core/src/features/event-page/events-page/events-page.component.spec.ts +++ b/src/frontend/packages/core/src/features/event-page/events-page/events-page.component.spec.ts @@ -2,10 +2,11 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { createBasicStoreModule } from '@stratosui/store/testing'; -import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; import { CoreModule } from '../../../core/core.module'; +import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; import { SharedModule } from '../../../shared/shared.module'; +import { TabNavService } from '../../../tab-nav.service'; import { EventsPageComponent } from './events-page.component'; describe('EventsPageComponent', () => { @@ -22,7 +23,10 @@ describe('EventsPageComponent', () => { createBasicStoreModule(), RouterTestingModule ], - providers: [TabNavService] + providers: [ + TabNavService, + CurrentUserPermissionsService + ] }) .compileComponents(); })); diff --git a/src/frontend/packages/core/src/features/home/home/home-page.component.spec.ts b/src/frontend/packages/core/src/features/home/home/home-page.component.spec.ts index a456ae097f..9579ad3f19 100644 --- a/src/frontend/packages/core/src/features/home/home/home-page.component.spec.ts +++ b/src/frontend/packages/core/src/features/home/home/home-page.component.spec.ts @@ -4,10 +4,11 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; import { createBasicStoreModule } from '@stratosui/store/testing'; -import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; import { CoreModule } from '../../../core/core.module'; +import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; import { SharedModule } from '../../../shared/shared.module'; +import { TabNavService } from '../../../tab-nav.service'; import { HomePageComponent } from './home-page.component'; describe('HomePageComponent', () => { @@ -26,7 +27,10 @@ describe('HomePageComponent', () => { CoreTestingModule, createBasicStoreModule() ], - providers: [TabNavService] + providers: [ + TabNavService, + CurrentUserPermissionsService + ] }) .compileComponents(); })); diff --git a/src/frontend/packages/core/src/features/home/home/home-page.component.ts b/src/frontend/packages/core/src/features/home/home/home-page.component.ts index 6700b83fa3..94e3e09635 100644 --- a/src/frontend/packages/core/src/features/home/home/home-page.component.ts +++ b/src/frontend/packages/core/src/features/home/home/home-page.component.ts @@ -10,7 +10,6 @@ import { IUserFavoritesGroups } from '../../../../../store/src/types/favorite-gr import { UserFavorite } from '../../../../../store/src/types/user-favorites.types'; import { UserFavoriteManager } from '../../../../../store/src/user-favorite-manager'; import { EndpointsService } from '../../../core/endpoints.service'; -import { LoggerService } from '../../../core/logger.service'; @Component({ selector: 'app-home-page', @@ -27,7 +26,6 @@ export class HomePageComponent { constructor( endpointsService: EndpointsService, store: Store<AppState>, - logger: LoggerService, public userFavoriteManager: UserFavoriteManager ) { this.allEndpointIds$ = endpointsService.endpoints$.pipe( diff --git a/src/frontend/packages/core/src/features/metrics/metrics.helpers.ts b/src/frontend/packages/core/src/features/metrics/metrics.helpers.ts index 72dfa2d0c1..677a94ee07 100644 --- a/src/frontend/packages/core/src/features/metrics/metrics.helpers.ts +++ b/src/frontend/packages/core/src/features/metrics/metrics.helpers.ts @@ -51,7 +51,7 @@ export function mapMetricsData(ep: MetricsEndpointProvider): MetricsEndpointInfo && Array.isArray(ep.provider.metadata.metrics_stratos)) { ep.provider.metadata.metrics_stratos.forEach(endp => { // See if we already know about this endpoint - const hasEndpoint = data.findIndex(i => i.url === endp.url || i.url === endp.cfEndpoint) !== -1; + const hasEndpoint = data.findIndex(i => compareUrl(i.url, endp.url) || compareUrl(i.url, endp.cfEndpoint)) !== -1; if (!hasEndpoint) { const catalogEndpoint = entityCatalog.getEndpoint(endp.type, ''); if (catalogEndpoint) { // Provider metadata could give unknown endpoint @@ -76,3 +76,16 @@ export function mapMetricsData(ep: MetricsEndpointProvider): MetricsEndpointInfo } return data; } + +// Simple URL compare that ignores tailing forward slashes +function compareUrl(a: string, b: string): boolean { + if (a && a.endsWith('/')) { + a = a.substr(0, a.length -1); + } + + if (b && b.endsWith('/')) { + b = b.substr(0, b.length -1); + } + + return a === b; +} diff --git a/src/frontend/packages/core/src/features/metrics/metrics.module.ts b/src/frontend/packages/core/src/features/metrics/metrics.module.ts index 9c14db9211..0fa61010da 100644 --- a/src/frontend/packages/core/src/features/metrics/metrics.module.ts +++ b/src/frontend/packages/core/src/features/metrics/metrics.module.ts @@ -1,7 +1,9 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; +import { stratosEntityCatalog } from '../../../../store/src/stratos-entity-catalog'; import { CoreModule } from '../../core/core.module'; +import { BaseEndpointAuth } from '../../core/endpoint-auth'; import { SharedModule } from '../../shared/shared.module'; import { MetricsEndpointDetailsComponent } from './metrics-endpoint-details/metrics-endpoint-details.component'; import { MetricsRoutingModule } from './metrics.routing'; @@ -23,4 +25,13 @@ import { MetricsService } from './services/metrics-service'; MetricsEndpointDetailsComponent, ] }) -export class MetricsModule { } +export class MetricsModule { + + constructor() { + // Register the endpoint details component + // This is done here to break circular dependency - since the registration is done in the store package + // But the core package defines the component for the endpoint card details + stratosEntityCatalog.metricsEndpoint.setListComponent(MetricsEndpointDetailsComponent); + stratosEntityCatalog.metricsEndpoint.setAuthTypes([BaseEndpointAuth.UsernamePassword, BaseEndpointAuth.None]); + } +} diff --git a/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.spec.ts b/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.spec.ts index 37500654bb..ae19f047f3 100644 --- a/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.spec.ts +++ b/src/frontend/packages/core/src/features/metrics/metrics/metrics.component.spec.ts @@ -4,10 +4,11 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; import { createBasicStoreModule } from '@stratosui/store/testing'; -import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; import { CoreModule } from '../../../core/core.module'; +import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; import { SharedModule } from '../../../shared/shared.module'; +import { TabNavService } from '../../../tab-nav.service'; import { MetricsService } from '../services/metrics-service'; import { MetricsComponent } from './metrics.component'; @@ -27,7 +28,11 @@ describe('MetricsComponent', () => { createBasicStoreModule(), ], declarations: [MetricsComponent], - providers: [MetricsService, TabNavService] + providers: [ + MetricsService, + TabNavService, + CurrentUserPermissionsService + ] }) .compileComponents(); })); diff --git a/src/frontend/packages/core/src/features/no-endpoints-non-admin/no-endpoints-non-admin.component.spec.ts b/src/frontend/packages/core/src/features/no-endpoints-non-admin/no-endpoints-non-admin.component.spec.ts index fb688116a9..4e8f59f873 100644 --- a/src/frontend/packages/core/src/features/no-endpoints-non-admin/no-endpoints-non-admin.component.spec.ts +++ b/src/frontend/packages/core/src/features/no-endpoints-non-admin/no-endpoints-non-admin.component.spec.ts @@ -2,10 +2,11 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { createBasicStoreModule } from '@stratosui/store/testing'; -import { TabNavService } from '../../../tab-nav.service'; import { CoreTestingModule } from '../../../test-framework/core-test.modules'; import { CoreModule } from '../../core/core.module'; +import { CurrentUserPermissionsService } from '../../core/permissions/current-user-permissions.service'; import { SharedModule } from '../../shared/shared.module'; +import { TabNavService } from '../../tab-nav.service'; import { NoEndpointsNonAdminComponent } from './no-endpoints-non-admin.component'; describe('NoEndpointsNonAdminComponent', () => { @@ -22,7 +23,10 @@ describe('NoEndpointsNonAdminComponent', () => { CoreTestingModule, createBasicStoreModule(), ], - providers: [TabNavService] + providers: [ + TabNavService, + CurrentUserPermissionsService + ] }) .compileComponents(); })); diff --git a/src/frontend/packages/core/src/features/setup/local-account-wizard/local-account-wizard.component.spec.ts b/src/frontend/packages/core/src/features/setup/local-account-wizard/local-account-wizard.component.spec.ts index d76be87a52..a15ea5f550 100644 --- a/src/frontend/packages/core/src/features/setup/local-account-wizard/local-account-wizard.component.spec.ts +++ b/src/frontend/packages/core/src/features/setup/local-account-wizard/local-account-wizard.component.spec.ts @@ -4,11 +4,12 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; import { createEmptyStoreModule } from '@stratosui/store/testing'; -import { TabNavService } from '../../../../tab-nav.service'; import { CoreModule } from '../../../core/core.module'; import { MDAppModule } from '../../../core/md.module'; +import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; import { PageHeaderModule } from '../../../shared/components/page-header/page-header.module'; import { SharedModule } from '../../../shared/shared.module'; +import { TabNavService } from '../../../tab-nav.service'; import { SetupModule } from '../setup.module'; import { LocalAccountWizardComponent } from './local-account-wizard.component'; @@ -30,7 +31,10 @@ describe('LocalAccountWizardComponent', () => { createEmptyStoreModule(), NoopAnimationsModule, ], - providers: [TabNavService] + providers: [ + TabNavService, + CurrentUserPermissionsService + ] }) .compileComponents(); })); diff --git a/src/frontend/packages/core/src/features/setup/setup-welcome/setup-welcome.component.spec.ts b/src/frontend/packages/core/src/features/setup/setup-welcome/setup-welcome.component.spec.ts index 071ba41d53..4e74e46d21 100644 --- a/src/frontend/packages/core/src/features/setup/setup-welcome/setup-welcome.component.spec.ts +++ b/src/frontend/packages/core/src/features/setup/setup-welcome/setup-welcome.component.spec.ts @@ -3,12 +3,12 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { TabNavService } from '../../../../tab-nav.service'; import { generateBaseTestStoreModules } from '../../../../test-framework/core-test.helper'; import { CoreModule } from '../../../core/core.module'; import { MDAppModule } from '../../../core/md.module'; import { PageHeaderModule } from '../../../shared/components/page-header/page-header.module'; import { SharedModule } from '../../../shared/shared.module'; +import { TabNavService } from '../../../tab-nav.service'; import { SetupModule } from '../setup.module'; import { SetupWelcomeComponent } from './setup-welcome.component'; diff --git a/src/frontend/packages/core/src/features/setup/uaa-wizard/console-uaa-wizard.component.spec.ts b/src/frontend/packages/core/src/features/setup/uaa-wizard/console-uaa-wizard.component.spec.ts index 873fe6b16d..56353578c4 100644 --- a/src/frontend/packages/core/src/features/setup/uaa-wizard/console-uaa-wizard.component.spec.ts +++ b/src/frontend/packages/core/src/features/setup/uaa-wizard/console-uaa-wizard.component.spec.ts @@ -3,13 +3,14 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; import { StoreModule } from '@ngrx/store'; -import { appReducers } from 'frontend/packages/store/src/reducers.module'; -import { TabNavService } from '../../../../tab-nav.service'; +import { appReducers } from '../../../../../store/src/reducers.module'; import { CoreModule } from '../../../core/core.module'; import { MDAppModule } from '../../../core/md.module'; +import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; import { PageHeaderModule } from '../../../shared/components/page-header/page-header.module'; import { SharedModule } from '../../../shared/shared.module'; +import { TabNavService } from '../../../tab-nav.service'; import { SetupModule } from '../setup.module'; import { ConsoleUaaWizardComponent } from './console-uaa-wizard.component'; @@ -31,7 +32,10 @@ describe('ConsoleUaaWizardComponent', () => { StoreModule.forRoot(appReducers), NoopAnimationsModule, ], - providers: [TabNavService] + providers: [ + TabNavService, + CurrentUserPermissionsService + ] }) .compileComponents(); })); diff --git a/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.spec.ts b/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.spec.ts index 89281e1382..3d68b5bcbe 100644 --- a/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.spec.ts +++ b/src/frontend/packages/core/src/features/user-profile/edit-profile-info/edit-profile-info.component.spec.ts @@ -4,12 +4,12 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; import { createBasicStoreModule } from '@stratosui/store/testing'; -import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; import { CoreModule } from '../../../core/core.module'; import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; import { UserProfileService } from '../../../core/user-profile.service'; import { SharedModule } from '../../../shared/shared.module'; +import { TabNavService } from '../../../tab-nav.service'; import { EditProfileInfoComponent } from './edit-profile-info.component'; describe('EditProfileInfoComponent', () => { diff --git a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.spec.ts b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.spec.ts index 2c855307de..e10f58880b 100644 --- a/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.spec.ts +++ b/src/frontend/packages/core/src/features/user-profile/profile-info/profile-info.component.spec.ts @@ -4,11 +4,12 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; import { createBasicStoreModule } from '@stratosui/store/testing'; -import { TabNavService } from '../../../../tab-nav.service'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; import { CoreModule } from '../../../core/core.module'; +import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; import { UserProfileService } from '../../../core/user-profile.service'; import { SharedModule } from '../../../shared/shared.module'; +import { TabNavService } from '../../../tab-nav.service'; import { ProfileInfoComponent } from './profile-info.component'; describe('ProfileInfoComponent', () => { @@ -27,7 +28,11 @@ describe('ProfileInfoComponent', () => { CoreTestingModule, createBasicStoreModule() ], - providers: [UserProfileService, TabNavService] + providers: [ + UserProfileService, + TabNavService, + CurrentUserPermissionsService + ] }) .compileComponents(); })); diff --git a/src/frontend/packages/core/src/route-reuse-stragegy.ts b/src/frontend/packages/core/src/route-reuse-stragegy.ts index d350557f57..53170825fb 100644 --- a/src/frontend/packages/core/src/route-reuse-stragegy.ts +++ b/src/frontend/packages/core/src/route-reuse-stragegy.ts @@ -1,8 +1,8 @@ +import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router'; import { AppComponent } from './app.component'; import { DashboardBaseComponent } from './features/dashboard/dashboard-base/dashboard-base.component'; -import { Injectable } from "@angular/core"; @Injectable() diff --git a/src/frontend/packages/core/src/shared/components/app-action-monitor/app-action-monitor.component.ts b/src/frontend/packages/core/src/shared/components/app-action-monitor/app-action-monitor.component.ts index 84b00b4d77..929836fbc0 100644 --- a/src/frontend/packages/core/src/shared/components/app-action-monitor/app-action-monitor.component.ts +++ b/src/frontend/packages/core/src/shared/components/app-action-monitor/app-action-monitor.component.ts @@ -84,11 +84,11 @@ export class AppActionMonitorComponent<T> implements OnInit { }; // Some data$ obs only ever emit once. If we subscribed directly to this then that emit would be consumed and will not be available - // in the data source connect subscription. So wrap it in a replay to ensure the last emitted value is available + // in the data source connect subscription. So wrap it in a replay to ensure the last emitted value is available this.replayData$ = this.data$.pipe( publishReplay(1), refCount() - ) + ); this.allColumns = [...this.columns, monitorColumn]; this.dataSource = { diff --git a/src/frontend/packages/core/src/shared/components/boolean-indicator/boolean-indicator.component.ts b/src/frontend/packages/core/src/shared/components/boolean-indicator/boolean-indicator.component.ts index 6a1675dc5f..be48e1d828 100644 --- a/src/frontend/packages/core/src/shared/components/boolean-indicator/boolean-indicator.component.ts +++ b/src/frontend/packages/core/src/shared/components/boolean-indicator/boolean-indicator.component.ts @@ -50,6 +50,25 @@ export class BooleanIndicatorComponent { @Input() showText = true; + private icons = { + Yes: 'check_circle', + Enabled: 'check_circle', + Healthy: 'check_circle', + True: 'check_circle', + Succeeded: 'check_circle', + Add: 'add_circle', + No: 'highlight_off', + Disabled: 'highlight_off', + Unhealthy: 'highlight_off', + Failed: 'remove_circle', + False: 'highlight_off', + Remove: 'remove_circle', + Locked: 'lock_outline', + Unlocked: 'lock_open', + Unknown: 'help_outline', + Progress: 'cached' + }; + private pType: BooleanIndicatorType; @Input() get type(): BooleanIndicatorType { @@ -74,31 +93,12 @@ export class BooleanIndicatorComponent { const isUnknown = typeof this.isTrue !== 'boolean'; this.booleanOutput = this.getIconTextAndSeverity({ isTrue: this.isTrue, - isUnknown: isUnknown, + isUnknown, inverse: this.inverse, subtle: this.subtle }); } - private icons = { - Yes: 'check_circle', - Enabled: 'check_circle', - Healthy: 'check_circle', - True: 'check_circle', - Succeeded: 'check_circle', - Add: 'add_circle', - No: 'highlight_off', - Disabled: 'highlight_off', - Unhealthy: 'highlight_off', - Failed: 'remove_circle', - False: 'highlight_off', - Remove: 'remove_circle', - Locked: 'lock_outline', - Unlocked: 'lock_open', - Unknown: 'help_outline', - Progress: 'cached' - }; - private getIconTextAndSeverity = ( { isTrue = false, isUnknown = false, inverse = false, subtle = true }: IBooleanConfig ): IBooleanOutput => { @@ -117,13 +117,13 @@ export class BooleanIndicatorComponent { isTrue: inverse ? !isTrue : isTrue, subtle }; - } + }; private getText = ({ isTrue = false, inverse = false }: IBooleanConfig): string => { const [enabledText, disabledText] = this.getTypeText(this.type); const value = inverse ? !isTrue : isTrue; return this.capitalizeFirstLetter(value ? enabledText : disabledText); - } + }; private getTypeText = (s: string) => s.split('-'); diff --git a/src/frontend/packages/core/src/shared/components/copy-to-clipboard/copy-to-clipboard.component.ts b/src/frontend/packages/core/src/shared/components/copy-to-clipboard/copy-to-clipboard.component.ts index 1534ec9e43..39046acd13 100644 --- a/src/frontend/packages/core/src/shared/components/copy-to-clipboard/copy-to-clipboard.component.ts +++ b/src/frontend/packages/core/src/shared/components/copy-to-clipboard/copy-to-clipboard.component.ts @@ -1,8 +1,6 @@ import { DOCUMENT } from '@angular/common'; import { Component, Inject, Input, OnInit } from '@angular/core'; -import { LoggerService } from '../../../core/logger.service'; - @Component({ selector: 'app-copy-to-clipboard', templateUrl: './copy-to-clipboard.component.html', @@ -23,7 +21,6 @@ export class CopyToClipboardComponent implements OnInit { constructor( @Inject(DOCUMENT) document: Document, - private logService: LoggerService ) { this.document = document; } @@ -63,7 +60,7 @@ export class CopyToClipboardComponent implements OnInit { this.copySuccessWait = true; setTimeout(() => this.copySuccessWait = false, 2000); } catch (err) { - this.logService.warn('Failed to copy to clipboard'); + console.warn('Failed to copy to clipboard'); } this.document.body.removeChild(textArea); diff --git a/src/frontend/packages/core/src/shared/components/date-time/date-time.component.ts b/src/frontend/packages/core/src/shared/components/date-time/date-time.component.ts index 340445fef4..fe9026a7a4 100644 --- a/src/frontend/packages/core/src/shared/components/date-time/date-time.component.ts +++ b/src/frontend/packages/core/src/shared/components/date-time/date-time.component.ts @@ -1,9 +1,8 @@ -import { SetupModule } from './../../../features/setup/setup.module'; -import { Component, OnInit, Output, OnDestroy, Input, EventEmitter } from '@angular/core'; +import { Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core'; import { FormControl } from '@angular/forms'; -import * as moment from 'moment'; -import { tap, map, filter, shareReplay, debounceTime } from 'rxjs/operators'; -import { combineLatest, Subscription, Observable } from 'rxjs'; +import moment from 'moment'; +import { combineLatest, Observable, Subscription } from 'rxjs'; +import { debounceTime, filter, map, shareReplay, tap } from 'rxjs/operators'; @Component({ selector: 'app-date-time', diff --git a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts index 16797fb932..06367a9196 100644 --- a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts +++ b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts @@ -87,12 +87,9 @@ export class FavoritesMetaCardComponent { this.prettyName = prettyName || 'Unknown'; this.entityConfig = new ComponentEntityMonitorConfig(favorite.guid, stratosEntityFactory(userFavouritesEntityType)); - // If this favorite is an endpoint, lookup the image for it from the entitiy catalog + // If this favorite is an endpoint, lookup the image for it from the entity catalog if (this.favorite.entityType === 'endpoint') { - this.iconUrl$ = endpoint$.pipe(map(a => { - const entityDef = entityCatalog.getEndpoint(a.cnsi_type, a.sub_type); - return entityDef.definition.logoUrl; - })); + this.iconUrl$ = endpoint$.pipe(map(a => entityCatalog.getEndpoint(a.cnsi_type, a.sub_type).definition.logoUrl)); } else { this.iconUrl$ = observableOf(''); } @@ -141,7 +138,7 @@ export class FavoritesMetaCardComponent { private removeFavorite = () => { stratosEntityCatalog.userFavorite.api.delete(this.favorite); - } + }; public toggleMoreError() { this.showMore = !this.showMore; diff --git a/src/frontend/packages/core/src/shared/components/list/list-table/table-cell/table-cell.component.spec.ts b/src/frontend/packages/core/src/shared/components/list/list-table/table-cell/table-cell.component.spec.ts index b7ff749166..ffea2724cc 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-table/table-cell/table-cell.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-table/table-cell/table-cell.component.spec.ts @@ -15,10 +15,6 @@ import { AppChipsComponent } from '../../../chips/chips.component'; import { UsageGaugeComponent } from '../../../usage-gauge/usage-gauge.component'; import { listTableCells, TableCellComponent } from './table-cell.component'; -/* tslint:disable:max-line-length */ - -/* tslint:enable:max-line-length */ - describe('TableCellComponent', () => { let component: TableCellComponent<any>; let fixture: ComponentFixture<TableCellComponent<any>>; diff --git a/src/frontend/packages/core/src/shared/components/list/list-table/table.component.ts b/src/frontend/packages/core/src/shared/components/list/list-table/table.component.ts index ab1af731f3..e63d89b826 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-table/table.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-table/table.component.ts @@ -64,7 +64,7 @@ export class TableComponent<T> implements OnInit, OnDestroy { public columnNames: string[]; @Input() minRowHeight: string; - @Input() prominentErrorBar: boolean = true; + @Input() prominentErrorBar = true; ngOnInit() { if (this.addSelect || this.expandComponent || this.addActions) { diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/apiKeys/apiKey-data-source.ts b/src/frontend/packages/core/src/shared/components/list/list-types/apiKeys/apiKey-data-source.ts new file mode 100644 index 0000000000..a57eee6721 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/list/list-types/apiKeys/apiKey-data-source.ts @@ -0,0 +1,32 @@ +import { Store } from '@ngrx/store'; + +import { GetAllApiKeys } from '../../../../../../../store/src/actions/apiKey.actions'; +import { ApiKey } from '../../../../../../../store/src/apiKey.types'; +import { AppState } from '../../../../../../../store/src/app-state'; +import { ListDataSource } from '../../data-sources-controllers/list-data-source'; +import { IListConfig } from '../../list.component.types'; + +export class ApiKeyDataSource extends ListDataSource<ApiKey> { + + constructor( + store: Store<AppState>, + listConfig: IListConfig<ApiKey>, + action: GetAllApiKeys, + ) { + super({ + store, + action, + schema: action.entity[0], + getRowUniqueId: (object) => action.entity[0].getId(object), + paginationKey: action.paginationKey, + isLocal: true, + transformEntities: [ + { + type: 'filter', + field: 'comment' + }, + ], + listConfig, + }); + } +} diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/apiKeys/apiKey-list-config.service.ts b/src/frontend/packages/core/src/shared/components/list/list-types/apiKeys/apiKey-list-config.service.ts new file mode 100644 index 0000000000..035a9067da --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/list/list-types/apiKeys/apiKey-list-config.service.ts @@ -0,0 +1,103 @@ +import { Injectable } from '@angular/core'; +import { SortDirection } from '@angular/material/sort'; +import { Store } from '@ngrx/store'; +import moment from 'moment'; + +import { ListView } from '../../../../../../../store/src/actions/list.actions'; +import { ApiKey } from '../../../../../../../store/src/apiKey.types'; +import { AppState } from '../../../../../../../store/src/app-state'; +import { stratosEntityCatalog } from '../../../../../../../store/src/stratos-entity-catalog'; +import { ConfirmationDialogConfig } from '../../../confirmation-dialog.config'; +import { ConfirmationDialogService } from '../../../confirmation-dialog.service'; +import { ITableColumn } from '../../list-table/table.types'; +import { IListAction, IListConfig, ListViewTypes } from '../../list.component.types'; +import { ApiKeyDataSource } from './apiKey-data-source'; + +@Injectable() +export class ApiKeyListConfigService implements IListConfig<ApiKey> { + + private static comment = 'comment'; + private static lastUsedName = 'last_used'; + + private deleteAction: IListAction<ApiKey> = { + action: (item: ApiKey) => { + const confirmation = new ConfirmationDialogConfig( + 'Delete Key', + `Are you sure?`, + 'Delete', + true + ); + this.confirmDialog.open( + confirmation, + () => stratosEntityCatalog.apiKey.api.delete(item.guid) + ); + }, + label: 'Delete', + description: 'Delete API Key', + } + private singleActions: IListAction<ApiKey>[] = [this.deleteAction]; + + + public readonly columns: ITableColumn<ApiKey>[] = [ + { + columnId: ApiKeyListConfigService.comment, + headerCell: () => 'Description', + cellDefinition: { + valuePath: 'comment' + }, + sort: { + type: 'sort', + orderKey: ApiKeyListConfigService.comment, + field: 'comment' + }, + cellFlex: '2' + }, + { + columnId: ApiKeyListConfigService.lastUsedName, + headerCell: () => 'Last Used', + cellDefinition: { + getValue: row => row.last_used ? moment(row.last_used).format('LLL') : null + }, + sort: { + type: 'sort', + orderKey: ApiKeyListConfigService.lastUsedName, + field: 'last_used' + }, + cellFlex: '1' + } + ]; + + isLocal = true; + dataSource: ApiKeyDataSource; + viewType = ListViewTypes.TABLE_ONLY; + defaultView = 'table' as ListView; + text = { + title: '', + filter: 'Filter API Keys' + }; + enableTextFilter = true; + + constructor( + store: Store<AppState>, + private confirmDialog: ConfirmationDialogService, + ) { + const action = stratosEntityCatalog.apiKey.actions.getMultiple(); + action.initialParams = { + 'order-direction': 'desc' as SortDirection, + 'order-direction-field': 'comment' + }; + this.dataSource = new ApiKeyDataSource( + store, + this, + action + ); + } + + public getGlobalActions = () => []; + public getMultiActions = () => []; + public getSingleActions = () => this.singleActions; + public getColumns = () => this.columns; + public getDataSource = () => this.dataSource; + public getMultiFiltersConfigs = () => []; + +} diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.ts b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.ts index 82bb85e5a4..b06faaca61 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.ts @@ -9,10 +9,10 @@ import { ViewContainerRef, } from '@angular/core'; import { Store } from '@ngrx/store'; -import { AppState } from 'frontend/packages/store/src/app-state'; import { Observable, of, ReplaySubject, Subscription } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; +import { AppState } from '../../../../../../../../store/src/app-state'; import { getFullEndpointApiUrl } from '../../../../../../../../store/src/endpoint-utils'; import { entityCatalog } from '../../../../../../../../store/src/entity-catalog/entity-catalog'; import { diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-list.helpers.ts b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-list.helpers.ts index 41347976e7..c1350720f1 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-list.helpers.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-list.helpers.ts @@ -10,7 +10,6 @@ import { entityCatalog } from '../../../../../../../store/src/entity-catalog/ent import { ActionState } from '../../../../../../../store/src/reducers/api-request-reducer/types'; import { stratosEntityCatalog } from '../../../../../../../store/src/stratos-entity-catalog'; import { EndpointModel } from '../../../../../../../store/src/types/endpoint.types'; -import { LoggerService } from '../../../../../core/logger.service'; import { CurrentUserPermissionsService } from '../../../../../core/permissions/current-user-permissions.service'; import { StratosCurrentUserPermissions } from '../../../../../core/permissions/stratos-user-permissions.checker'; import { @@ -66,7 +65,6 @@ export class EndpointListHelper { private dialog: MatDialog, private currentUserPermissionsService: CurrentUserPermissionsService, private confirmDialog: ConfirmationDialogService, - private log: LoggerService, private snackBarService: SnackBarService, ) { } @@ -192,7 +190,7 @@ export class EndpointListHelper { endpointDetails: container }; if (!component) { - this.log.warn(`Attempted to create a non-endpoint list details component "${listDetailsComponent}"`); + console.warn(`Attempted to create a non-endpoint list details component "${listDetailsComponent}"`); this.destroyEndpointDetails(refs); } return refs; diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.ts b/src/frontend/packages/core/src/shared/components/list/list.component.ts index 318bd0dd7e..2bc6da4898 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list.component.ts @@ -701,7 +701,7 @@ export class ListComponent<T> implements OnInit, OnChanges, OnDestroy, AfterView map(requestInfo => ({ deleting: requestInfo.deleting.busy, error: requestInfo.deleting.error, - message: requestInfo.deleting.error ? `Sorry, deletion failed` : null + message: requestInfo.deleting.error ? requestInfo.deleting.message || `Sorry, deletion failed` : null })) ); }; diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.types.ts b/src/frontend/packages/core/src/shared/components/list/list.component.types.ts index 6416a73dc3..174fc5eaa9 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.types.ts +++ b/src/frontend/packages/core/src/shared/components/list/list.component.types.ts @@ -1,5 +1,5 @@ import { Injectable, Type } from '@angular/core'; -import * as moment from 'moment'; +import moment from 'moment'; import { BehaviorSubject, combineLatest, Observable, of as observableOf } from 'rxjs'; import { first, map, startWith } from 'rxjs/operators'; @@ -230,7 +230,7 @@ export class MultiFilterManager<T> { if (options && options.length > 0) { this.selectItem(options[0].value); } - }) + }); } } diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.spec.ts b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.spec.ts index 6e783f38c2..9c7a44a49e 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.spec.ts @@ -5,7 +5,6 @@ import { RouterTestingModule } from '@angular/router/testing'; import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; -import { LoggerService } from '../../../core/logger.service'; import { SidepanelPreviewComponent } from '../sidepanel-preview/sidepanel-preview.component'; import { MDAppModule } from './../../../core/md.module'; import { SidePanelService } from './../../services/side-panel.service'; @@ -18,7 +17,7 @@ describe('MarkdownPreviewComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [MarkdownPreviewComponent, SidepanelPreviewComponent], - providers: [LoggerService, HttpClient, HttpHandler, SidePanelService], + providers: [HttpClient, HttpHandler, SidePanelService], imports: [ MDAppModule, RouterTestingModule, diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts index 9dba74b1d5..ee00380c54 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts @@ -1,9 +1,8 @@ import { HttpClient } from '@angular/common/http'; import { Component, ElementRef, Input, SecurityContext, ViewChild } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; -import * as markdown from 'marked'; +import markdown from 'marked'; -import { LoggerService } from '../../../core/logger.service'; import { PreviewableComponent } from '../../previewable-component'; @Component({ @@ -30,7 +29,6 @@ export class MarkdownPreviewComponent implements PreviewableComponent { constructor( private httpClient: HttpClient, - private logger: LoggerService, private domSanitizer: DomSanitizer ) { } @@ -56,7 +54,7 @@ export class MarkdownPreviewComponent implements PreviewableComponent { this.markdownHtml = markdown(markText, { renderer }); } }, - (error) => this.logger.warn(`Failed to fetch markdown with url ${this.documentUrl}: `, error)); + (error) => console.warn(`Failed to fetch markdown with url ${this.documentUrl}: `, error)); } public markdownRendered() { diff --git a/src/frontend/packages/core/src/shared/components/metrics-range-selector/metrics-range-selector.component.ts b/src/frontend/packages/core/src/shared/components/metrics-range-selector/metrics-range-selector.component.ts index 1fab0c74fc..facb1bfe9a 100644 --- a/src/frontend/packages/core/src/shared/components/metrics-range-selector/metrics-range-selector.component.ts +++ b/src/frontend/packages/core/src/shared/components/metrics-range-selector/metrics-range-selector.component.ts @@ -1,5 +1,5 @@ import { Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core'; -import * as moment from 'moment'; +import moment from 'moment'; import { Subscription } from 'rxjs'; import { MetricsAction } from '../../../../../store/src/actions/metrics.actions'; diff --git a/src/frontend/packages/core/src/shared/components/nested-tabs/nested-tabs.component.ts b/src/frontend/packages/core/src/shared/components/nested-tabs/nested-tabs.component.ts index 6f60fdb092..98f10111f7 100644 --- a/src/frontend/packages/core/src/shared/components/nested-tabs/nested-tabs.component.ts +++ b/src/frontend/packages/core/src/shared/components/nested-tabs/nested-tabs.component.ts @@ -1,5 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; -import { TabNavItem } from '../../../../tab-nav.types'; + +import { TabNavItem } from '../../../tab-nav.types'; @Component({ diff --git a/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.html b/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.html index efe500ff02..18aefbc5f3 100644 --- a/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.html +++ b/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.html @@ -1,4 +1,4 @@ - <div class="app-no-content-container" [ngClass]="{'app-no-content-container__menu': mode === 'menu'}"> +<div class="app-no-content-container" [ngClass]="{'app-no-content-container__menu': mode === 'menu'}"> <div *ngIf="toolbarLink?.text" class="app-no-content-container__link" #toolBarLinkElement> <div class="app-no-content-container__link-text">{{toolbarLink.text}}</div> <mat-icon #toolbarRef class="app-no-content-container__link-icon">reply</mat-icon> diff --git a/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.scss b/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.scss index e9ea57ee5e..b46c1ba6fb 100644 --- a/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.scss +++ b/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.scss @@ -26,6 +26,7 @@ } &__link { display: flex; + opacity: 0%; position: absolute; right: 113px; top: 54px; @@ -39,6 +40,10 @@ font-size: 16px; margin-top: 26px; } + &--show { + opacity: 100%; + transition: opacity .6s; + } } &__menu { margin-top: 0; diff --git a/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.ts b/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.ts index a303a443e2..e156f3cf0d 100644 --- a/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.ts +++ b/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.ts @@ -29,13 +29,18 @@ export class NoContentMessageComponent implements AfterViewInit { constructor(private renderer: Renderer2) { } ngAfterViewInit() { - // Align the prompt with the toolbar item - if (this.toolBarLinkElement) { - const elem = document.getElementById(this.toolbarAlign); - if (elem) { - const right = document.body.clientWidth - elem.getBoundingClientRect().right; - this.renderer.setStyle(this.toolBarLinkElement.nativeElement, 'right', right + 'px'); + // Align the prompt with the toolbar item ... + // Note - Only execute after a delay. The final place of the target element may change given visibility of other menu items + // (polling disabled, notification bell). We should come back to this and replace the timeout with a better way of determining readyness + setTimeout(() => { + if (this.toolBarLinkElement) { + const elem = document.getElementById(this.toolbarAlign); + if (elem) { + const right = document.body.clientWidth - elem.getBoundingClientRect().right - 3; + this.renderer.setStyle(this.toolBarLinkElement.nativeElement, 'right', right + 'px'); + this.renderer.addClass(this.toolBarLinkElement.nativeElement, 'app-no-content-container__link--show') + } } - } + }, 500); } } diff --git a/src/frontend/packages/core/src/shared/components/page-header/page-header.component.html b/src/frontend/packages/core/src/shared/components/page-header/page-header.component.html index 387c40e4ba..5f6c34fe7d 100644 --- a/src/frontend/packages/core/src/shared/components/page-header/page-header.component.html +++ b/src/frontend/packages/core/src/shared/components/page-header/page-header.component.html @@ -14,7 +14,8 @@ <ng-container *ngFor="let breadcrumbDef of breadcrumbDefinitions"> <span class="page-header__breadcrumb page-header__breadcrumb-link" *ngIf="breadcrumbDef.routerLink" [routerLink]="breadcrumbDef.routerLink">{{ breadcrumbDef.value }}</span> - <span class="page-header__breadcrumb" *ngIf="!breadcrumbDef.routerLink">{{ breadcrumbDef.value }}</span> + <span class="page-header__breadcrumb" + *ngIf="!breadcrumbDef.routerLink">{{ breadcrumbDef.value }}</span> <mat-icon class="page-header__breadcrumb-separator">chevron_right</mat-icon> </ng-container> </div> @@ -24,61 +25,67 @@ </div> <div class="page-header__filler"></div> <div class="page-header__right"> - <div #extensionRef> - <app-extension-buttons *ngIf="actionsKey" [type]="actionsKey"></app-extension-buttons> - </div> - <div #ref> - <ng-content select=".page-header-right"></ng-content> - </div> - <span class="page-header__divider" *ngIf=" + <div #extensionRef> + <app-extension-buttons *ngIf="actionsKey" [type]="actionsKey"></app-extension-buttons> + </div> + <div #ref> + <ng-content select=".page-header-right"></ng-content> + </div> + <span class="page-header__divider" *ngIf=" (extensionRef.children[0] && extensionRef.children[0].children && extensionRef.children[0].children.length > 0) || (ref.children[0] && ref.children[0] && ref.children[0].children && ref.children[0].children.length > 0) "> - | - </span> - <button *ngIf="showHistory" class="page-header__menu-button" mat-icon-button [matMenuTriggerFor]="history"> - <mat-icon class="page-header__nav-toggle">schedule</mat-icon> - </button> - <mat-menu #history="matMenu" [overlapTrigger]="false" class="page-header__menu"> - <div class="page-header__history"> - <h3 class="page-header__history-title">Recent Activity</h3> - <app-recent-entities mode="menu" class="page-header__history-list" history="true"></app-recent-entities> - </div> - </mat-menu> - <button *ngIf="(events$ | async).length > 0" routerLink="/events" mat-icon-button - class="page-header__notification-button"> - <mat-icon matBadgeColor="warn" [matBadge]="(unreadEventCount$ | async) || ''" - class="page-header__notifications-button"> - notifications - </mat-icon> - </button> - <button id="userMenu" *ngIf="!hideMenu" class="page-header__menu-button page-header__usermenu" - mat-icon-button [matMenuTriggerFor]="menu"> - <app-user-avatar color="header" [allowGravatar]="allowGravatar$ | async" [user]="user$ | async"></app-user-avatar> - </button> - <mat-menu #menu="matMenu" [overlapTrigger]="false" class="page-header__menu"> - <div class="page-header__menu-inner"> - <div class="page-header__username"> - <app-user-avatar [allowGravatar]="allowGravatar$ | async" [user]="user$ | async"></app-user-avatar> - <div class="page-header__username-text"> - <div>{{ username$ | async }}</div> - </div> + | + </span> + <button *ngIf="showHistory" class="page-header__menu-button" mat-icon-button + [matMenuTriggerFor]="history"> + <mat-icon class="page-header__nav-toggle">schedule</mat-icon> + </button> + <mat-menu #history="matMenu" [overlapTrigger]="false" class="page-header__menu"> + <div class="page-header__history"> + <h3 class="page-header__history-title">Recent Activity</h3> + <app-recent-entities mode="menu" class="page-header__history-list" history="true"> + </app-recent-entities> </div> - <div *ngIf="!logoutOnly"> - <button mat-menu-item routerLink="/about"> - <span>About</span> - </button> - <button mat-menu-item routerLink="/user-profile"> - <span>Profile</span> + </mat-menu> + <button *ngIf="(events$ | async).length > 0" routerLink="/events" mat-icon-button + class="page-header__notification-button"> + <mat-icon matBadgeColor="warn" [matBadge]="(unreadEventCount$ | async) || ''" + class="page-header__notifications-button"> + notifications + </mat-icon> + </button> + <button id="userMenu" *ngIf="!hideMenu" class="page-header__menu-button page-header__usermenu" + mat-icon-button [matMenuTriggerFor]="menu"> + <app-user-avatar color="header" [allowGravatar]="allowGravatar$ | async" [user]="user$ | async"> + </app-user-avatar> + </button> + <mat-menu #menu="matMenu" [overlapTrigger]="false" class="page-header__menu"> + <div class="page-header__menu-inner"> + <div class="page-header__username"> + <app-user-avatar [allowGravatar]="allowGravatar$ | async" [user]="user$ | async"></app-user-avatar> + <div class="page-header__username-text"> + <div>{{ username$ | async }}</div> + </div> + </div> + <div *ngIf="!logoutOnly"> + <button mat-menu-item routerLink="/about"> + <span>About</span> + </button> + <button mat-menu-item routerLink="/user-profile"> + <span>Profile</span> + </button> + <button mat-menu-item routerLink="/api-keys" *ngIf="canAPIKeys$ | async"> + <span>API Keys</span> + </button> + <div class="page-header__menu-separator"></div> + </div> + <button mat-menu-item (click)="logout()"> + <span>Logout</span> </button> - <div class="page-header__menu-separator"></div> </div> - <button mat-menu-item (click)="logout()"> - <span>Logout</span> - </button> - </div> - </mat-menu> - </div> + </mat-menu> + </div> </mat-toolbar> </div> <app-page-header-events *ngIf="!hideEndpointErrors" class="header-events" [endpointIds$]="endpointIds$"> diff --git a/src/frontend/packages/core/src/shared/components/page-header/page-header.component.spec.ts b/src/frontend/packages/core/src/shared/components/page-header/page-header.component.spec.ts index bc242042f2..dfa81f3643 100644 --- a/src/frontend/packages/core/src/shared/components/page-header/page-header.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/page-header/page-header.component.spec.ts @@ -3,11 +3,12 @@ import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { StoreModule } from '@ngrx/store'; +import { InternalEventMonitorFactory } from '../../../../../store/src/monitors/internal-event-monitor.factory'; import { appReducers } from '../../../../../store/src/reducers.module'; -import { TabNavService } from '../../../../tab-nav.service'; import { CoreModule } from '../../../core/core.module'; import { MDAppModule } from '../../../core/md.module'; -import { InternalEventMonitorFactory } from '../../../../../store/src/monitors/internal-event-monitor.factory'; +import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; +import { TabNavService } from '../../../tab-nav.service'; import { SharedModule } from '../../shared.module'; import { PageHeaderComponent } from './page-header.component'; import { PageHeaderModule } from './page-header.module'; @@ -28,7 +29,8 @@ describe('PageHeaderComponent', () => { } } }, - TabNavService + TabNavService, + CurrentUserPermissionsService ], imports: [ MDAppModule, diff --git a/src/frontend/packages/core/src/shared/components/page-header/page-header.component.ts b/src/frontend/packages/core/src/shared/components/page-header/page-header.component.ts index 46be7b2b61..2547077ff4 100644 --- a/src/frontend/packages/core/src/shared/components/page-header/page-header.component.ts +++ b/src/frontend/packages/core/src/shared/components/page-header/page-header.component.ts @@ -2,7 +2,7 @@ import { TemplatePortal } from '@angular/cdk/portal'; import { AfterViewInit, Component, Input, OnDestroy, TemplateRef, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { Store } from '@ngrx/store'; -import * as moment from 'moment'; +import moment from 'moment'; import { Observable } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; @@ -15,9 +15,11 @@ import { selectIsMobile } from '../../../../../store/src/selectors/dashboard.sel import { InternalEventSeverity } from '../../../../../store/src/types/internal-events.types'; import { StratosStatus } from '../../../../../store/src/types/shared.types'; import { IFavoriteMetadata, UserFavorite } from '../../../../../store/src/types/user-favorites.types'; -import { TabNavService } from '../../../../tab-nav.service'; +import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service'; +import { StratosCurrentUserPermissions } from '../../../core/permissions/stratos-user-permissions.checker'; import { UserProfileService } from '../../../core/user-profile.service'; import { IPageSideNavTab } from '../../../features/dashboard/page-side-nav/page-side-nav.component'; +import { TabNavService } from '../../../tab-nav.service'; import { GlobalEventService, IGlobalEvent } from '../../global-events.service'; import { selectDashboardState } from './../../../../../store/src/selectors/dashboard.selectors'; import { UserProfileInfo } from './../../../../../store/src/types/user-profile.types'; @@ -29,6 +31,7 @@ import { BREADCRUMB_URL_PARAM, IHeaderBreadcrumb, IHeaderBreadcrumbLink } from ' styleUrls: ['./page-header.component.scss'] }) export class PageHeaderComponent implements OnDestroy, AfterViewInit { + public canAPIKeys$: Observable<boolean>; public breadcrumbDefinitions: IHeaderBreadcrumbLink[] = null; private breadcrumbKey: string; public eventSeverity = InternalEventSeverity; @@ -156,6 +159,7 @@ export class PageHeaderComponent implements OnDestroy, AfterViewInit { eventService: GlobalEventService, private favoritesConfigMapper: FavoritesConfigMapper, private userProfileService: UserProfileService, + private cups: CurrentUserPermissionsService, ) { this.events$ = eventService.events$.pipe( startWith([]) @@ -185,6 +189,8 @@ export class PageHeaderComponent implements OnDestroy, AfterViewInit { this.allowGravatar$ = this.store.select(selectDashboardState).pipe( map(dashboardState => dashboardState.gravatarEnabled) ); + + this.canAPIKeys$ = this.cups.can(StratosCurrentUserPermissions.API_KEYS); } ngOnDestroy() { diff --git a/src/frontend/packages/core/src/shared/components/page-header/show-page-header/show-page-header.component.spec.ts b/src/frontend/packages/core/src/shared/components/page-header/show-page-header/show-page-header.component.spec.ts index e98cc350b6..3214487c5e 100644 --- a/src/frontend/packages/core/src/shared/components/page-header/show-page-header/show-page-header.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/page-header/show-page-header/show-page-header.component.spec.ts @@ -1,8 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { TabNavService } from '../../../../../tab-nav.service'; import { CoreModule } from '../../../../core/core.module'; +import { TabNavService } from '../../../../tab-nav.service'; import { ShowPageHeaderComponent } from './show-page-header.component'; describe('ShowPageHeaderComponent', () => { diff --git a/src/frontend/packages/core/src/shared/components/page-header/show-page-header/show-page-header.component.ts b/src/frontend/packages/core/src/shared/components/page-header/show-page-header/show-page-header.component.ts index bf90d6eccd..506572d214 100644 --- a/src/frontend/packages/core/src/shared/components/page-header/show-page-header/show-page-header.component.ts +++ b/src/frontend/packages/core/src/shared/components/page-header/show-page-header/show-page-header.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; -import { TabNavService } from '../../../../../tab-nav.service'; +import { TabNavService } from '../../../../tab-nav.service'; @Component({ selector: 'app-show-page-header', diff --git a/src/frontend/packages/core/src/shared/components/page-sub-nav/page-sub-nav.component.spec.ts b/src/frontend/packages/core/src/shared/components/page-sub-nav/page-sub-nav.component.spec.ts index ef0a872651..a2b538f67d 100644 --- a/src/frontend/packages/core/src/shared/components/page-sub-nav/page-sub-nav.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/page-sub-nav/page-sub-nav.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { TabNavService } from '../../../../tab-nav.service'; +import { TabNavService } from '../../../tab-nav.service'; import { PageSubNavComponent } from './page-sub-nav.component'; describe('PageSubNavComponent', () => { diff --git a/src/frontend/packages/core/src/shared/components/page-sub-nav/page-sub-nav.component.ts b/src/frontend/packages/core/src/shared/components/page-sub-nav/page-sub-nav.component.ts index 8c66274b02..604a2680e7 100644 --- a/src/frontend/packages/core/src/shared/components/page-sub-nav/page-sub-nav.component.ts +++ b/src/frontend/packages/core/src/shared/components/page-sub-nav/page-sub-nav.component.ts @@ -1,7 +1,7 @@ import { TemplatePortal } from '@angular/cdk/portal'; import { AfterViewInit, Component, Input, OnDestroy, TemplateRef, ViewChild } from '@angular/core'; -import { TabNavService } from '../../../../tab-nav.service'; +import { TabNavService } from '../../../tab-nav.service'; import { IHeaderBreadcrumbLink } from '../page-header/page-header.types'; @Component({ @@ -15,7 +15,7 @@ export class PageSubNavComponent implements AfterViewInit, OnDestroy { set breadcrumbs(crumbs: IHeaderBreadcrumbLink[]) { this.tabNavService.setSubNavBreadcrumbs(crumbs); } - + @ViewChild('subNavTmpl', { static: true }) subNavTmpl: TemplateRef<any>; constructor(private tabNavService: TabNavService) { } diff --git a/src/frontend/packages/core/src/shared/components/recent-entities/recent-entities.component.ts b/src/frontend/packages/core/src/shared/components/recent-entities/recent-entities.component.ts index a44f13294b..ed5adeaed9 100644 --- a/src/frontend/packages/core/src/shared/components/recent-entities/recent-entities.component.ts +++ b/src/frontend/packages/core/src/shared/components/recent-entities/recent-entities.component.ts @@ -1,15 +1,15 @@ import { Component, Input } from '@angular/core'; import { Store } from '@ngrx/store'; -import { entityCatalog } from 'frontend/packages/store/src/entity-catalog/entity-catalog'; -import { - MAX_RECENT_COUNT, -} from 'frontend/packages/store/src/reducers/current-user-roles-reducer/recently-visited.reducer.helpers'; -import * as moment from 'moment'; +import moment from 'moment'; import { Observable, of as observableOf } from 'rxjs'; import { map } from 'rxjs/operators'; import { AppState } from '../../../../../store/src/app-state'; +import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; import { endpointEntityType } from '../../../../../store/src/helpers/stratos-entity-factory'; +import { + MAX_RECENT_COUNT, +} from '../../../../../store/src/reducers/current-user-roles-reducer/recently-visited.reducer.helpers'; import { endpointEntitiesSelector } from '../../../../../store/src/selectors/endpoint.selectors'; import { recentlyVisitedSelector } from '../../../../../store/src/selectors/recently-visitied.selectors'; import { IRecentlyVisitedEntity } from '../../../../../store/src/types/recently-visited.types'; diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts index e09366568c..3af77fa9bf 100644 --- a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts @@ -5,7 +5,6 @@ import { RouterTestingModule } from '@angular/router/testing'; import { createBasicStoreModule } from '@stratosui/store/testing'; import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; -import { LoggerService } from '../../../core/logger.service'; import { MDAppModule } from '../../../core/md.module'; import { SidePanelService } from './../../services/side-panel.service'; import { SidepanelPreviewComponent } from './sidepanel-preview.component'; @@ -17,7 +16,7 @@ describe('SidepanelPreviewComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SidepanelPreviewComponent], - providers: [LoggerService, HttpClient, HttpHandler, SidePanelService], + providers: [HttpClient, HttpHandler, SidePanelService], imports: [ MDAppModule, RouterTestingModule, diff --git a/src/frontend/packages/core/src/shared/components/start-end-date/start-end-date.component.spec.ts b/src/frontend/packages/core/src/shared/components/start-end-date/start-end-date.component.spec.ts index eaa8ba06e3..44339bcddf 100644 --- a/src/frontend/packages/core/src/shared/components/start-end-date/start-end-date.component.spec.ts +++ b/src/frontend/packages/core/src/shared/components/start-end-date/start-end-date.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import * as moment from 'moment'; +import moment from 'moment'; import { SharedModule } from './../../shared.module'; import { StartEndDateComponent } from './start-end-date.component'; diff --git a/src/frontend/packages/core/src/shared/components/start-end-date/start-end-date.component.ts b/src/frontend/packages/core/src/shared/components/start-end-date/start-end-date.component.ts index 46958aa923..a8b3640c81 100644 --- a/src/frontend/packages/core/src/shared/components/start-end-date/start-end-date.component.ts +++ b/src/frontend/packages/core/src/shared/components/start-end-date/start-end-date.component.ts @@ -1,5 +1,5 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; -import * as moment from 'moment'; +import moment from 'moment'; @Component({ selector: 'app-start-end-date', diff --git a/src/frontend/packages/core/src/shared/components/stepper/steppers/steppers.component.ts b/src/frontend/packages/core/src/shared/components/stepper/steppers/steppers.component.ts index f35cb5c7ba..062ef505f2 100644 --- a/src/frontend/packages/core/src/shared/components/stepper/steppers/steppers.component.ts +++ b/src/frontend/packages/core/src/shared/components/stepper/steppers/steppers.component.ts @@ -17,7 +17,6 @@ import { catchError, first, map, switchMap } from 'rxjs/operators'; import { IRouterNavPayload, RouterNav } from '../../../../../../store/src/actions/router.actions'; import { AppState } from '../../../../../../store/src/app-state'; import { getPreviousRoutingState } from '../../../../../../store/src/types/routing.type'; -import { LoggerService } from '../../../../core/logger.service'; import { BASE_REDIRECT_QUERY } from '../stepper.types'; import { SteppersService } from '../steppers.service'; import { StepComponent, StepOnNextResult } from './../step/step.component'; @@ -63,7 +62,6 @@ export class SteppersComponent implements OnInit, AfterContentInit, OnDestroy { private steppersService: SteppersService, private store: Store<AppState>, private snackBar: MatSnackBar, - private logger: LoggerService, private route: ActivatedRoute ) { const previousRoute$ = store.select(getPreviousRoutingState).pipe(first()); @@ -125,7 +123,7 @@ export class SteppersComponent implements OnInit, AfterContentInit, OnDestroy { this.nextSub = obs$.pipe( first(), catchError(err => { - this.logger.warn('Stepper failed: ', err); + console.warn('Stepper failed: ', err); return observableOf({ success: false, message: err || 'Failed', diff --git a/src/frontend/packages/core/src/shared/components/user-avatar/md5.ts b/src/frontend/packages/core/src/shared/components/user-avatar/md5.ts index da9418a3b4..06abf96e4d 100644 --- a/src/frontend/packages/core/src/shared/components/user-avatar/md5.ts +++ b/src/frontend/packages/core/src/shared/components/user-avatar/md5.ts @@ -1,5 +1,6 @@ /*eslint-disable*/ -/*tslint:disabled*/ +/*tslint:disable*/ + export class MD5 { diff --git a/src/frontend/packages/core/src/shared/components/user-avatar/user-avatar.component.ts b/src/frontend/packages/core/src/shared/components/user-avatar/user-avatar.component.ts index f60246cab8..adbca46ed5 100644 --- a/src/frontend/packages/core/src/shared/components/user-avatar/user-avatar.component.ts +++ b/src/frontend/packages/core/src/shared/components/user-avatar/user-avatar.component.ts @@ -1,5 +1,6 @@ import { Component, Input } from '@angular/core'; -import { UserProfileInfo } from 'frontend/packages/store/src/types/user-profile.types'; + +import { UserProfileInfo } from '../../../../../store/src/types/user-profile.types'; import { MD5 } from './md5'; @Component({ diff --git a/src/frontend/packages/core/src/shared/components/user-profile-banner/user-profile-banner.component.ts b/src/frontend/packages/core/src/shared/components/user-profile-banner/user-profile-banner.component.ts index e198687778..a7efc4cf89 100644 --- a/src/frontend/packages/core/src/shared/components/user-profile-banner/user-profile-banner.component.ts +++ b/src/frontend/packages/core/src/shared/components/user-profile-banner/user-profile-banner.component.ts @@ -1,5 +1,6 @@ import { Component, Input } from '@angular/core'; -import { UserProfileInfo } from 'frontend/packages/store/src/types/user-profile.types'; + +import { UserProfileInfo } from '../../../../../store/src/types/user-profile.types'; @Component({ selector: 'app-user-profile-banner', diff --git a/src/frontend/packages/core/src/shared/services/metrics-range-selector-manager.service.ts b/src/frontend/packages/core/src/shared/services/metrics-range-selector-manager.service.ts index bb8ef4dc09..994a287b2f 100644 --- a/src/frontend/packages/core/src/shared/services/metrics-range-selector-manager.service.ts +++ b/src/frontend/packages/core/src/shared/services/metrics-range-selector-manager.service.ts @@ -1,5 +1,5 @@ import { Injectable, NgZone } from '@angular/core'; -import * as moment from 'moment'; +import moment from 'moment'; import { Subject, Subscription } from 'rxjs'; import { debounceTime, takeWhile, tap } from 'rxjs/operators'; diff --git a/src/frontend/packages/core/src/shared/services/metrics-range-selector.service.ts b/src/frontend/packages/core/src/shared/services/metrics-range-selector.service.ts index 9789997fe0..a90943d7ac 100644 --- a/src/frontend/packages/core/src/shared/services/metrics-range-selector.service.ts +++ b/src/frontend/packages/core/src/shared/services/metrics-range-selector.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import * as moment from 'moment'; +import moment from 'moment'; import { MetricQueryConfig, MetricsAction } from '../../../../store/src/actions/metrics.actions'; import { IMetrics } from '../../../../store/src/types/base-metric.types'; @@ -23,6 +23,16 @@ export class MetricsRangeSelectorService { label: 'The past hour', queryType: MetricQueryType.QUERY }, + { + value: '1:day', + label: 'The past day', + queryType: MetricQueryType.QUERY + }, + { + value: '3:day', + label: 'The past 3 days', + queryType: MetricQueryType.QUERY + }, { value: '1:week', label: 'The past week', diff --git a/src/frontend/packages/core/src/shared/services/metrics-range-selector.types.ts b/src/frontend/packages/core/src/shared/services/metrics-range-selector.types.ts index 872a42e0c8..2c2e47fe32 100644 --- a/src/frontend/packages/core/src/shared/services/metrics-range-selector.types.ts +++ b/src/frontend/packages/core/src/shared/services/metrics-range-selector.types.ts @@ -1,4 +1,4 @@ -import * as moment from 'moment'; +import moment from 'moment'; import { MetricQueryType } from '../../../../store/src/types/metric.types'; diff --git a/src/frontend/packages/core/src/shared/shared.module.ts b/src/frontend/packages/core/src/shared/shared.module.ts index 160c4d90a7..cb3c000b5d 100644 --- a/src/frontend/packages/core/src/shared/shared.module.ts +++ b/src/frontend/packages/core/src/shared/shared.module.ts @@ -57,6 +57,7 @@ import { TableCellStatusDirective } from './components/list/list-table/table-cel import { listTableCells } from './components/list/list-table/table-cell/table-cell.component'; import { TableComponent } from './components/list/list-table/table.component'; import { listTableComponents } from './components/list/list-table/table.types'; +import { ApiKeyListConfigService } from './components/list/list-types/apiKeys/apiKey-list-config.service'; import { EndpointCardComponent } from './components/list/list-types/endpoint/endpoint-card/endpoint-card.component'; import { EndpointListHelper } from './components/list/list-types/endpoint/endpoint-list.helpers'; import { EndpointsListConfigService } from './components/list/list-types/endpoint/endpoints-list-config.service'; @@ -319,6 +320,7 @@ import { UserPermissionDirective } from './user-permission.directive'; ListConfig, EndpointListHelper, EndpointsListConfigService, + ApiKeyListConfigService, ConfirmationDialogService, InternalEventMonitorFactory, MetricsRangeSelectorService, diff --git a/src/frontend/packages/core/src/styles.scss b/src/frontend/packages/core/src/styles.scss index c4e58757d0..58d85e671b 100644 --- a/src/frontend/packages/core/src/styles.scss +++ b/src/frontend/packages/core/src/styles.scss @@ -10,6 +10,7 @@ @import '../sass/components/mat-tabs'; @import '../sass/components/mat-table'; @import '../sass/components/mat-button-toggle-group'; +@import '../sass/components/json-schema-form'; body { font-family: Lato, sans-serif; // font-size: 13px; diff --git a/src/frontend/packages/core/tab-nav.service.ts b/src/frontend/packages/core/src/tab-nav.service.ts similarity index 94% rename from src/frontend/packages/core/tab-nav.service.ts rename to src/frontend/packages/core/src/tab-nav.service.ts index d11ed06666..99af87716c 100644 --- a/src/frontend/packages/core/tab-nav.service.ts +++ b/src/frontend/packages/core/src/tab-nav.service.ts @@ -4,8 +4,8 @@ import { NavigationEnd, Router } from '@angular/router'; import { asapScheduler, BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs'; import { filter, map, observeOn, publishReplay, refCount, startWith } from 'rxjs/operators'; -import { IPageSideNavTab } from './src/features/dashboard/page-side-nav/page-side-nav.component'; -import { IHeaderBreadcrumbLink } from './src/shared/components/page-header/page-header.types'; +import { IPageSideNavTab } from './features/dashboard/page-side-nav/page-side-nav.component'; +import { IHeaderBreadcrumbLink } from './shared/components/page-header/page-header.types'; @Injectable() @@ -84,7 +84,7 @@ export class TabNavService { return null; } return activeTab; - } + }; private observeSubject(subject: Subject<any>) { return subject.asObservable().pipe( diff --git a/src/frontend/packages/core/tab-nav.types.ts b/src/frontend/packages/core/src/tab-nav.types.ts similarity index 100% rename from src/frontend/packages/core/tab-nav.types.ts rename to src/frontend/packages/core/src/tab-nav.types.ts diff --git a/src/frontend/packages/core/xsrf.module.ts b/src/frontend/packages/core/src/xsrf.module.ts similarity index 100% rename from src/frontend/packages/core/xsrf.module.ts rename to src/frontend/packages/core/src/xsrf.module.ts diff --git a/src/frontend/packages/core/tslint.json b/src/frontend/packages/core/tslint.json index 1f63906f09..9da788b6cb 100644 --- a/src/frontend/packages/core/tslint.json +++ b/src/frontend/packages/core/tslint.json @@ -1,3 +1,3 @@ { - "extends": "../../../tslint.json" + "extends": "../../../../tslint.json" } diff --git a/src/frontend/packages/store/ng-package.json b/src/frontend/packages/store/ng-package.json index babd6c3d91..d06c8efd6e 100644 --- a/src/frontend/packages/store/ng-package.json +++ b/src/frontend/packages/store/ng-package.json @@ -2,6 +2,18 @@ "$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json", "dest": "../../../../dist/store", "lib": { - "entryFile": "src/public-api.ts" + "entryFile": "src/public-api.ts", + "umdModuleIds": { + "normalizr": "normalizr", + "rxjs-spy/operators": "rxjsSpy_operators", + "rxjs-spy/operators/tag": "rxjsSpy_operators_tag", + "ngrx-store-localstorage": "ngrxStoreLocalstorage", + "@ngrx/store": "ngrx_store", + "@ngrx/effects": "ngrx_effects", + "immer": "immer", + "util": "util", + "@ngrx/router-store": "ngrx_routerStore", + "moment": "moment" + } } -} \ No newline at end of file +} diff --git a/src/frontend/packages/store/src/actions/action-history.actions.ts b/src/frontend/packages/store/src/actions/action-history.actions.ts deleted file mode 100644 index a8a0b1e6e4..0000000000 --- a/src/frontend/packages/store/src/actions/action-history.actions.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Action } from '@ngrx/store'; - -export const ActionHistoryActions = { - DUMP: '[Action History] Dump', -}; - -export class ActionHistoryDump implements Action { - type = ActionHistoryActions.DUMP; -} diff --git a/src/frontend/packages/store/src/actions/apiKey.actions.ts b/src/frontend/packages/store/src/actions/apiKey.actions.ts new file mode 100644 index 0000000000..4b10e34bef --- /dev/null +++ b/src/frontend/packages/store/src/actions/apiKey.actions.ts @@ -0,0 +1,44 @@ +import { apiKeyEntityType, STRATOS_ENDPOINT_TYPE, stratosEntityFactory } from '../helpers/stratos-entity-factory'; +import { PaginatedAction, PaginationParam } from '../types/pagination.types'; +import { EntityRequestAction } from '../types/request.types'; + +export const API_KEY_ADD = '[API Key] Add API Key' +export const API_KEY_DELETE = '[API Key] Delete API Key' +export const API_KEY_GET_ALL = '[API Key] Get All API Key' + +abstract class BaseApiKeyAction implements EntityRequestAction { + entityType = apiKeyEntityType; + endpointType = STRATOS_ENDPOINT_TYPE; + entity = [stratosEntityFactory(apiKeyEntityType)] + constructor(public type: string) { } +} + +interface PaginationApiKeyAction extends PaginatedAction, EntityRequestAction { + flattenPagination: boolean; +} +interface SingleApiKeyAction extends EntityRequestAction { + guid: string; +} + +export class AddApiKey extends BaseApiKeyAction implements SingleApiKeyAction { + constructor(public comment: string) { + super(API_KEY_ADD); + } + guid = 'ADD' +} + +export class DeleteApiKey extends BaseApiKeyAction implements SingleApiKeyAction { + constructor(public guid: string) { + super(API_KEY_DELETE); + } +} + +export class GetAllApiKeys extends BaseApiKeyAction implements PaginationApiKeyAction { + constructor() { + super(API_KEY_GET_ALL); + this.paginationKey = 'CURRENT_USERS'; + } + flattenPagination = true; + paginationKey: string; + initialParams: PaginationParam +} \ No newline at end of file diff --git a/src/frontend/packages/store/src/actions/internal-events.actions.ts b/src/frontend/packages/store/src/actions/internal-events.actions.ts index 845a0adca4..d644492891 100644 --- a/src/frontend/packages/store/src/actions/internal-events.actions.ts +++ b/src/frontend/packages/store/src/actions/internal-events.actions.ts @@ -1,5 +1,5 @@ import { Action } from '@ngrx/store'; -import * as moment from 'moment'; +import moment from 'moment'; import { CLEAR_ENDPOINT_ERROR_EVENTS, diff --git a/src/frontend/packages/store/src/actions/log.actions.ts b/src/frontend/packages/store/src/actions/log.actions.ts deleted file mode 100644 index dd35c9e6d1..0000000000 --- a/src/frontend/packages/store/src/actions/log.actions.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Action } from '@ngrx/store'; - -export enum LogLevel { - DEBUG, - INFO, - WARN, - ERROR -} - -export const LoggerActionTypes = { - DEBUG: '[LOG] Debug', - INFO: '[LOG] Info', - WARN: '[LOG] Warn', - ERROR: '[LOG] Error', -}; - -export class LoggerAction implements Action { - constructor( - public logLevel: LogLevel = LogLevel.INFO, - public message: string, - public type: string - ) { - } -} - -export class LoggerDebugAction implements LoggerAction { - constructor( - public message: string - ) { - } - logLevel = LogLevel.DEBUG; - type = LoggerActionTypes.DEBUG; -} - -export class LoggerInfoAction implements LoggerAction { - constructor( - public message: string - ) { - } - logLevel = LogLevel.INFO; - type = LoggerActionTypes.INFO; -} - -export class LoggerWarnAction implements LoggerAction { - constructor( - public message: string - ) { - } - logLevel = LogLevel.WARN; - type = LoggerActionTypes.WARN; -} - -export class LoggerErrorAction implements LoggerAction { - constructor( - public message: string - ) { - } - logLevel = LogLevel.ERROR; - type = LoggerActionTypes.ERROR; -} diff --git a/src/frontend/packages/store/src/actions/router.actions.ts b/src/frontend/packages/store/src/actions/router.actions.ts index e824ec55b5..5a20c3bc2d 100644 --- a/src/frontend/packages/store/src/actions/router.actions.ts +++ b/src/frontend/packages/store/src/actions/router.actions.ts @@ -2,7 +2,6 @@ import { NavigationExtras } from '@angular/router'; import { Action } from '@ngrx/store'; import { RouterRedirect } from '../reducers/routing.reducer'; -import { LoggerAction, LogLevel } from './log.actions'; export const RouterActions = { GO: '[Router] Go To', @@ -16,8 +15,7 @@ export interface IRouterNavPayload { query?: RouterQueryParams; extras?: NavigationExtras; } -export class RouterNav implements Action, LoggerAction { - public logLevel: LogLevel.INFO; +export class RouterNav implements Action { public message: string; type = RouterActions.GO; constructor(public payload: IRouterNavPayload, public redirect?: RouterRedirect) { diff --git a/src/frontend/packages/store/src/actions/system.actions.ts b/src/frontend/packages/store/src/actions/system.actions.ts index 29c9f9410d..93f178d758 100644 --- a/src/frontend/packages/store/src/actions/system.actions.ts +++ b/src/frontend/packages/store/src/actions/system.actions.ts @@ -3,22 +3,24 @@ import { Action } from '@ngrx/store'; import { STRATOS_ENDPOINT_TYPE, stratosEntityFactory, systemInfoEntityType } from '../helpers/stratos-entity-factory'; import { EntityRequestAction } from '../types/request.types'; import { SystemInfo } from '../types/system.types'; -import { BaseEndpointAction, GetAllEndpoints } from './endpoint.actions'; +import { GetAllEndpoints, GetEndpoint } from './endpoint.actions'; export const GET_SYSTEM_INFO = '[System] Get info'; export const GET_SYSTEM_INFO_SUCCESS = '[System] Get info success'; export const GET_SYSTEM_INFO_FAILED = '[System] Get info failed'; +export type GetSystemInfoAssociatedAction = GetEndpoint | GetAllEndpoints; + export class GetSystemInfo implements EntityRequestAction { static guid = 'info'; guid = GetSystemInfo.guid; - constructor(public login = false, public associatedAction?: BaseEndpointAction) { + constructor(public login = false, public associatedAction?: GetSystemInfoAssociatedAction) { if (!this.associatedAction) { this.associatedAction = new GetAllEndpoints(login); } } schemaKey = systemInfoEntityType; - entity = [stratosEntityFactory(systemInfoEntityType)] + entity = [stratosEntityFactory(systemInfoEntityType)]; entityType = systemInfoEntityType; endpointType = STRATOS_ENDPOINT_TYPE; type = GET_SYSTEM_INFO; @@ -26,10 +28,10 @@ export class GetSystemInfo implements EntityRequestAction { GET_SYSTEM_INFO, GET_SYSTEM_INFO_SUCCESS, GET_SYSTEM_INFO_FAILED - ] + ]; } export class GetSystemSuccess implements Action { - constructor(public payload: SystemInfo, public login = false, public associatedAction: BaseEndpointAction) { } + constructor(public payload: SystemInfo, public login = false, public associatedAction: GetSystemInfoAssociatedAction) { } type = GET_SYSTEM_INFO_SUCCESS; } diff --git a/src/frontend/packages/store/src/actions/user-favourites.actions.ts b/src/frontend/packages/store/src/actions/user-favourites.actions.ts index c4fcfdf744..029a6b3f71 100644 --- a/src/frontend/packages/store/src/actions/user-favourites.actions.ts +++ b/src/frontend/packages/store/src/actions/user-favourites.actions.ts @@ -12,7 +12,7 @@ abstract class BaseUserFavoritesAction implements EntityRequestAction { public type: string; public url = '/user-favorites'; - public entity = [stratosEntityFactory(userFavouritesEntityType)] + public entity = [stratosEntityFactory(userFavouritesEntityType)]; public entityType = userFavouritesEntityType; public endpointType = STRATOS_ENDPOINT_TYPE; } @@ -51,7 +51,7 @@ abstract class BaseSuccessFavouriteAction extends BaseSingleUserFavouritesAction } } -// --------- +// --------- export class GetUserFavoritesAction extends BaseMultipleUserFavouritesAction { static PAGINATION_KEY = 'user_favourites'; @@ -85,7 +85,7 @@ export class GetUserFavoritesFailedAction extends BaseMultipleUserFavouritesActi } } -// --------- +// --------- export class RemoveUserFavoriteAction extends BaseSingleUserFavouritesAction { static ACTION_TYPE = '[Favorite] Remove Favorite'; @@ -100,11 +100,11 @@ export class RemoveUserFavoriteAction extends BaseSingleUserFavouritesAction { export class RemoveUserFavoriteSuccessAction extends BaseSuccessFavouriteAction { static ACTION_TYPE = '[Favorite] Remove Favorite Success'; constructor(favorite: UserFavorite<IFavoriteMetadata>) { - super(RemoveUserFavoriteSuccessAction.ACTION_TYPE, favorite) + super(RemoveUserFavoriteSuccessAction.ACTION_TYPE, favorite); } } -// --------- +// --------- export class SaveUserFavoriteAction extends BaseSingleUserFavouritesAction { static ACTION_TYPE = '[Favorite] Save Favorite'; @@ -120,7 +120,7 @@ export class SaveUserFavoriteSuccessAction extends BaseSuccessFavouriteAction { } } -// --------- +// --------- export class ToggleUserFavoriteAction extends BaseSingleUserFavouritesAction { static ACTION_TYPE = '[Favorite] Toggle Favorite'; diff --git a/src/frontend/packages/store/src/apiKey.types.ts b/src/frontend/packages/store/src/apiKey.types.ts new file mode 100644 index 0000000000..35ac4736b7 --- /dev/null +++ b/src/frontend/packages/store/src/apiKey.types.ts @@ -0,0 +1,7 @@ +export interface ApiKey { + comment: string; + guid: string; + last_used: string; + secret: string; + user_guid: string; +} \ No newline at end of file diff --git a/src/frontend/packages/store/src/app-state.ts b/src/frontend/packages/store/src/app-state.ts index 6974c9c510..8c31bdf705 100644 --- a/src/frontend/packages/store/src/app-state.ts +++ b/src/frontend/packages/store/src/app-state.ts @@ -1,4 +1,3 @@ -import { ActionHistoryState } from './reducers/action-history-reducer'; import { RequestInfoState } from './reducers/api-request-reducer/types'; import { AuthState } from './reducers/auth.reducer'; import { DashboardState } from './reducers/dashboard-reducer'; @@ -26,7 +25,6 @@ export type BaseRequestDataState = Record<string, IRequestEntityTypeState<any>>; export abstract class AppState< T extends Record<string, any> = any > { - actionHistory: ActionHistoryState; auth: AuthState; uaaSetup: UAASetupState; endpoints: EndpointState; diff --git a/src/frontend/packages/store/src/effects/action-history.effects.ts b/src/frontend/packages/store/src/effects/action-history.effects.ts deleted file mode 100644 index c96a9bbaac..0000000000 --- a/src/frontend/packages/store/src/effects/action-history.effects.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; -import { Store } from '@ngrx/store'; -import { map, take } from 'rxjs/operators'; - -import { ActionHistoryActions, ActionHistoryDump } from '../actions/action-history.actions'; -import { InternalAppState } from '../app-state'; - - -@Injectable() -export class ActionHistoryEffect { - - constructor( - private actions$: Actions, - private store: Store<InternalAppState>, - ) { } - - @Effect({ dispatch: false }) dumpActionHistory$ = this.actions$.pipe( - ofType<ActionHistoryDump>(ActionHistoryActions.DUMP), - map(() => { - this.store.select('actionHistory').pipe( - take(1)) - .subscribe(); - })); -} - diff --git a/src/frontend/packages/store/src/effects/apiKey.effects.ts b/src/frontend/packages/store/src/effects/apiKey.effects.ts new file mode 100644 index 0000000000..d4dc3a033b --- /dev/null +++ b/src/frontend/packages/store/src/effects/apiKey.effects.ts @@ -0,0 +1,132 @@ +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Store } from '@ngrx/store'; +import { catchError, mergeMap, switchMap } from 'rxjs/operators'; + +import { + AddApiKey, + API_KEY_ADD, + API_KEY_DELETE, + API_KEY_GET_ALL, + DeleteApiKey, + GetAllApiKeys, +} from '../actions/apiKey.actions'; +import { ApiKey } from '../apiKey.types'; +import { InternalAppState } from '../app-state'; +import { BrowserStandardEncoder } from '../browser-encoder'; +import { entityCatalog } from '../entity-catalog/entity-catalog'; +import { proxyAPIVersion } from '../jetstream'; +import { NormalizedResponse } from '../types/api.types'; +import { StartRequestAction, WrapperRequestActionFailed, WrapperRequestActionSuccess } from '../types/request.types'; + +const apiKeyUrlPath = `/pp/${proxyAPIVersion}/api_keys`; + +@Injectable() +export class ApiKeyEffect { + + constructor( + private http: HttpClient, + private actions$: Actions, + private store: Store<InternalAppState>, + ) { + } + + @Effect() add = this.actions$.pipe( + ofType<AddApiKey>(API_KEY_ADD), + mergeMap(action => { + const actionType = 'create'; + this.store.dispatch(new StartRequestAction(action, actionType)) + + return this.http.post<ApiKey>(apiKeyUrlPath, new HttpParams({ + encoder: new BrowserStandardEncoder(), + fromObject: { + comment: action.comment + } + })).pipe( + switchMap(newApiKey => { + const guid = action.entity[0].getId(newApiKey); + const entityKey = entityCatalog.getEntityKey(action); + const response: NormalizedResponse<ApiKey> = { + entities: { + [entityKey]: { + [guid]: newApiKey + } + }, + result: [guid] + } + this.store.dispatch(new WrapperRequestActionSuccess(response, action, actionType)); + return []; + }), + catchError(err => { + this.store.dispatch(new WrapperRequestActionFailed(this.convertErrorToString(err), action, actionType)); + return []; + }) + ); + }) + ); + + @Effect() delete = this.actions$.pipe( + ofType<DeleteApiKey>(API_KEY_DELETE), + mergeMap(action => { + const actionType = 'delete'; + this.store.dispatch(new StartRequestAction(action, actionType)) + + return this.http.delete(apiKeyUrlPath, { + params: new HttpParams({ + encoder: new BrowserStandardEncoder(), + fromObject: { + guid: action.guid + } + }) + }).pipe( + switchMap(() => { + this.store.dispatch(new WrapperRequestActionSuccess(null, action, actionType)); + return []; + }), + catchError(err => { + this.store.dispatch(new WrapperRequestActionFailed(this.convertErrorToString(err), action, actionType)); + return []; + }) + ); + }) + ); + + @Effect() getAll = this.actions$.pipe( + ofType<GetAllApiKeys>(API_KEY_GET_ALL), + mergeMap(action => { + const actionType = 'fetch'; + this.store.dispatch(new StartRequestAction(action, actionType)) + return this.http.get(apiKeyUrlPath).pipe( + switchMap((res: ApiKey[]) => { + const entityKey = entityCatalog.getEntityKey(action); + const response: NormalizedResponse<ApiKey> = { + entities: { + [entityKey]: { + } + }, + result: [] + } + + res.forEach(apiKey => { + const guid = action.entity[0].getId(apiKey); + response.entities[entityKey][guid] = apiKey; + response.result.push(guid); + }); + + this.store.dispatch(new WrapperRequestActionSuccess(response, action, actionType)); + return []; + }), + catchError(err => { + this.store.dispatch(new WrapperRequestActionFailed(this.convertErrorToString(err), action, actionType)); + return []; + }) + ); + }) + ); + + private convertErrorToString(err: any): string { + // We should look into beefing this up / combining with generic error handling + return err && err.error ? err.error : 'Failed API Key action'; + } +} \ No newline at end of file diff --git a/src/frontend/packages/store/src/effects/endpoint.effects.ts b/src/frontend/packages/store/src/effects/endpoint.effects.ts index 6925c05e5c..36d73ee967 100644 --- a/src/frontend/packages/store/src/effects/endpoint.effects.ts +++ b/src/frontend/packages/store/src/effects/endpoint.effects.ts @@ -89,11 +89,13 @@ export class EndpointsEffect { }); }); + const isLogin = associatedAction.type === GET_ENDPOINTS ? (associatedAction as GetAllEndpoints).login : false; + // Order is important. Need to ensure data is written (none cf action success) before we notify everything is loaded // (endpoint success) return [ new WrapperRequestActionSuccess(mappedData, associatedAction, 'fetch'), - new GetAllEndpointsSuccess(mappedData, associatedAction['login']), + new GetAllEndpointsSuccess(mappedData, isLogin), ]; })); diff --git a/src/frontend/packages/store/src/entity-catalog/action-dispatcher/action-dispatcher.spec.ts b/src/frontend/packages/store/src/entity-catalog/action-dispatcher/action-dispatcher.spec.ts index 88cc83b37a..4be9e84ff6 100644 --- a/src/frontend/packages/store/src/entity-catalog/action-dispatcher/action-dispatcher.spec.ts +++ b/src/frontend/packages/store/src/entity-catalog/action-dispatcher/action-dispatcher.spec.ts @@ -12,7 +12,7 @@ import { EntityCatalogHelpers } from '../entity-catalog.helper'; describe('ActionDispatcher', () => { it('should not dispatch unknown action', () => { - const actionBuilders = ActionBuilderConfigMapper.getActionBuilders({}, null, null, null) + const actionBuilders = ActionBuilderConfigMapper.getActionBuilders({}, null, null, null); const actionOrchestrator = new ActionOrchestrator('Empty', actionBuilders); const store = { @@ -30,7 +30,7 @@ describe('ActionDispatcher', () => { const entityActionDispatcher = EntityCatalogEntityStoreHelpers.getActionDispatchers( store, actionBuilders - ) + ); expect(entityActionDispatcher.get).toBeUndefined(); }); @@ -38,14 +38,20 @@ describe('ActionDispatcher', () => { const endpointType = 'endpointType'; it('should dispatch actions', () => { - const endpointGuid = 'endpoint Guid'; - const guid = 'guid'; - const paginationKey = 'asd'; - - const getAction = { type: 'get action', entityType, endpointType, guid, endpointGuid }; - const getMultipleAction = { type: 'getMultiple action', entityType, endpointType, endpointGuid, paginationKey }; - const customGetAction = { type: 'custom Action', entityType, endpointType, guid } - const customGetMultipleAction = { type: 'custom MultipleAction', entityType, endpointType, paginationKey } + const testEndpointGuid = 'endpoint Guid'; + const testGuid = 'guid'; + const testPaginationKey = 'asd'; + + const getAction = { type: 'get action', entityType, endpointType, guid: testGuid, endpointGuid: testEndpointGuid }; + const getMultipleAction = { + type: 'getMultiple action', + entityType, + endpointType, + endpointGuid: testEndpointGuid, + paginationKey: testPaginationKey + }; + const customGetAction = { type: 'custom Action', entityType, endpointType, guid: testGuid }; + const customGetMultipleAction = { type: 'custom MultipleAction', entityType, endpointType, paginationKey: testPaginationKey }; const builders: OrchestratedActionBuilders = { get: (guid: string, endpointGuid: string, extraArgs?: any) => ({ @@ -66,8 +72,8 @@ describe('ActionDispatcher', () => { ...customGetMultipleAction, paginationKey }), - } - const actionBuilders = ActionBuilderConfigMapper.getActionBuilders(builders, null, null, null) + }; + const actionBuilders = ActionBuilderConfigMapper.getActionBuilders(builders, null, null, null); const actionOrchestrator = new ActionOrchestrator(entityType, actionBuilders); const entityStore = { @@ -85,12 +91,12 @@ describe('ActionDispatcher', () => { const entityActionDispatcher = EntityCatalogEntityStoreHelpers.getActionDispatchers( entityStore, actionBuilders - ) + ); const store = { - dispatch: (action: Action) => { console.log(action) }, + dispatch: (action: Action) => { console.log(action); }, select: (...args: any[]) => of(null) - } as Store<AppState<any>> + } as Store<AppState<any>>; EntityCatalogHelpers.SetEntityCatalogHelper({ store, @@ -102,27 +108,27 @@ describe('ActionDispatcher', () => { currentPageState$: {} }) } as unknown as PaginationMonitorFactory - } as EntityCatalogHelper) + } as EntityCatalogHelper); const storeDispatchSpy = spyOn(store, 'dispatch'); expect(entityActionDispatcher.get).toBeDefined(); - expect(entityActionDispatcher.get(guid, endpointGuid)).toBeDefined(); - expect(storeDispatchSpy).toHaveBeenCalledWith(getAction) + expect(entityActionDispatcher.get(testGuid, testEndpointGuid)).toBeDefined(); + expect(storeDispatchSpy).toHaveBeenCalledWith(getAction); storeDispatchSpy.calls.reset(); expect(entityActionDispatcher.custom).toBeDefined(); - expect(entityActionDispatcher.custom(guid)).toBeDefined(); - expect(storeDispatchSpy).toHaveBeenCalledWith(customGetAction) + expect(entityActionDispatcher.custom(testGuid)).toBeDefined(); + expect(storeDispatchSpy).toHaveBeenCalledWith(customGetAction); storeDispatchSpy.calls.reset(); expect(entityActionDispatcher.getMultiple).toBeDefined(); - expect(entityActionDispatcher.getMultiple(endpointGuid, paginationKey)).toBeDefined(); - expect(storeDispatchSpy).toHaveBeenCalledWith(getMultipleAction) + expect(entityActionDispatcher.getMultiple(testEndpointGuid, testPaginationKey)).toBeDefined(); + expect(storeDispatchSpy).toHaveBeenCalledWith(getMultipleAction); storeDispatchSpy.calls.reset(); expect(entityActionDispatcher.customMultipleAction).toBeDefined(); - expect(entityActionDispatcher.customMultipleAction(paginationKey)).toBeDefined(); - expect(storeDispatchSpy).toHaveBeenCalledWith(customGetMultipleAction) + expect(entityActionDispatcher.customMultipleAction(testPaginationKey)).toBeDefined(); + expect(storeDispatchSpy).toHaveBeenCalledWith(customGetMultipleAction); storeDispatchSpy.calls.reset(); }); diff --git a/src/frontend/packages/store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity.ts b/src/frontend/packages/store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity.ts index 61bbc7883e..cbe9b83830 100644 --- a/src/frontend/packages/store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity.ts +++ b/src/frontend/packages/store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity.ts @@ -12,6 +12,7 @@ import { PaginationPageIteratorConfig, } from '../../entity-request-pipeline/pagination-request-base-handlers/pagination-iterator.pipe'; import { EntityPipelineEntity, stratosEndpointGuidKey } from '../../entity-request-pipeline/pipeline.types'; +import { EndpointAuthTypeConfig } from '../../extension-types'; import { EntitySchema } from '../../helpers/entity-schema'; import { endpointEntityType, STRATOS_ENDPOINT_TYPE, stratosEntityFactory } from '../../helpers/stratos-entity-factory'; import { EndpointModel } from '../../types/endpoint.types'; @@ -352,5 +353,20 @@ export class StratosCatalogEndpointEntity extends StratosBaseCatalogEntity<IEndp } }); } + + public setListComponent(component: any) { + // Can only be set once + if (!this.definition.listDetailsComponent) { + (this.definition as any).listDetailsComponent = component; + } + } + + public setAuthTypes(authTypes: EndpointAuthTypeConfig[]) { + // Can only be set once + if (!this.definition.authTypes || this.definition.authTypes.length === 0) { + (this.definition as any).authTypes = authTypes; + } + } + } diff --git a/src/frontend/packages/store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity.types.ts b/src/frontend/packages/store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity.types.ts index ac6e5e3ae3..ba490b0d57 100644 --- a/src/frontend/packages/store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity.types.ts +++ b/src/frontend/packages/store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity.types.ts @@ -18,11 +18,12 @@ export interface CoreEntityCatalogEntityStore<Y, ABC extends OrchestratedActionB entityId: string, params?: { schemaKey?: string, - startWithNull?: boolean + startWithNull?: boolean, } ) => EntityMonitor<Y>; /** - * Return a collection of observables for the given entity id. Subscribing to core observables (like entityObs$) will fetch the entity if missing + * Return a collection of observables for the given entity id. Subscribing to core observables (like entityObs$) will fetch the entity if + * missing */ getEntityService: ( ...args: Parameters<ABC['get']> @@ -34,7 +35,8 @@ export interface CoreEntityCatalogEntityStore<Y, ABC extends OrchestratedActionB ...args: Parameters<ABC['getMultiple']> ) => PaginationMonitor<Y>; /** - * Return a collection of observables for the given collection of entities. Subscribing to core (like entities$) will fetch the entity if missing + * Return a collection of observables for the given collection of entities. Subscribing to core (like entities$) will fetch the entity if + * missing */ getPaginationService: ( ...args: Parameters<ABC['getMultiple']> @@ -53,16 +55,25 @@ type PaginatedActionBuildersWithNevers<ABC extends OrchestratedActionBuilders> = /** * Filter out builders that don't return pagination actions from ABC */ -type PaginatedActionBuilders<ABC extends OrchestratedActionBuilders> = Omit<PaginatedActionBuildersWithNevers<ABC>, NeverKeys<PaginatedActionBuildersWithNevers<ABC>>> +type PaginatedActionBuilders<ABC extends OrchestratedActionBuilders> = Omit< + PaginatedActionBuildersWithNevers<ABC>, + NeverKeys<PaginatedActionBuildersWithNevers<ABC>> +>; /** * Mark builders that return a pagination action as `never` */ -type NonPaginatedActionBuildersWithNevers<ABC extends OrchestratedActionBuilders> = FilteredByNotReturnType<CustomBuilders<ABC>, PaginatedAction>; +type NonPaginatedActionBuildersWithNevers<ABC extends OrchestratedActionBuilders> = FilteredByNotReturnType< + CustomBuilders<ABC>, + PaginatedAction +>; /** * Filter out builders that return pagination actions from ABC */ -type NonPaginatedActionBuilders<ABC extends OrchestratedActionBuilders> = Omit<NonPaginatedActionBuildersWithNevers<ABC>, NeverKeys<NonPaginatedActionBuildersWithNevers<ABC>>> +type NonPaginatedActionBuilders<ABC extends OrchestratedActionBuilders> = Omit< + NonPaginatedActionBuildersWithNevers<ABC>, + NeverKeys<NonPaginatedActionBuildersWithNevers<ABC>> +>; /** @@ -90,13 +101,13 @@ type EntityCatalogEntityStoreSingles<Y, ABC extends OrchestratedActionBuilders, ) => EntityMonitor<Y>; getEntityService: ( ...args: Parameters<SABC[K]> - ) => EntityService<Y> + ) => EntityService<Y>; } }; export type CustomEntityCatalogEntityStore<Y, ABC extends OrchestratedActionBuilders> = EntityCatalogEntityStoreCollections<Y, ABC, PaginatedActionBuilders<ABC>> & - EntityCatalogEntityStoreSingles<Y, ABC, NonPaginatedActionBuilders<ABC>> + EntityCatalogEntityStoreSingles<Y, ABC, NonPaginatedActionBuilders<ABC>>; /** @@ -107,7 +118,7 @@ export type CustomEntityCatalogEntityStore<Y, ABC extends OrchestratedActionBuil */ export type EntityCatalogEntityStore<Y, ABC extends OrchestratedActionBuilders> = CoreEntityCatalogEntityStore<Y, ABC> & - CustomEntityCatalogEntityStore<Y, ABC> + CustomEntityCatalogEntityStore<Y, ABC>; diff --git a/src/frontend/packages/store/src/entity-catalog/entity-catalog.helper.ts b/src/frontend/packages/store/src/entity-catalog/entity-catalog.helper.ts index c4fc7b45f8..f52c07063c 100644 --- a/src/frontend/packages/store/src/entity-catalog/entity-catalog.helper.ts +++ b/src/frontend/packages/store/src/entity-catalog/entity-catalog.helper.ts @@ -2,6 +2,8 @@ import { EntityCatalogHelper } from './entity-catalog-entity/entity-catalog.serv export abstract class EntityCatalogHelpers { static readonly endpointType = 'endpoint'; + private static Instance: EntityCatalogHelper; + static buildEntityKey(entityType: string, endpointType: string): string { if (!entityType) { return endpointType; @@ -9,11 +11,10 @@ export abstract class EntityCatalogHelpers { if (!endpointType) { return entityType; } - // Camelcased to make it work better with the store. + // Camel cased to make it work better with the store. return `${endpointType}${entityType.charAt(0).toUpperCase() + entityType.slice(1)}`; } - private static Instance: EntityCatalogHelper; static SetEntityCatalogHelper(ecf: EntityCatalogHelper) { this.Instance = ecf; } diff --git a/src/frontend/packages/store/src/entity-catalog/entity-catalog.spec.ts b/src/frontend/packages/store/src/entity-catalog/entity-catalog.spec.ts index 7b2a0f4c5a..449bc49cc7 100644 --- a/src/frontend/packages/store/src/entity-catalog/entity-catalog.spec.ts +++ b/src/frontend/packages/store/src/entity-catalog/entity-catalog.spec.ts @@ -1,13 +1,10 @@ -import { - EndpointListDetailsComponent, -} from '../../../core/src/shared/components/list/list-types/endpoint/endpoint-list.helpers'; import { EntitySchema } from '../helpers/entity-schema'; import { endpointEntityType, stratosEntityFactory } from '../helpers/stratos-entity-factory'; import { TestEntityCatalog } from './entity-catalog'; import { StratosCatalogEndpointEntity, StratosCatalogEntity } from './entity-catalog-entity/entity-catalog-entity'; import { EntityCatalogSchemas, IStratosEndpointDefinition } from './entity-catalog.types'; -fdescribe('EntityCatalogService', () => { +describe('EntityCatalogService', () => { let entityCatalog: TestEntityCatalog; function getEndpointDefinition() { return { @@ -18,7 +15,7 @@ fdescribe('EntityCatalogService', () => { iconFont: 'stratos-icons', logoUrl: '/core/assets/endpoint-icons/cloudfoundry.png', authTypes: [], - listDetailsComponent: EndpointListDetailsComponent, + listDetailsComponent: 'Test Component', } as IStratosEndpointDefinition; } function getDefaultSchema() { @@ -136,7 +133,7 @@ fdescribe('EntityCatalogService', () => { ...subtypeDefinition, icon: 'cloud_foundry', iconFont: 'stratos-icons', - listDetailsComponent: EndpointListDetailsComponent, + listDetailsComponent: 'Test Component', schema: { default: stratosEntityFactory(endpointEntityType) }, diff --git a/src/frontend/packages/store/src/entity-catalog/entity-catalog.ts b/src/frontend/packages/store/src/entity-catalog/entity-catalog.ts index 224f05b471..3144ee57b4 100644 --- a/src/frontend/packages/store/src/entity-catalog/entity-catalog.ts +++ b/src/frontend/packages/store/src/entity-catalog/entity-catalog.ts @@ -131,7 +131,6 @@ class EntityCatalog { entityType?: string, subType?: string ): StratosBaseCatalogEntity<T, Y, AB, AB> { - /* tslint:enable:max-line-length */ const config = this.getConfig(endpointTypeOrConfig, entityType, subType); const entityOfType = this.getEntityOfType(config.entityType, config.endpointType); if (entityOfType && subType) { diff --git a/src/frontend/packages/store/src/entity-request-pipeline/entity-pagination-request-pipeline.ts b/src/frontend/packages/store/src/entity-request-pipeline/entity-pagination-request-pipeline.ts index 509b5f661d..c2bb4371e1 100644 --- a/src/frontend/packages/store/src/entity-request-pipeline/entity-pagination-request-pipeline.ts +++ b/src/frontend/packages/store/src/entity-request-pipeline/entity-pagination-request-pipeline.ts @@ -10,7 +10,6 @@ import { StratosCatalogEntity, } from '../entity-catalog/entity-catalog-entity/entity-catalog-entity'; import { IStratosEntityDefinition } from '../entity-catalog/entity-catalog.types'; -import { PaginationFlattenerConfig } from '../helpers/paginated-request-helpers'; import { selectPaginationState } from '../selectors/pagination.selectors'; import { PaginatedAction, PaginationEntityState } from '../types/pagination.types'; import { @@ -69,7 +68,6 @@ function getRequestObservable( } export interface PaginatedRequestPipelineConfig<T extends AppState = InternalAppState> extends BasePipelineConfig<T> { action: PaginatedAction; - pageFlattenerConfig: PaginationFlattenerConfig; } export const basePaginatedRequestPipeline: EntityRequestPipeline = ( store: Store<AppState>, diff --git a/src/frontend/packages/store/src/entity-service.ts b/src/frontend/packages/store/src/entity-service.ts index 38f2f1c911..d64705220a 100644 --- a/src/frontend/packages/store/src/entity-service.ts +++ b/src/frontend/packages/store/src/entity-service.ts @@ -35,10 +35,10 @@ const dispatcherFactory = <T>( const updatedAction = { ...action, updatingKey - } + }; // Do we have a fetch handler defined by the endpoint/entity? - const entityFetchHandler: EntityFetchHandler<T> = catalogEntity.getEntityFetchHandler() + const entityFetchHandler: EntityFetchHandler<T> = catalogEntity.getEntityFetchHandler(); const fetchHandler = entityFetchHandler ? entityFetchHandler(store, updatedAction) : (entity: T) => store.dispatch(updatedAction); @@ -46,8 +46,8 @@ const dispatcherFactory = <T>( // Fetch handler requires the entity, this may be missing or stale to update if required return fetchEntity ? (entity: T) => { // Entity may be null or stale - store.select(selectEntity<T>(catalogEntity.entityKey, action.guid)).pipe(first()).subscribe(entity => fetchHandler(entity)) - fetchHandler(entity) + store.select(selectEntity<T>(catalogEntity.entityKey, action.guid)).pipe(first()).subscribe(storeEntity => fetchHandler(storeEntity)); + fetchHandler(entity); } : fetchHandler; }; @@ -135,7 +135,7 @@ export class EntityService<T = any> { first(), switchMap(() => cleanEntityInfo$) ); - } + }; private getCleanEntityInfoObs(entityMonitor: EntityMonitor<T>) { return combineLatest( diff --git a/src/frontend/packages/store/src/helpers/paginated-request-helpers.ts b/src/frontend/packages/store/src/helpers/paginated-request-helpers.ts index 2a55648abc..b769333738 100644 --- a/src/frontend/packages/store/src/helpers/paginated-request-helpers.ts +++ b/src/frontend/packages/store/src/helpers/paginated-request-helpers.ts @@ -1,6 +1,6 @@ -import { HttpClient, HttpRequest } from '@angular/common/http'; -import { forkJoin, Observable, of as observableOf } from 'rxjs'; -import { first, map, mergeMap } from 'rxjs/operators'; +import { HttpClient, HttpRequest, HttpResponse } from '@angular/common/http'; +import { forkJoin, Observable, of as observableOf, of } from 'rxjs'; +import { first, map, mergeMap, switchMap } from 'rxjs/operators'; import { UpdatePaginationMaxedState } from '../actions/pagination.actions'; import { ActionDispatcher } from '../entity-request-pipeline/entity-request-pipeline.types'; @@ -9,15 +9,10 @@ import { ActionDispatcher } from '../entity-request-pipeline/entity-request-pipe // TODO: See #4208. This should be replaced with // src/frontend/packages/store/src/entity-request-pipeline/pagination-request-base-handlers/pagination-iterator.pipe.ts -export interface PaginationFlattenerConfig<T = any, C = any> extends Pick< - PaginationFlattener<T, C>, - 'getTotalPages' | 'getTotalResults' | 'mergePages' | 'clearResults' - > { } - export interface PaginationFlattener<T = any, C = any> { getTotalPages: (res: C) => number; getTotalResults: (res: C) => number; - mergePages: (res: T[]) => T; + mergePages: (res: C[]) => T; fetch: (...args) => Observable<C>; buildFetchParams: (i: number) => any[]; clearResults: (res: C, allResults: number) => Observable<C>; @@ -66,6 +61,39 @@ export class BaseHttpFetcher { } } +/** + * T is what we get back per request, C is what we return in the observable + */ +export interface IteratePaginationConfig<T, C> { + getFirst: (url: string) => Observable<HttpResponse<T>>; + getNext: (response: HttpResponse<T>) => Observable<HttpResponse<T>>; + getResult: (response: HttpResponse<T>) => C[]; +} +/** + * T is what we get back per request, C is what we return in the observable + */ +export function iteratePagination<T, C>( + results: C[] = [], + request: Observable<HttpResponse<T>>, + iterator: IteratePaginationConfig<T, C> +): Observable<C[]> { + return request.pipe( + first(), + switchMap(response => { + const nextRequest = iterator.getNext(response); + results.push(...iterator.getResult(response)); + if (!nextRequest) { + return of(results); + } + return iteratePagination<T, C>( + results, + nextRequest, + iterator + ); + }), + ); +} + export function flattenPagination<T, C>( actionDispatcher: ActionDispatcher, firstRequest: Observable<C>, @@ -90,7 +118,6 @@ export function flattenPagination<T, C>( return forkJoin([flattener.clearResults(firstResData, allResults)]); } } - // Discover the endpoint with the most pages. This is the amount of request we will need to make to fetch all pages from all // Make those requests const maxRequests = flattener.getTotalPages(firstResData); const requests = []; @@ -102,7 +129,7 @@ export function flattenPagination<T, C>( } return forkJoin(requests); }), - map((responses: T[]) => { + map((responses: C[]) => { // Merge all responses into the first page return flattener.mergePages(responses); }), diff --git a/src/frontend/packages/store/src/helpers/stratos-entity-factory.ts b/src/frontend/packages/store/src/helpers/stratos-entity-factory.ts index 1182c576a3..2c570b69e5 100644 --- a/src/frontend/packages/store/src/helpers/stratos-entity-factory.ts +++ b/src/frontend/packages/store/src/helpers/stratos-entity-factory.ts @@ -4,6 +4,7 @@ export const userFavouritesEntityType = 'userFavorites'; export const endpointEntityType = 'endpoint'; export const userProfileEntityType = 'userProfile'; export const systemInfoEntityType = 'systemInfo'; +export const apiKeyEntityType = 'apiKey'; export const metricEntityType = 'metrics'; @@ -31,6 +32,9 @@ entityCache[endpointEntityType] = EndpointSchema; const UserProfileInfoSchema = new StratosEntitySchema(userProfileEntityType, 'id'); entityCache[userProfileEntityType] = UserProfileInfoSchema; +const ApiKeySchema = new StratosEntitySchema(apiKeyEntityType, 'guid'); +entityCache[apiKeyEntityType] = ApiKeySchema; + export function stratosEntityFactory(key: string): EntitySchema { const entity = entityCache[key]; if (!entity) { diff --git a/src/frontend/packages/store/src/monitors/internal-event-monitor.factory.spec.ts b/src/frontend/packages/store/src/monitors/internal-event-monitor.factory.spec.ts index c29e7856a2..1911b1549f 100644 --- a/src/frontend/packages/store/src/monitors/internal-event-monitor.factory.spec.ts +++ b/src/frontend/packages/store/src/monitors/internal-event-monitor.factory.spec.ts @@ -1,15 +1,14 @@ -import { TestBed, inject } from '@angular/core/testing'; -import { InternalEventMonitorFactory } from './internal-event-monitor.factory'; -import { SharedModule } from '../../../core/src/shared/shared.module'; +import { inject, TestBed } from '@angular/core/testing'; import { StoreModule } from '@ngrx/store'; +import { InternalEventMonitorFactory } from './internal-event-monitor.factory'; + describe('InternalEventMonitorFactory', () => { beforeEach(() => { TestBed.configureTestingModule({ providers: [InternalEventMonitorFactory], imports: [ - SharedModule, StoreModule.forRoot({}) ] }); diff --git a/src/frontend/packages/store/src/monitors/internal-event.monitor.ts b/src/frontend/packages/store/src/monitors/internal-event.monitor.ts index 9beb4d4164..ba51dd2911 100644 --- a/src/frontend/packages/store/src/monitors/internal-event.monitor.ts +++ b/src/frontend/packages/store/src/monitors/internal-event.monitor.ts @@ -1,13 +1,13 @@ import { NgZone } from '@angular/core'; -import * as moment from 'moment'; +import moment from 'moment'; import { combineLatest, Observable, of as observableOf } from 'rxjs'; import { distinctUntilChanged, map, share, startWith } from 'rxjs/operators'; import { InternalEventSeverity, InternalEventsState, - InternalEventSubjectState, InternalEventState, + InternalEventSubjectState, } from '../types/internal-events.types'; export function newNonAngularInterval(ngZone: NgZone, intervalTime: number) { diff --git a/src/frontend/packages/store/src/public-api.ts b/src/frontend/packages/store/src/public-api.ts index 1b1787baef..ea38cf610c 100644 --- a/src/frontend/packages/store/src/public-api.ts +++ b/src/frontend/packages/store/src/public-api.ts @@ -2,8 +2,25 @@ * Public API Surface of store */ - // Helpers - export * from './helpers/store-helpers'; +// Helpers +export * from './helpers/store-helpers'; - // App State - export { AppState } from './app-state'; \ No newline at end of file +// App State +export { AppState } from './app-state'; + +// Used by store testing module +export { CATALOGUE_ENTITIES, EntityCatalogFeatureModule } from './entity-catalog.module'; +export { entityCatalog, TestEntityCatalog } from './entity-catalog/entity-catalog'; +export { EntityCatalogEntityConfig } from './entity-catalog/entity-catalog.types'; +export { endpointEntityType, stratosEntityFactory } from './helpers/stratos-entity-factory'; +export { appReducers } from './reducers.module'; +export { getDefaultRequestState, rootUpdatingKey } from './reducers/api-request-reducer/types'; +export { getDefaultPaginationEntityState } from './reducers/pagination-reducer/pagination-reducer-reset-pagination'; +export { NormalizedResponse } from './types/api.types'; +export { SessionData, SessionDataEndpoint } from './types/auth.types'; +export { getDefaultRolesRequestState } from './types/current-user-roles.types'; +export { EndpointModel } from './types/endpoint.types'; +export { BaseEntityValues } from './types/entity.types'; +export { WrapperRequestActionSuccess } from './types/request.types'; + +export { flattenPagination, PaginationFlattener } from './helpers/paginated-request-helpers'; diff --git a/src/frontend/packages/store/src/reducers.module.ts b/src/frontend/packages/store/src/reducers.module.ts index d5268b68bf..af7b4c3104 100644 --- a/src/frontend/packages/store/src/reducers.module.ts +++ b/src/frontend/packages/store/src/reducers.module.ts @@ -3,7 +3,6 @@ import { ActionReducer, ActionReducerMap, StoreModule } from '@ngrx/store'; import { localStorageSync } from 'ngrx-store-localstorage'; import { getDashboardStateSessionId } from './helpers/store-helpers'; -import { actionHistoryReducer } from './reducers/action-history-reducer'; import { requestReducer } from './reducers/api-request-reducers.generator'; import { authReducer } from './reducers/auth.reducer'; import { currentUserRolesReducer } from './reducers/current-user-roles-reducer/current-user-roles.reducer'; @@ -36,7 +35,6 @@ export const appReducers = { // This is added as part of the entity catalog module. // requestData, dashboard: dashboardReducer, - actionHistory: actionHistoryReducer, lists: listReducer, routing: routingReducer, internalEvents: internalEventReducer, diff --git a/src/frontend/packages/store/src/reducers/action-history-reducer.ts b/src/frontend/packages/store/src/reducers/action-history-reducer.ts deleted file mode 100644 index 3c42af31c5..0000000000 --- a/src/frontend/packages/store/src/reducers/action-history-reducer.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Action } from '@ngrx/store'; - -export class ActionHistoryState extends Array<string> { } - -const maxAge = 100; -const defaultState: ActionHistoryState = []; - -export function actionHistoryReducer(state: ActionHistoryState = defaultState, action: Action) { - // Un-comment this at some point - // const newState = [...state]; - // let historyItem = action.type; - // const message = (action as LoggerAction).message; - // if (message) { - // historyItem += ` '${message}'`; - // } - // newState.push(historyItem); - - // if (newState.length > maxAge) { - // newState.shift(); - // } - // return newState; - return state; -} diff --git a/src/frontend/packages/store/src/reducers/api-request-reducer/fail-request.ts b/src/frontend/packages/store/src/reducers/api-request-reducer/fail-request.ts index 1a3826a515..c32e437e5e 100644 --- a/src/frontend/packages/store/src/reducers/api-request-reducer/fail-request.ts +++ b/src/frontend/packages/store/src/reducers/api-request-reducer/fail-request.ts @@ -1,7 +1,6 @@ -import { isNullOrUndefined } from 'util'; - import { BaseEntityRequestAction } from '../../entity-catalog/action-orchestrator/action-orchestrator'; import { IFailedRequestAction } from '../../types/request.types'; +import { isNullOrUndefined } from '../../utils'; import { getEntityRequestState, mergeUpdatingState, setEntityRequestState } from './request-helpers'; export function failRequest(state, action: IFailedRequestAction) { diff --git a/src/frontend/packages/store/src/reducers/api-request-reducer/start-request.ts b/src/frontend/packages/store/src/reducers/api-request-reducer/start-request.ts index e6534502a7..7a2446129e 100644 --- a/src/frontend/packages/store/src/reducers/api-request-reducer/start-request.ts +++ b/src/frontend/packages/store/src/reducers/api-request-reducer/start-request.ts @@ -1,7 +1,6 @@ -import { isNullOrUndefined } from 'util'; - import { BaseEntityRequestAction } from '../../entity-catalog/action-orchestrator/action-orchestrator'; import { IStartRequestAction } from '../../types/request.types'; +import { isNullOrUndefined } from '../../utils'; import { getEntityRequestState, mergeUpdatingState, diff --git a/src/frontend/packages/store/src/reducers/api-request-reducer/succeed-request.ts b/src/frontend/packages/store/src/reducers/api-request-reducer/succeed-request.ts index 71b79e9aaa..ee4089a1e1 100644 --- a/src/frontend/packages/store/src/reducers/api-request-reducer/succeed-request.ts +++ b/src/frontend/packages/store/src/reducers/api-request-reducer/succeed-request.ts @@ -1,9 +1,8 @@ -import { isNullOrUndefined } from 'util'; - import { BaseRequestState } from '../../app-state'; import { BaseEntityRequestAction } from '../../entity-catalog/action-orchestrator/action-orchestrator'; import { mergeState } from '../../helpers/reducer.helper'; import { ISuccessRequestAction, WrapperRequestActionSuccess } from '../../types/request.types'; +import { isNullOrUndefined } from '../../utils'; import { createRequestStateFromResponse, getEntityRequestState, diff --git a/src/frontend/packages/store/src/reducers/api-request-reducer/update-request.ts b/src/frontend/packages/store/src/reducers/api-request-reducer/update-request.ts index 4d9cbc8ebe..c7e2bfd1a9 100644 --- a/src/frontend/packages/store/src/reducers/api-request-reducer/update-request.ts +++ b/src/frontend/packages/store/src/reducers/api-request-reducer/update-request.ts @@ -1,8 +1,7 @@ -import { isNullOrUndefined } from 'util'; - -import { BaseEntityRequestAction } from '../../entity-catalog/action-orchestrator/action-orchestrator'; import { BaseRequestState } from '../../app-state'; +import { BaseEntityRequestAction } from '../../entity-catalog/action-orchestrator/action-orchestrator'; import { IUpdateRequestAction } from '../../types/request.types'; +import { isNullOrUndefined } from '../../utils'; import { getEntityRequestState, mergeUpdatingState, setEntityRequestState } from './request-helpers'; export function updateRequest(state: BaseRequestState, action: IUpdateRequestAction) { diff --git a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-roles.reducer.ts b/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-roles.reducer.ts index ad4ffc58b4..3f958b4758 100644 --- a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-roles.reducer.ts +++ b/src/frontend/packages/store/src/reducers/current-user-roles-reducer/current-user-roles.reducer.ts @@ -46,7 +46,7 @@ function coreCurrentUserRolesReducer(state: ICurrentUserRolesState, action: Acti state: currentUserRolesRequestStateReducer(state.state, RolesRequestStateStage.FAILURE) }; case SESSION_VERIFIED: - const svAction = action as VerifiedSession + const svAction = action as VerifiedSession; return applyInternalScopes(state, svAction.sessionData.user); } return state; @@ -59,7 +59,10 @@ export enum RolesRequestStateStage { OTHER } -export function currentUserRolesRequestStateReducer(state: RolesRequestState = getDefaultRolesRequestState(), stage: RolesRequestStateStage) { +export function currentUserRolesRequestStateReducer( + state: RolesRequestState = getDefaultRolesRequestState(), + stage: RolesRequestStateStage +) { switch (stage) { case RolesRequestStateStage.START: return { diff --git a/src/frontend/packages/store/src/store.module.ts b/src/frontend/packages/store/src/store.module.ts index 794355dc3a..903f620b94 100644 --- a/src/frontend/packages/store/src/store.module.ts +++ b/src/frontend/packages/store/src/store.module.ts @@ -2,8 +2,8 @@ import { HttpClientModule } from '@angular/common/http'; import { NgModule } from '@angular/core'; import { EffectsModule } from '@ngrx/effects'; -import { ActionHistoryEffect } from './effects/action-history.effects'; import { APIEffect } from './effects/api.effects'; +import { ApiKeyEffect } from './effects/apiKey.effects'; import { AuthEffect } from './effects/auth.effects'; import { DashboardEffect } from './effects/dashboard.effects'; import { EndpointApiError } from './effects/endpoint-api-errors.effects'; @@ -37,7 +37,6 @@ import { AppReducersModule } from './reducers.module'; UAASetupEffect, EndpointsEffect, PaginationEffects, - ActionHistoryEffect, RouterEffect, SystemEffects, SetClientFilterEffect, @@ -45,7 +44,8 @@ import { AppReducersModule } from './reducers.module'; UserProfileEffect, RecursiveDeleteEffect, UserFavoritesEffect, - PermissionsEffects + PermissionsEffects, + ApiKeyEffect ]) ] }) diff --git a/src/frontend/packages/store/src/stratos-action-builders.ts b/src/frontend/packages/store/src/stratos-action-builders.ts index c64a95cd01..d14da73621 100644 --- a/src/frontend/packages/store/src/stratos-action-builders.ts +++ b/src/frontend/packages/store/src/stratos-action-builders.ts @@ -1,6 +1,6 @@ +import { AddApiKey, DeleteApiKey, GetAllApiKeys } from './actions/apiKey.actions'; import { AuthParams, - BaseEndpointAction, ConnectEndpoint, DisconnectEndpoint, GetAllEndpoints, @@ -9,7 +9,7 @@ import { UnregisterEndpoint, UpdateEndpoint, } from './actions/endpoint.actions'; -import { GetSystemInfo } from './actions/system.actions'; +import { GetSystemInfo, GetSystemInfoAssociatedAction } from './actions/system.actions'; import { GetUserFavoritesAction, RemoveUserFavoriteAction, @@ -33,7 +33,7 @@ export interface EndpointActionBuilder extends OrchestratedActionBuilders { getMultiple: ( endpointGuid?: string, paginationKey?: string, - args?: { login: boolean } + args?: { login: boolean, } ) => GetAllEndpoints, connect: ( guid: string, @@ -83,7 +83,7 @@ export const endpointActionBuilder: EndpointActionBuilder = { getMultiple: ( endpointGuid?: string, paginationKey?: string, - args?: { login: boolean } + args?: { login: boolean, } ) => new GetAllEndpoints(args ? args.login : false), connect: ( guid: string, @@ -137,20 +137,20 @@ export const endpointActionBuilder: EndpointActionBuilder = { args.clientSecret, args.allowSSO ), -} +}; export interface SystemInfoActionBuilder extends OrchestratedActionBuilders { getSystemInfo: ( login?: boolean, - associatedAction?: BaseEndpointAction - ) => GetSystemInfo + associatedAction?: GetSystemInfoAssociatedAction + ) => GetSystemInfo; } export const systemInfoActionBuilder: SystemInfoActionBuilder = { getSystemInfo: ( login?: false, - associatedAction?: BaseEndpointAction + associatedAction?: GetSystemInfoAssociatedAction ) => new GetSystemInfo(login, associatedAction) -} +}; export interface UserFavoriteActionBuilder extends OrchestratedActionBuilders { getMultiple: () => GetUserFavoritesAction, @@ -163,10 +163,10 @@ export interface UserFavoriteActionBuilder extends OrchestratedActionBuilders { ) => SaveUserFavoriteAction, toggle: ( favorite: UserFavorite<IFavoriteMetadata> - ) => ToggleUserFavoriteAction + ) => ToggleUserFavoriteAction; updateFavorite: ( favorite: UserFavorite<IFavoriteMetadata> - ) => UpdateUserFavoriteMetadataAction + ) => UpdateUserFavoriteMetadataAction; } export const userFavoriteActionBuilder: UserFavoriteActionBuilder = { @@ -176,23 +176,40 @@ export const userFavoriteActionBuilder: UserFavoriteActionBuilder = { save: (favorite: UserFavorite<IFavoriteMetadata>) => new SaveUserFavoriteAction(favorite), toggle: (favorite: UserFavorite<IFavoriteMetadata>) => new ToggleUserFavoriteAction(favorite), updateFavorite: (favorite: UserFavorite<IFavoriteMetadata>) => new UpdateUserFavoriteMetadataAction(favorite) -} +}; export interface UserProfileActionBuilder extends OrchestratedActionBuilders { get: ( userGuid: string - ) => FetchUserProfileAction + ) => FetchUserProfileAction; updateProfile: ( profile: UserProfileInfo, password: string - ) => UpdateUserProfileAction + ) => UpdateUserProfileAction; updatePassword: ( guid: string, passwordChanges: UserProfilePasswordUpdate - ) => UpdateUserPasswordAction + ) => UpdateUserPasswordAction; } export const userProfileActionBuilder: UserProfileActionBuilder = { get: (userGuid: string) => new FetchUserProfileAction(userGuid), updateProfile: (profile: UserProfileInfo, password: string) => new UpdateUserProfileAction(profile, password), updatePassword: (guid: string, passwordChanges: UserProfilePasswordUpdate) => new UpdateUserPasswordAction(guid, passwordChanges) -} \ No newline at end of file +}; + +export interface ApiKeyActionBuilder extends OrchestratedActionBuilders { + create: ( + comment: string + ) => AddApiKey; + delete: ( + guid: string + ) => DeleteApiKey; + getMultiple: ( + + ) => GetAllApiKeys; +} +export const apiKeyActionBuilder: ApiKeyActionBuilder = { + create: (comment: string) => new AddApiKey(comment), + delete: (guid: string) => new DeleteApiKey(guid), + getMultiple: () => new GetAllApiKeys() +}; \ No newline at end of file diff --git a/src/frontend/packages/store/src/stratos-entity-catalog.ts b/src/frontend/packages/store/src/stratos-entity-catalog.ts index 9237a16978..5a58cc14aa 100644 --- a/src/frontend/packages/store/src/stratos-entity-catalog.ts +++ b/src/frontend/packages/store/src/stratos-entity-catalog.ts @@ -1,8 +1,10 @@ +import { ApiKey } from './apiKey.types'; import { StratosCatalogEndpointEntity, StratosCatalogEntity, } from './entity-catalog/entity-catalog-entity/entity-catalog-entity'; import { + ApiKeyActionBuilder, EndpointActionBuilder, SystemInfoActionBuilder, UserFavoriteActionBuilder, @@ -39,6 +41,12 @@ export class StratosEntityCatalog { > metricsEndpoint: StratosCatalogEndpointEntity; + + apiKey: StratosCatalogEntity< + undefined, + ApiKey, + ApiKeyActionBuilder + > } export const stratosEntityCatalog = new StratosEntityCatalog(); diff --git a/src/frontend/packages/store/src/stratos-entity-generator.ts b/src/frontend/packages/store/src/stratos-entity-generator.ts index 0062f4fc4a..c1ae7ffae0 100644 --- a/src/frontend/packages/store/src/stratos-entity-generator.ts +++ b/src/frontend/packages/store/src/stratos-entity-generator.ts @@ -1,28 +1,23 @@ -import { BaseEndpointAuth } from '../../core/src/core/endpoint-auth'; -import { - MetricsEndpointDetailsComponent, -} from '../../core/src/features/metrics/metrics-endpoint-details/metrics-endpoint-details.component'; +import { ApiKey } from './apiKey.types'; import { StratosBaseCatalogEntity, StratosCatalogEndpointEntity, StratosCatalogEntity, -} from '../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; +} from './entity-catalog/entity-catalog-entity/entity-catalog-entity'; +import { IStratosEntityDefinition } from './entity-catalog/entity-catalog.types'; import { - endpointEntityType, + apiKeyEntityType, STRATOS_ENDPOINT_TYPE, - stratosEntityFactory, systemInfoEntityType, userFavouritesEntityType, userProfileEntityType, -} from '../../store/src/helpers/stratos-entity-factory'; -import { - addOrUpdateUserFavoriteMetadataReducer, - deleteUserFavoriteMetadataReducer, -} from '../../store/src/reducers/favorite.reducer'; -import { systemEndpointsReducer } from '../../store/src/reducers/system-endpoints.reducer'; -import { EndpointModel } from '../../store/src/types/endpoint.types'; -import { IStratosEntityDefinition } from './entity-catalog/entity-catalog.types'; +} from './helpers/stratos-entity-factory'; +import { endpointEntityType, EndpointModel, stratosEntityFactory } from './public-api'; +import { addOrUpdateUserFavoriteMetadataReducer, deleteUserFavoriteMetadataReducer } from './reducers/favorite.reducer'; +import { systemEndpointsReducer } from './reducers/system-endpoints.reducer'; import { + ApiKeyActionBuilder, + apiKeyActionBuilder, EndpointActionBuilder, endpointActionBuilder, SystemInfoActionBuilder, @@ -52,7 +47,8 @@ export function generateStratosEntities(): StratosBaseCatalogEntity[] { generateSystemInfo(stratosType), generateUserFavorite(stratosType), generateUserProfile(stratosType), - generateMetricsEndpoint() + generateMetricsEndpoint(), + generateAPIKeys(stratosType) ] } @@ -151,11 +147,29 @@ function generateMetricsEndpoint() { labelPlural: 'Metrics', tokenSharing: true, logoUrl: '/core/assets/endpoint-icons/metrics.svg', - authTypes: [BaseEndpointAuth.UsernamePassword, BaseEndpointAuth.None], - renderPriority: 1, - listDetailsComponent: MetricsEndpointDetailsComponent, + authTypes: [], + renderPriority: 1 }, metadata => `/endpoints/metrics/${metadata.guid}` ) return stratosEntityCatalog.metricsEndpoint; } + +function generateAPIKeys(stratosType) { + const definition: IStratosEntityDefinition = { + schema: stratosEntityFactory(apiKeyEntityType), + type: apiKeyEntityType, + endpoint: stratosType, + } + stratosEntityCatalog.apiKey = new StratosCatalogEntity< + undefined, + ApiKey, + ApiKeyActionBuilder + >( + definition, + { + actionBuilders: apiKeyActionBuilder + } + ) + return stratosEntityCatalog.apiKey; +} diff --git a/src/frontend/packages/store/src/types/auth.types.ts b/src/frontend/packages/store/src/types/auth.types.ts index cef354d888..5787243ea7 100644 --- a/src/frontend/packages/store/src/types/auth.types.ts +++ b/src/frontend/packages/store/src/types/auth.types.ts @@ -22,10 +22,16 @@ export interface SessionEndpoints { export interface SessionEndpoint { [guid: string]: SessionDataEndpoint; } +export enum APIKeysEnabled { + DISABLED = 'disabled', + ADMIN_ONLY = 'admin_only', + ALL_USERS = 'all_users' +} export interface SessionDataConfig { enableTechPreview?: boolean; listMaxSize?: number; listAllowLoadMaxed?: boolean; + APIKeysEnabled?: APIKeysEnabled; } export interface SessionData { endpoints?: SessionEndpoints; @@ -44,7 +50,7 @@ export interface SessionData { ['plugin-config']?: PluginConfig; plugins: { demo: boolean, - [pluginName: string]: boolean + [pluginName: string]: boolean, }; config: SessionDataConfig; } diff --git a/src/frontend/packages/store/src/utils.ts b/src/frontend/packages/store/src/utils.ts new file mode 100644 index 0000000000..4167318128 --- /dev/null +++ b/src/frontend/packages/store/src/utils.ts @@ -0,0 +1,6 @@ +// We don't want to bring in the utils package from nodejs +// We only use this one function: + +export function isNullOrUndefined(obj: any): boolean { + return typeof obj === 'undefined' || obj === null; +} diff --git a/src/frontend/packages/store/testing/ng-package.json b/src/frontend/packages/store/testing/ng-package.json index bfa6b062cf..f5e468c9b5 100644 --- a/src/frontend/packages/store/testing/ng-package.json +++ b/src/frontend/packages/store/testing/ng-package.json @@ -2,6 +2,9 @@ "$schema": "../../../../../node_modules/ng-packagr/ng-package.schema.json", "dest": "../../../../dist/store/testing", "lib": { - "entryFile": "public-api.ts" + "entryFile": "public-api.ts", + "umdModuleIds": { + "@ngrx/store": "ngrx_store" + } } } \ No newline at end of file diff --git a/src/frontend/packages/store/testing/src/store-test-helper.ts b/src/frontend/packages/store/testing/src/store-test-helper.ts index 327474a530..ad0942e40f 100644 --- a/src/frontend/packages/store/testing/src/store-test-helper.ts +++ b/src/frontend/packages/store/testing/src/store-test-helper.ts @@ -1,20 +1,24 @@ import { ModuleWithProviders } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { Store, StoreModule } from '@ngrx/store'; - -import { AppState } from '../../src/app-state'; -import { entityCatalog } from '../../src/entity-catalog/entity-catalog'; -import { EntityCatalogEntityConfig } from '../../src/entity-catalog/entity-catalog.types'; -import { endpointEntityType, stratosEntityFactory } from '../../src/helpers/stratos-entity-factory'; -import { appReducers } from '../../src/reducers.module'; -import { getDefaultRequestState, rootUpdatingKey } from '../../src/reducers/api-request-reducer/types'; -import { getDefaultPaginationEntityState } from '../../src/reducers/pagination-reducer/pagination-reducer-reset-pagination'; -import { NormalizedResponse } from '../../src/types/api.types'; -import { SessionData, SessionDataEndpoint } from '../../src/types/auth.types'; -import { getDefaultRolesRequestState } from '../../src/types/current-user-roles.types'; -import { EndpointModel } from '../../src/types/endpoint.types'; -import { BaseEntityValues } from '../../src/types/entity.types'; -import { WrapperRequestActionSuccess } from '../../src/types/request.types'; +import { + appReducers, + AppState, + BaseEntityValues, + endpointEntityType, + EndpointModel, + entityCatalog, + EntityCatalogEntityConfig, + getDefaultPaginationEntityState, + getDefaultRequestState, + getDefaultRolesRequestState, + NormalizedResponse, + rootUpdatingKey, + SessionData, + SessionDataEndpoint, + stratosEntityFactory, + WrapperRequestActionSuccess, +} from '@stratosui/store'; export const testSCFEndpointGuid = '01ccda9d-8f40-4dd0-bc39-08eea68e364f'; const testSCFSessionEndpoint: SessionDataEndpoint = { @@ -164,7 +168,6 @@ function getDefaultInitialTestStratosStoreState() { headerEventMinimized: true, gravatarEnabled: false, }, - actionHistory: [], lists: {}, routing: { previousState: { diff --git a/src/frontend/packages/store/testing/src/store-test.module.ts b/src/frontend/packages/store/testing/src/store-test.module.ts index 9bb257559b..e5bc7764c2 100644 --- a/src/frontend/packages/store/testing/src/store-test.module.ts +++ b/src/frontend/packages/store/testing/src/store-test.module.ts @@ -1,7 +1,5 @@ import { NgModule } from '@angular/core'; - -import { CATALOGUE_ENTITIES, EntityCatalogFeatureModule } from '../../src/entity-catalog.module'; -import { entityCatalog, TestEntityCatalog } from '../../src/entity-catalog/entity-catalog'; +import { CATALOGUE_ENTITIES, entityCatalog, EntityCatalogFeatureModule, TestEntityCatalog } from '@stratosui/store'; @NgModule({ imports: [ diff --git a/src/frontend/packages/store/testing/tslint.json b/src/frontend/packages/store/testing/tslint.json index 6ee5e859e0..847d730db4 100644 --- a/src/frontend/packages/store/testing/tslint.json +++ b/src/frontend/packages/store/testing/tslint.json @@ -1,5 +1,5 @@ { - "extends": "../../../../tslint.json", + "extends": "../../../../../tslint.json", "rules": { "directive-selector": [ false, diff --git a/src/frontend/packages/suse-extensions/src/custom/demo/demo-helper/demo-helper.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/demo/demo-helper/demo-helper.component.spec.ts index 847f32c442..b0c270cf94 100644 --- a/src/frontend/packages/suse-extensions/src/custom/demo/demo-helper/demo-helper.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/demo/demo-helper/demo-helper.component.spec.ts @@ -1,7 +1,7 @@ import { HttpClientModule } from '@angular/common/http'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { BaseTestModules } from '../../../../../core/test-framework/core-test.helper'; import { DemoHelperComponent } from './demo-helper.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.spec.ts index 410d613753..462903feb4 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { BaseTestModulesNoShared } from '../../../../../core/test-framework/core-test.helper'; import { HelmModule } from '../helm.module'; import { MonocularTabBaseComponent } from './monocular-tab-base.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.ts index 939eb86e08..171a260696 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.ts @@ -1,5 +1,5 @@ import { Component, Input } from '@angular/core'; -import * as markdown from 'marked'; +import markdown from 'marked'; import { Observable, of as observableOf } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.spec.ts index b9512b6483..e07911a9da 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.spec.ts @@ -4,7 +4,6 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { LoggerService } from '../../../../../../core/src/core/logger.service'; import { createBasicStoreModule } from '../../../../../../store/testing/public-api'; import { ChartsService } from '../shared/services/charts.service'; import { ConfigService } from '../shared/services/config.service'; @@ -23,7 +22,6 @@ describe('Component: ChartItem', () => { HttpClient, ConfigService, ChartsService, - LoggerService, { provide: ActivatedRoute, useValue: { diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/charts/charts.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/charts/charts.component.spec.ts index 493bebfb1d..eae5836ec8 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/charts/charts.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/charts/charts.component.spec.ts @@ -6,7 +6,6 @@ import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { of } from 'rxjs'; -import { LoggerService } from '../../../../../../core/src/core/logger.service'; import { createBasicStoreModule } from '../../../../../../store/testing/public-api'; import { ChartItemComponent } from '../chart-item/chart-item.component'; import { ChartListComponent } from '../chart-list/chart-list.component'; @@ -51,7 +50,6 @@ describe('ChartsComponent', () => { } }, ReposService, - LoggerService ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts index cb22418fba..5d48d733b0 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts @@ -4,7 +4,6 @@ import { ActivatedRoute } from '@angular/router'; import { Observable, of, throwError } from 'rxjs'; import { catchError, map, tap } from 'rxjs/operators'; -import { LoggerService } from '../../../../../../../core/src/core/logger.service'; import { getMonocularEndpoint, stratosMonocularEndpointGuid } from '../../stratos-monocular.helper'; import { Chart } from '../models/chart'; import { ChartVersion } from '../models/chart-version'; @@ -21,7 +20,6 @@ export class ChartsService { constructor( private http: HttpClient, config: ConfigService, - private loggerService: LoggerService, private route: ActivatedRoute, ) { this.hostname = `${config.backendHostname}/chartsvc`; @@ -204,9 +202,7 @@ export class ChartsService { private handleError(error: any) { const errMsg = (error.message) ? error.message : error.status ? `${error.status} - ${error.statusText}` : 'Server error'; - if (!!this.loggerService) { - this.loggerService.error(errMsg); - } + console.error(errMsg); return throwError(errMsg); } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/repos.service.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/repos.service.ts index 23a0880309..8903061080 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/repos.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/repos.service.ts @@ -3,7 +3,6 @@ import { Injectable } from '@angular/core'; import { Observable, throwError } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; -import { LoggerService } from '../../../../../../../core/src/core/logger.service'; import { RepoAttributes } from '../models/repo'; import { ConfigService } from './config.service'; @@ -16,7 +15,6 @@ export class ReposService { constructor( private http: HttpClient, private config: ConfigService, - private loggerService: LoggerService ) { this.hostname = `/pp/v1/chartrepos`; } @@ -40,9 +38,7 @@ export class ReposService { private handleError(error: any) { const errMsg = (error.json().message) ? error.json().message : error.status ? `${error.status} - ${error.statusText}` : 'Server error'; - if (!!this.loggerService) { - this.loggerService.error(errMsg); - } + console.error(errMsg); return throwError(errMsg); } } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.ts index ffb13fa10c..5b5bce37a5 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.ts @@ -1,5 +1,5 @@ import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; -import * as moment from 'moment'; +import moment from 'moment'; import { Observable, Subscription } from 'rxjs'; import { map, tap } from 'rxjs/operators'; @@ -60,7 +60,7 @@ export class AnalysisReportSelectorComponent implements OnInit, OnDestroy { this.onSelected(reports[0]); } }) - ) + ); } @@ -75,13 +75,13 @@ export class AnalysisReportSelectorComponent implements OnInit, OnDestroy { } public refreshReports($event: MouseEvent) { - this.analysisService.getByPath(this.endpoint, this.path, true) + this.analysisService.getByPath(this.endpoint, this.path, true); $event.preventDefault(); $event.cancelBubble = true; } ngOnDestroy() { - safeUnsubscribe(...this.subs) + safeUnsubscribe(...this.subs); } } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-terminal/kube-console.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-terminal/kube-console.component.spec.ts index 65f7e4ba32..75932802f6 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-terminal/kube-console.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-terminal/kube-console.component.spec.ts @@ -4,8 +4,9 @@ import { createBasicStoreModule } from '@stratosui/store/testing'; import { ApplicationService } from '../../../../../cloud-foundry/src/features/applications/application.service'; import { ApplicationServiceMock } from '../../../../../cloud-foundry/test-framework/application-service-helper'; +import { CurrentUserPermissionsService } from '../../../../../core/src/core/permissions/current-user-permissions.service'; import { SharedModule } from '../../../../../core/src/public-api'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { CoreModule } from './../../../../../core/src/core/core.module'; import { KubeConsoleComponent } from './kube-console.component'; @@ -24,7 +25,8 @@ describe('KubeConsoleComponent', () => { ], providers: [ { provide: ApplicationService, useClass: ApplicationServiceMock }, - TabNavService + TabNavService, + CurrentUserPermissionsService ], }) .compileComponents(); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.spec.ts index 84062f4fc2..0779af7d2c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.spec.ts @@ -2,7 +2,7 @@ import { HttpClient, HttpHandler } from '@angular/common/http'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { TabNavService } from '../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../core/src/tab-nav.service'; import { KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { KubedashConfigurationComponent } from './kubedash-configuration.component'; @@ -13,7 +13,7 @@ describe('KubedashConfigurationComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [...KubernetesBaseTestModules], - declarations: [ KubedashConfigurationComponent ], + declarations: [KubedashConfigurationComponent], providers: [ { provide: ActivatedRoute, @@ -31,7 +31,7 @@ describe('KubedashConfigurationComponent', () => { HttpHandler, ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.spec.ts index f918ee294b..92ee1c9d9d 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { KubernetesBaseTestModules } from '../kubernetes.testing.module'; import { KubernetesDashboardTabComponent } from './kubernetes-dashboard.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-metrics.helpers.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-metrics.helpers.ts index 1507afc966..45580cbbdf 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-metrics.helpers.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-metrics.helpers.ts @@ -1,5 +1,4 @@ -import { ValidatorFn } from '@angular/forms'; -import * as moment from 'moment'; +import moment from 'moment'; export function formatCPUTime(value: string | number, debug = false): string { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.spec.ts index 4834ac63ec..c247e9f24e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.spec.ts @@ -1,5 +1,5 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from 'frontend/packages/core/tab-nav.service'; +import { TabNavService } from 'frontend/packages/core/src/tab-nav.service'; import { MDAppModule } from '../../../../../../core/src/public-api'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../kubernetes.testing.module'; @@ -18,7 +18,7 @@ describe('KubernetesNamespaceAnalysisReportComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ KubernetesNamespaceAnalysisReportComponent, AnalysisReportSelectorComponent, AnalysisReportViewerComponent ], + declarations: [KubernetesNamespaceAnalysisReportComponent, AnalysisReportSelectorComponent, AnalysisReportViewerComponent], imports: [ KubernetesBaseTestModules, MDAppModule @@ -31,7 +31,7 @@ describe('KubernetesNamespaceAnalysisReportComponent', () => { TabNavService, ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.spec.ts index d638b3b663..083db88ddf 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../core/src/tab-nav.service'; import { BaseKubeGuid } from '../../kubernetes-page.types'; import { KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node.component.spec.ts index b334471044..b06b4eee40 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../kubernetes.testing.module'; import { KubernetesNodeComponent } from './kubernetes-node.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.ts index ded7309f19..8944cb03ef 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import * as moment from 'moment'; +import moment from 'moment'; import { Observable, of } from 'rxjs'; import { filter, first, map, publishReplay, refCount, switchMap } from 'rxjs/operators'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.spec.ts index 0d8e5a109e..d714ab66a2 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { KubernetesBaseTestModules } from '../kubernetes.testing.module'; import { KubernetesTabBaseComponent } from './kubernetes-tab-base.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.testing.module.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.testing.module.ts index ea8fa319f8..af0520b267 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.testing.module.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.testing.module.ts @@ -5,7 +5,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { CoreModule } from '../../../../core/src/core/core.module'; import { SharedModule } from '../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../core/src/tab-nav.service'; import { AppTestModule } from '../../../../core/test-framework/core-test.helper'; import { CATALOGUE_ENTITIES, EntityCatalogFeatureModule } from '../../../../store/src/entity-catalog.module'; import { entityCatalog, TestEntityCatalog } from '../../../../store/src/entity-catalog/entity-catalog'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes/kubernetes.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes/kubernetes.component.spec.ts index f715faab3a..d222961131 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes/kubernetes.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes/kubernetes.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { KubernetesBaseTestModules } from '../kubernetes.testing.module'; import { KubernetesComponent } from './kubernetes.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-config.service.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-config.service.ts index 9e68483e80..8755eda8b2 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-config.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-config.service.ts @@ -7,7 +7,7 @@ import { IListMultiFilterConfig, ListViewTypes, } from 'frontend/packages/core/src/shared/components/list/list.component.types'; -import * as moment from 'moment'; +import moment from 'moment'; import { of } from 'rxjs'; import { ListView } from '../../../../../store/src/actions/list.actions'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kube-list.helper.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kube-list.helper.ts index 5769d1bbee..1509c97e9c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kube-list.helper.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kube-list.helper.ts @@ -1,4 +1,4 @@ -import * as moment from 'moment'; +import moment from 'moment'; import { DataFunction } from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; import { ITableColumn } from '../../../../../core/src/shared/components/list/list-table/table.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.ts index 7f349cd24a..526d4132e9 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.ts @@ -1,7 +1,7 @@ /* tslint:disable:max-line-length */ import { TitleCasePipe } from '@angular/common'; import { Component, Input } from '@angular/core'; -import * as moment from 'moment'; +import moment from 'moment'; import { Observable } from 'rxjs'; import { filter, map } from 'rxjs/operators'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/pod-metrics/pod-metrics.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/pod-metrics/pod-metrics.component.spec.ts index 3efdf94c24..5f245aad3a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/pod-metrics/pod-metrics.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/pod-metrics/pod-metrics.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { TabNavService } from '../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { KubernetesBaseTestModules } from '../kubernetes.testing.module'; import { PodMetricsComponent } from './pod-metrics.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.ts index 56fcba9997..4fb75c972c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.ts @@ -1,6 +1,6 @@ import { HttpClient } from '@angular/common/http'; import { Component, Input } from '@angular/core'; -import * as markdown from 'marked'; +import markdown from 'marked'; import { Observable, of } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.spec.ts index 642176aa68..ee1e090856 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../../core/src/tab-nav.service'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; import { KubernetesAnalysisService } from '../../../services/kubernetes.analysis.service'; @@ -15,7 +15,7 @@ describe('KubernetesAnalysisReportComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ KubernetesAnalysisReportComponent, AnalysisReportViewerComponent ], + declarations: [KubernetesAnalysisReportComponent, AnalysisReportViewerComponent], imports: [ KubernetesBaseTestModules, CoreModule, @@ -27,7 +27,7 @@ describe('KubernetesAnalysisReportComponent', () => { TabNavService, ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.spec.ts index 6e6255dd8c..1706cd886f 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.spec.ts @@ -1,5 +1,5 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from 'frontend/packages/core/tab-nav.service'; +import { TabNavService } from 'frontend/packages/core/src/tab-nav.service'; import { MDAppModule } from '../../../../../../core/src/public-api'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../kubernetes.testing.module'; @@ -15,7 +15,7 @@ describe('KubernetesAnalysisTabComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ KubernetesAnalysisTabComponent, AnalysisReportViewerComponent ], + declarations: [KubernetesAnalysisTabComponent, AnalysisReportViewerComponent], imports: [ KubernetesBaseTestModules, MDAppModule @@ -27,7 +27,7 @@ describe('KubernetesAnalysisTabComponent', () => { TabNavService, ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.spec.ts index 539312f0a8..c5ec93f420 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.spec.ts @@ -1,7 +1,7 @@ import { HttpClient, HttpHandler } from '@angular/common/http'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../core/src/tab-nav.service'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; import { KubernetesSummaryTabComponent } from './kubernetes-summary.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.spec.ts index f2533edc8b..00adc0ba34 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.spec.ts @@ -4,7 +4,7 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ConfirmationDialogService } from '../../../../../../core/src/shared/components/confirmation-dialog.service'; -import { TabNavService } from '../../../../../../core/tab-nav.service'; +import { TabNavService } from '../../../../../../core/src/tab-nav.service'; import { EntityMonitorFactory } from '../../../../../../store/src/monitors/entity-monitor.factory.service'; import { InternalEventMonitorFactory } from '../../../../../../store/src/monitors/internal-event-monitor.factory'; import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; @@ -37,7 +37,7 @@ describe('CreateReleaseComponent', () => { ConfirmationDialogService, { provide: ChartsService, useValue: new MockChartService() }, { provide: ConfigService, useValue: { appName: 'appName' } }, - ] + ] }) .compileComponents(); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-socket-service.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-socket-service.ts index 4147fe3e0e..920e07a48d 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-socket-service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-socket-service.ts @@ -4,7 +4,6 @@ import { Subject, Subscription } from 'rxjs'; import makeWebSocketObservable, { GetWebSocketResponses } from 'rxjs-websockets'; import { catchError, map, share, switchMap } from 'rxjs/operators'; -import { LoggerService } from '../../../../../../../core/src/core/logger.service'; import { SnackBarService } from '../../../../../../../core/src/shared/services/snackbar.service'; import { AppState } from '../../../../../../../store/src/app-state'; import { entityCatalog } from '../../../../../../../store/src/entity-catalog/entity-catalog'; @@ -37,7 +36,6 @@ export class HelmReleaseSocketService implements OnDestroy { constructor( private helmReleaseHelper: HelmReleaseHelperService, private store: Store<AppState>, - private logService: LoggerService, private snackbarService: SnackBarService, ) { @@ -56,7 +54,7 @@ export class HelmReleaseSocketService implements OnDestroy { ); const socket$ = makeWebSocketObservable(streamUrl).pipe(catchError(e => { - this.logService.error( + console.error( 'Error while connecting to socket: ' + JSON.stringify(e) ); return []; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.spec.ts index 96996ee411..521eb3a53e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.spec.ts @@ -1,10 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from 'frontend/packages/core/tab-nav.service'; +import { TabNavService } from 'frontend/packages/core/src/tab-nav.service'; -import { HelmReleaseProviders, KubernetesBaseTestModules, KubeBaseGuidMock } from '../../../kubernetes.testing.module'; -import { HelmReleaseTabBaseComponent } from './helm-release-tab-base.component'; -import { KubernetesAnalysisService } from '../../../services/kubernetes.analysis.service'; +import { HelmReleaseProviders, KubeBaseGuidMock, KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; +import { KubernetesAnalysisService } from '../../../services/kubernetes.analysis.service'; +import { HelmReleaseTabBaseComponent } from './helm-release-tab-base.component'; describe('HelmReleaseTabBaseComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.spec.ts index a2c29af062..9d531b7a8e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.spec.ts @@ -1,13 +1,14 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { TabNavService } from 'frontend/packages/core/src/tab-nav.service'; -import { HelmReleaseAnalysisTabComponent } from './helm-release-analysis-tab.component'; -import { AnalysisReportSelectorComponent } from './../../../../analysis-report-viewer/analysis-report-selector/analysis-report-selector.component'; -import { KubernetesBaseTestModules, KubeBaseGuidMock } from '../../../../kubernetes.testing.module'; -import { KubernetesAnalysisService } from '../../../../services/kubernetes.analysis.service'; -import { KubernetesEndpointService } from '../../../../services/kubernetes-endpoint.service'; import { AnalysisReportViewerComponent } from '../../../../analysis-report-viewer/analysis-report-viewer.component'; -import { HelmReleaseProviders } from '../../../../kubernetes.testing.module'; -import { TabNavService } from 'frontend/packages/core/tab-nav.service'; +import { HelmReleaseProviders, KubeBaseGuidMock, KubernetesBaseTestModules } from '../../../../kubernetes.testing.module'; +import { KubernetesEndpointService } from '../../../../services/kubernetes-endpoint.service'; +import { KubernetesAnalysisService } from '../../../../services/kubernetes.analysis.service'; +import { + AnalysisReportSelectorComponent, +} from './../../../../analysis-report-viewer/analysis-report-selector/analysis-report-selector.component'; +import { HelmReleaseAnalysisTabComponent } from './helm-release-analysis-tab.component'; describe('HelmReleaseAnalysisTabComponent', () => { let component: HelmReleaseAnalysisTabComponent; @@ -15,7 +16,7 @@ describe('HelmReleaseAnalysisTabComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ HelmReleaseAnalysisTabComponent, AnalysisReportSelectorComponent, AnalysisReportViewerComponent], + declarations: [HelmReleaseAnalysisTabComponent, AnalysisReportSelectorComponent, AnalysisReportViewerComponent], imports: [ KubernetesBaseTestModules, ], @@ -27,7 +28,7 @@ describe('HelmReleaseAnalysisTabComponent', () => { TabNavService ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.ts index 2e0ada870e..08907252bc 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import * as moment from 'moment'; +import moment from 'moment'; import { of } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.spec.ts index 99e5683d26..dca8ca40f6 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.spec.ts @@ -1,14 +1,16 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NgxGraphModule } from '@swimlane/ngx-graph'; import { SidePanelService } from 'frontend/packages/core/src/shared/services/side-panel.service'; -import { TabNavService } from 'frontend/packages/core/tab-nav.service'; +import { TabNavService } from 'frontend/packages/core/src/tab-nav.service'; import { HelmReleaseProviders, KubernetesBaseTestModules } from '../../../../kubernetes.testing.module'; -import { HelmReleaseResourceGraphComponent } from './helm-release-resource-graph.component'; -import { AnalysisReportSelectorComponent } from './../../../../analysis-report-viewer/analysis-report-selector/analysis-report-selector.component'; -import { KubernetesAnalysisService } from '../../../../services/kubernetes.analysis.service'; import { KubernetesEndpointService } from '../../../../services/kubernetes-endpoint.service'; +import { KubernetesAnalysisService } from '../../../../services/kubernetes.analysis.service'; +import { + AnalysisReportSelectorComponent, +} from './../../../../analysis-report-viewer/analysis-report-selector/analysis-report-selector.component'; import { KubeBaseGuidMock } from './../../../../kubernetes.testing.module'; +import { HelmReleaseResourceGraphComponent } from './helm-release-resource-graph.component'; describe('HelmReleaseResourceGraphComponent', () => { let component: HelmReleaseResourceGraphComponent; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts index 9eb1592e81..85a9bcbb98 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts @@ -1,5 +1,5 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from 'frontend/packages/core/tab-nav.service'; +import { TabNavService } from 'frontend/packages/core/src/tab-nav.service'; import { SidePanelService } from '../../../../../../../../core/src/shared/services/side-panel.service'; import { HelmReleaseProviders, KubeBaseGuidMock } from '../../../../kubernetes.testing.module'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts index a3f7b44bb4..f1c0413fc7 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts @@ -1,7 +1,6 @@ import { HttpClient } from '@angular/common/http'; import { Component, ComponentFactoryResolver, OnDestroy } from '@angular/core'; import { Store } from '@ngrx/store'; -import { LoggerService } from 'frontend/packages/core/src/core/logger.service'; import { ConfirmationDialogConfig } from 'frontend/packages/core/src/shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from 'frontend/packages/core/src/shared/components/confirmation-dialog.service'; import { SidePanelService } from 'frontend/packages/core/src/shared/services/side-panel.service'; @@ -93,7 +92,6 @@ export class HelmReleaseSummaryTabComponent implements OnDestroy { private store: Store<AppState>, private confirmDialog: ConfirmationDialogService, private httpClient: HttpClient, - private logService: LoggerService, private snackbarService: SnackBarService, public analyzerService: KubernetesAnalysisService, private previewPanel: SidePanelService, @@ -221,7 +219,7 @@ export class HelmReleaseSummaryTabComponent implements OnDestroy { error: (err: any) => { this.endDelete(); this.snackbarService.show('Failed to delete release', 'Close'); - this.logService.error('Failed to delete release: ', err); + console.error('Failed to delete release: ', err); }, complete: () => { const action = workloadsEntityCatalog.release.actions.getMultiple(); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.spec.ts index 753a4c1445..dd864daa79 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.spec.ts @@ -1,5 +1,5 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from 'frontend/packages/core/tab-nav.service'; +import { TabNavService } from 'frontend/packages/core/src/tab-nav.service'; import { HelmReleaseProviders, KubernetesBaseTestModules } from '../../../../kubernetes.testing.module'; import { HelmReleaseValuesTabComponent } from './helm-release-values-tab.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/releases-tab/releases-tab.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/releases-tab/releases-tab.component.spec.ts index 745833b6fd..20f505b6b5 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/releases-tab/releases-tab.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/releases-tab/releases-tab.component.spec.ts @@ -1,6 +1,6 @@ import { DatePipe } from '@angular/common'; import { async, TestBed } from '@angular/core/testing'; -import { TabNavService } from 'frontend/packages/core/tab-nav.service'; +import { TabNavService } from 'frontend/packages/core/src/tab-nav.service'; import { KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { HelmReleaseHelperService } from '../release/tabs/helm-release-helper.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-list-config.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-list-config.ts index fe811a1a78..0d0f0307c5 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-list-config.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-list-config.ts @@ -1,5 +1,5 @@ import { Store } from '@ngrx/store'; -import * as moment from 'moment'; +import moment from 'moment'; import { BehaviorSubject, of } from 'rxjs'; import { first } from 'rxjs/operators'; diff --git a/src/jetstream/apikeys.go b/src/jetstream/apikeys.go new file mode 100644 index 0000000000..c9ab110113 --- /dev/null +++ b/src/jetstream/apikeys.go @@ -0,0 +1,92 @@ +package main + +import ( + "errors" + "net/http" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces/config" + "github.com/labstack/echo" + log "github.com/sirupsen/logrus" +) + +func (p *portalProxy) checkIfAPIKeysEnabled(userGUID string) error { + if p.Config.APIKeysEnabled == config.APIKeysConfigEnum.Disabled { + log.Info("API keys are disabled") + return errors.New("API keys are disabled") + } else if p.Config.APIKeysEnabled == config.APIKeysConfigEnum.AdminOnly { + user, err := p.StratosAuthService.GetUser(userGUID) + if err != nil { + return err + } + + if !user.Admin { + log.Info("API keys are disabled for non-admin users") + return errors.New("API keys are disabled for non-admin users") + } + } + + return nil +} + +func (p *portalProxy) addAPIKey(c echo.Context) error { + log.Debug("addAPIKey") + + userGUID := c.Get("user_id").(string) + comment := c.FormValue("comment") + + if len(comment) == 0 { + return echo.NewHTTPError(http.StatusBadRequest, "Comment can't be empty") + } + + if err := p.checkIfAPIKeysEnabled(userGUID); err != nil { + return echo.NewHTTPError(http.StatusForbidden, err.Error()) + } + + apiKey, err := p.APIKeysRepository.AddAPIKey(userGUID, comment) + if err != nil { + log.Errorf("Error adding API key: %v", err) + return errors.New("Error adding API key") + } + + return c.JSON(http.StatusOK, apiKey) +} + +func (p *portalProxy) listAPIKeys(c echo.Context) error { + log.Debug("listAPIKeys") + + userGUID := c.Get("user_id").(string) + + if err := p.checkIfAPIKeysEnabled(userGUID); err != nil { + return echo.NewHTTPError(http.StatusForbidden, err.Error()) + } + + apiKeys, err := p.APIKeysRepository.ListAPIKeys(userGUID) + if err != nil { + log.Errorf("Error listing API keys: %v", err) + return errors.New("Error listing API keys") + } + + return c.JSON(http.StatusOK, apiKeys) +} + +func (p *portalProxy) deleteAPIKey(c echo.Context) error { + log.Debug("deleteAPIKey") + + userGUID := c.Get("user_id").(string) + keyGUID := c.FormValue("guid") + + if len(keyGUID) == 0 { + return echo.NewHTTPError(http.StatusBadRequest, "API key guid can't be empty") + } + + if err := p.checkIfAPIKeysEnabled(userGUID); err != nil { + return echo.NewHTTPError(http.StatusForbidden, err.Error()) + } + + if err := p.APIKeysRepository.DeleteAPIKey(userGUID, keyGUID); err != nil { + log.Errorf("Error deleting API key: %v", err) + return errors.New("Error deleting API key") + } + + return nil +} diff --git a/src/jetstream/apikeys_test.go b/src/jetstream/apikeys_test.go new file mode 100644 index 0000000000..3e7e952f4d --- /dev/null +++ b/src/jetstream/apikeys_test.go @@ -0,0 +1,389 @@ +package main + +import ( + "encoding/json" + "errors" + "net/http" + "testing" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/apikeys" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces/config" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/mock_interfaces" + "github.com/golang/mock/gomock" + "github.com/labstack/echo" + log "github.com/sirupsen/logrus" + . "github.com/smartystreets/goconvey/convey" +) + +func Test_addAPIKey(t *testing.T) { + t.Parallel() + + // disabling logging noise + log.SetLevel(log.PanicLevel) + + Convey("Given a request to add an API key", t, func() { + userID := "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" + + ctrl := gomock.NewController(t) + mockAPIRepo := apikeys.NewMockRepository(ctrl) + mockStratosAuth := mock_interfaces.NewMockStratosAuth(ctrl) + pp := makeMockServer(mockAPIRepo, mockStratosAuth) + defer ctrl.Finish() + defer pp.DatabaseConnectionPool.Close() + + Convey("when API keys are disabled", func() { + pp.Config.APIKeysEnabled = config.APIKeysConfigEnum.Disabled + + Convey("when API key comment is present", func() { + comment := "Test API key" + ctx, _ := makeNewRequestWithParams("POST", map[string]string{"comment": comment}) + ctx.Set("user_id", userID) + + err := pp.addAPIKey(ctx) + + Convey("should return an error", func() { + So(err, ShouldResemble, echo.NewHTTPError(http.StatusForbidden, "API keys are disabled")) + }) + }) + }) + + Convey("when API keys are enabled for admin users", func() { + pp.Config.APIKeysEnabled = config.APIKeysConfigEnum.AdminOnly + + Convey("when a StratosAuth error occurs", func() { + comment := "Test API key" + ctx, _ := makeNewRequestWithParams("POST", map[string]string{"comment": comment}) + ctx.Set("user_id", userID) + + mockStratosAuth. + EXPECT(). + GetUser(gomock.Eq(userID)). + Return(nil, errors.New("Something went wrong")) + + err := pp.addAPIKey(ctx) + + Convey("should return an error", func() { + So(err, ShouldResemble, echo.NewHTTPError(http.StatusForbidden, "Something went wrong")) + }) + }) + + Convey("when user is not an admin", func() { + Convey("when API key comment is present", func() { + comment := "Test API key" + ctx, _ := makeNewRequestWithParams("POST", map[string]string{"comment": comment}) + ctx.Set("user_id", userID) + + connectedUser := &interfaces.ConnectedUser{ + GUID: userID, + Admin: false, + } + + mockStratosAuth. + EXPECT(). + GetUser(gomock.Eq(userID)). + Return(connectedUser, nil) + + err := pp.addAPIKey(ctx) + + Convey("should return an error", func() { + So(err, ShouldResemble, echo.NewHTTPError(http.StatusForbidden, "API keys are disabled for non-admin users")) + }) + }) + }) + + Convey("when user is an admin", func() { + Convey("when API key comment is present", func() { + comment := "Test API key" + retval := interfaces.APIKey{UserGUID: userID, Comment: comment} + + ctx, rec := makeNewRequestWithParams("POST", map[string]string{"comment": comment}) + ctx.Set("user_id", userID) + + connectedUser := &interfaces.ConnectedUser{ + GUID: userID, + Admin: true, + } + + mockStratosAuth. + EXPECT(). + GetUser(gomock.Eq(userID)). + Return(connectedUser, nil) + + mockAPIRepo. + EXPECT(). + AddAPIKey(gomock.Eq(userID), gomock.Eq(comment)). + Return(&retval, nil) + + err := pp.addAPIKey(ctx) + + var data map[string]interface{} + if jsonErr := json.Unmarshal(rec.Body.Bytes(), &data); jsonErr != nil { + panic(jsonErr) + } + + Convey("there should be no error", func() { + So(err, ShouldBeNil) + }) + + Convey("should return HTTP code 200", func() { + So(rec.Code, ShouldEqual, 200) + }) + + Convey("API key user_guid should equal context user", func() { + So(data["user_guid"], ShouldEqual, userID) + }) + + Convey("API key comment should equal request comment", func() { + So(data["comment"], ShouldEqual, comment) + }) + + Convey("API key last_used should be nil", func() { + So(data["last_used"], ShouldBeNil) + }) + }) + }) + }) + + Convey("when API keys are enabled", func() { + pp.Config.APIKeysEnabled = config.APIKeysConfigEnum.AllUsers + + Convey("when comment is not specified", func() { + ctx, _ := makeNewRequest() + ctx.Set("user_id", userID) + + err := pp.addAPIKey(ctx) + + Convey("should return an error", func() { + So(err, ShouldResemble, echo.NewHTTPError(http.StatusBadRequest, "Comment can't be empty")) + }) + }) + + Convey("when a DB error occurs", func() { + comment := "Test API key" + + mockAPIRepo. + EXPECT(). + AddAPIKey(gomock.Eq(userID), gomock.Eq(comment)). + Return(nil, errors.New("Something went wrong")) + + ctx, _ := makeNewRequestWithParams("POST", map[string]string{"comment": comment}) + ctx.Set("user_id", userID) + + err := pp.addAPIKey(ctx) + + Convey("should return an error", func() { + So(err, ShouldResemble, errors.New("Error adding API key")) + }) + }) + + Convey("when API key comment is present", func() { + comment := "Test API key" + retval := interfaces.APIKey{UserGUID: userID, Comment: comment} + + mockAPIRepo. + EXPECT(). + AddAPIKey(gomock.Eq(userID), gomock.Eq(comment)). + Return(&retval, nil) + + ctx, rec := makeNewRequestWithParams("POST", map[string]string{"comment": comment}) + ctx.Set("user_id", userID) + + err := pp.addAPIKey(ctx) + + var data map[string]interface{} + if jsonErr := json.Unmarshal(rec.Body.Bytes(), &data); jsonErr != nil { + panic(jsonErr) + } + + Convey("there should be no error", func() { + So(err, ShouldBeNil) + }) + + Convey("should return HTTP code 200", func() { + So(rec.Code, ShouldEqual, 200) + }) + + Convey("API key user_guid should equal context user", func() { + So(data["user_guid"], ShouldEqual, userID) + }) + + Convey("API key comment should equal request comment", func() { + So(data["comment"], ShouldEqual, comment) + }) + + Convey("API key last_used should be nil", func() { + So(data["last_used"], ShouldBeNil) + }) + }) + }) + }) +} + +func Test_listAPIKeys(t *testing.T) { + t.Parallel() + + // disabling logging noise + log.SetLevel(log.PanicLevel) + + ctrl := gomock.NewController(t) + mockAPIRepo := apikeys.NewMockRepository(ctrl) + mockStratosAuth := mock_interfaces.NewMockStratosAuth(ctrl) + pp := makeMockServer(mockAPIRepo, mockStratosAuth) + defer ctrl.Finish() + defer pp.DatabaseConnectionPool.Close() + + Convey("Given a request to list API keys", t, func() { + userID := "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" + + Convey("When API keys are disabled", func() { + pp.Config.APIKeysEnabled = config.APIKeysConfigEnum.Disabled + + ctx, _ := makeNewRequest() + ctx.Set("user_id", userID) + + err := pp.listAPIKeys(ctx) + + Convey("should return an error", func() { + So(err, ShouldResemble, echo.NewHTTPError(http.StatusForbidden, "API keys are disabled")) + }) + }) + + Convey("When API keys are enabled", func() { + pp.Config.APIKeysEnabled = config.APIKeysConfigEnum.AllUsers + + Convey("when a DB error occurs", func() { + mockAPIRepo. + EXPECT(). + ListAPIKeys(gomock.Eq(userID)). + Return(nil, errors.New("Something went wrong")) + + ctx, _ := makeNewRequest() + ctx.Set("user_id", userID) + + err := pp.listAPIKeys(ctx) + + Convey("should return an error", func() { + So(err, ShouldResemble, errors.New("Error listing API keys")) + }) + }) + + Convey("when DB no errors occur", func() { + r1 := &interfaces.APIKey{ + GUID: "00000000-0000-0000-0000-000000000000", + Secret: "", + UserGUID: userID, + Comment: "First key", + LastUsed: nil, + } + + r2 := &interfaces.APIKey{ + GUID: "11111111-1111-1111-1111-111111111111", + Secret: "", + UserGUID: userID, + Comment: "Second key", + LastUsed: nil, + } + + retval := []interfaces.APIKey{*r1, *r2} + + mockAPIRepo. + EXPECT(). + ListAPIKeys(gomock.Eq(userID)). + Return(retval, nil) + + ctx, rec := makeNewRequest() + ctx.Set("user_id", userID) + + err := pp.listAPIKeys(ctx) + + Convey("there should be no error", func() { + So(err, ShouldBeNil) + }) + + Convey("return valid JSON", func() { + So(rec.Body.String(), ShouldEqual, jsonMust(retval)+"\n") + }) + }) + }) + }) +} + +func Test_deleteAPIKeys(t *testing.T) { + t.Parallel() + + // disabling logging noise + log.SetLevel(log.PanicLevel) + + ctrl := gomock.NewController(t) + mockAPIRepo := apikeys.NewMockRepository(ctrl) + mockStratosAuth := mock_interfaces.NewMockStratosAuth(ctrl) + pp := makeMockServer(mockAPIRepo, mockStratosAuth) + defer ctrl.Finish() + defer pp.DatabaseConnectionPool.Close() + + userID := "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" + keyID := "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" + + Convey("Given a request to delete an API key", t, func() { + Convey("when API keys are disabled", func() { + pp.Config.APIKeysEnabled = config.APIKeysConfigEnum.Disabled + + ctx, _ := makeNewRequestWithParams("POST", map[string]string{"guid": keyID}) + ctx.Set("user_id", userID) + + err := pp.deleteAPIKey(ctx) + + Convey("should return an error", func() { + So(err, ShouldResemble, echo.NewHTTPError(http.StatusForbidden, "API keys are disabled")) + }) + }) + + Convey("when API keys are enabled", func() { + pp.Config.APIKeysEnabled = config.APIKeysConfigEnum.AllUsers + + Convey("when no API key GUID is supplied", func() { + ctx, _ := makeNewRequest() + ctx.Set("user_id", userID) + + err := pp.deleteAPIKey(ctx) + + Convey("should return an error", func() { + So(err, ShouldResemble, echo.NewHTTPError(http.StatusBadRequest, "API key guid can't be empty")) + }) + }) + + Convey("when an error occured during API key deletion", func() { + mockAPIRepo. + EXPECT(). + DeleteAPIKey(gomock.Eq(userID), gomock.Eq(keyID)). + Return(errors.New("Something went wrong")) + + ctx, _ := makeNewRequestWithParams("POST", map[string]string{"guid": keyID}) + ctx.Set("user_id", userID) + + err := pp.deleteAPIKey(ctx) + + Convey("should return an error", func() { + So(err, ShouldResemble, errors.New("Error deleting API key")) + }) + }) + + Convey("when an API key is deleted succesfully", func() { + mockAPIRepo. + EXPECT(). + DeleteAPIKey(gomock.Eq(userID), gomock.Eq(keyID)). + Return(nil) + + ctx, _ := makeNewRequestWithParams("POST", map[string]string{"guid": keyID}) + ctx.Set("user_id", userID) + + err := pp.deleteAPIKey(ctx) + + Convey("there should be no error", func() { + So(err, ShouldBeNil) + }) + }) + }) + }) +} diff --git a/src/jetstream/auth_test.go b/src/jetstream/auth_test.go index b7657fbede..f2c72f7cd0 100644 --- a/src/jetstream/auth_test.go +++ b/src/jetstream/auth_test.go @@ -784,9 +784,9 @@ func TestVerifySession(t *testing.T) { So(contentType, ShouldEqual, "application/json; charset=UTF-8") }) - var expectedScopes = "\"scopes\":[\"openid\",\"scim.read\",\"cloud_controller.admin\",\"uaa.user\",\"cloud_controller.read\",\"password.write\",\"routing.router_groups.read\",\"cloud_controller.write\",\"doppler.firehose\",\"scim.write\"]" + var expectedScopes = `"scopes":["openid","scim.read","cloud_controller.admin","uaa.user","cloud_controller.read","password.write","routing.router_groups.read","cloud_controller.write","doppler.firehose","scim.write"]` - var expectedBody = "{\"version\":{\"proxy_version\":\"dev\",\"database_version\":20161117141922},\"user\":{\"guid\":\"asd-gjfg-bob\",\"name\":\"admin\",\"admin\":false," + expectedScopes + "},\"endpoints\":{\"cf\":{}},\"plugins\":null,\"config\":{\"enableTechPreview\":false}}" + var expectedBody = `{"version":{"proxy_version":"dev","database_version":20161117141922},"user":{"guid":"asd-gjfg-bob","name":"admin","admin":false,` + expectedScopes + `},"endpoints":{"cf":{}},"plugins":null,"config":{"enableTechPreview":false,"APIKeysEnabled":"admin_only"}}` Convey("Should contain expected body", func() { So(res, ShouldNotBeNil) diff --git a/src/jetstream/config.dev b/src/jetstream/config.dev index 9b98a9b21b..27ec166bf3 100644 --- a/src/jetstream/config.dev +++ b/src/jetstream/config.dev @@ -54,5 +54,8 @@ LOCAL_USER=admin LOCAL_USER_PASSWORD=admin LOCAL_USER_SCOPE=stratos.admin +# Enable/disable API key-based access to Stratos API (disabled, admin_only, all_users). Default is admin_only +#API_KEYS_ENABLED=admin_only + # Cache folder for Helm Charts HELM_CACHE_FOLDER=./.helm-cache diff --git a/src/jetstream/datastore/20200814140918_ApiKeys.go b/src/jetstream/datastore/20200814140918_ApiKeys.go new file mode 100644 index 0000000000..2a00b44365 --- /dev/null +++ b/src/jetstream/datastore/20200814140918_ApiKeys.go @@ -0,0 +1,22 @@ +package datastore + +import ( + "database/sql" + + "bitbucket.org/liamstask/goose/lib/goose" +) + +func init() { + RegisterMigration(20200814140918, "ApiKeys", func(txn *sql.Tx, conf *goose.DBConf) error { + apiTokenTable := "CREATE TABLE IF NOT EXISTS api_keys (" + apiTokenTable += "guid VARCHAR(36) NOT NULL UNIQUE," + apiTokenTable += "secret VARCHAR(36) NOT NULL UNIQUE," + apiTokenTable += "user_guid VARCHAR(36) NOT NULL," + apiTokenTable += "comment VARCHAR(255) NOT NULL," + apiTokenTable += "last_used TIMESTAMP," + apiTokenTable += "PRIMARY KEY (guid) );" + + _, err := txn.Exec(apiTokenTable) + return err + }) +} diff --git a/src/jetstream/go.mod b/src/jetstream/go.mod index 9c59189023..4ca6a15bdc 100644 --- a/src/jetstream/go.mod +++ b/src/jetstream/go.mod @@ -35,6 +35,7 @@ require ( github.com/elazarl/goproxy/ext v0.0.0-20200315184450-1f3cb6622dad // indirect github.com/fatih/color v1.7.0 // indirect github.com/go-sql-driver/mysql v1.5.0 + github.com/golang/mock v1.2.0 github.com/golang/snappy v0.0.1 // indirect github.com/google/go-querystring v1.0.0 // indirect github.com/google/martian v2.1.0+incompatible diff --git a/src/jetstream/go.sum b/src/jetstream/go.sum index 219e52d2ec..32e2f9c513 100644 --- a/src/jetstream/go.sum +++ b/src/jetstream/go.sum @@ -236,6 +236,7 @@ github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tF github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= @@ -256,7 +257,10 @@ github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4er github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= @@ -596,6 +600,7 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190225153610-fe579d43d832/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -654,9 +659,11 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= diff --git a/src/jetstream/info.go b/src/jetstream/info.go index 2000460cc5..0c4cef7d57 100644 --- a/src/jetstream/info.go +++ b/src/jetstream/info.go @@ -59,6 +59,7 @@ func (p *portalProxy) getInfo(c echo.Context) (*interfaces.Info, error) { s.Configuration.TechPreview = p.Config.EnableTechPreview s.Configuration.ListMaxSize = p.Config.UIListMaxSize s.Configuration.ListAllowLoadMaxed = p.Config.UIListAllowLoadMaxed + s.Configuration.APIKeysEnabled = string(p.Config.APIKeysEnabled) // Only add diagnostics information if the user is an admin if uaaUser.Admin { diff --git a/src/jetstream/main.go b/src/jetstream/main.go index 955f6f0000..fcacdf4cdf 100644 --- a/src/jetstream/main.go +++ b/src/jetstream/main.go @@ -37,6 +37,7 @@ import ( "github.com/cloudfoundry-incubator/stratos/src/jetstream/crypto" "github.com/cloudfoundry-incubator/stratos/src/jetstream/datastore" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/apikeys" "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/cnsis" "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/console_config" "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" @@ -178,6 +179,7 @@ func main() { console_config.InitRepositoryProvider(dc.DatabaseProvider) localusers.InitRepositoryProvider(dc.DatabaseProvider) sessiondata.InitRepositoryProvider(dc.DatabaseProvider) + apikeys.InitRepositoryProvider(dc.DatabaseProvider) // Establish a Postgresql connection pool databaseConnectionPool, migratorConf, err := initConnPool(dc, envLookup) @@ -657,6 +659,12 @@ func newPortalProxy(pc interfaces.PortalConfig, dcp *sql.DB, ss HttpSessionStore log.Infof("Session Cookie name: %s", cookieName) + // Setting default value for APIKeysEnabled + if pc.APIKeysEnabled == "" { + log.Debug(`APIKeysEnabled not set, setting to "admin_only"`) + pc.APIKeysEnabled = config.APIKeysConfigEnum.AdminOnly + } + pp := &portalProxy{ Config: pc, DatabaseConnectionPool: dcp, @@ -681,6 +689,12 @@ func newPortalProxy(pc interfaces.PortalConfig, dcp *sql.DB, ss HttpSessionStore Handler: pp.DoOidcFlowRequest, }) + var err error + pp.APIKeysRepository, err = apikeys.NewPgsqlAPIKeysRepository(pp.DatabaseConnectionPool) + if err != nil { + panic(fmt.Errorf("Can't initialize APIKeysRepository: %v", err)) + } + return pp } @@ -905,8 +919,19 @@ func (p *portalProxy) registerRoutes(e *echo.Echo, needSetupMiddleware bool) { // All routes in the session group need the user to be authenticated sessionGroup := pp.Group("/v1") - sessionGroup.Use(p.sessionMiddleware) - sessionGroup.Use(p.xsrfMiddleware) + sessionGroup.Use(p.sessionMiddleware()) + sessionGroup.Use(p.xsrfMiddleware()) + + sessionGroup.POST("/api_keys", p.addAPIKey) + sessionGroup.GET("/api_keys", p.listAPIKeys) + sessionGroup.DELETE("/api_keys", p.deleteAPIKey) + + apiKeyGroupConfig := MiddlewareConfig{Skipper: p.apiKeySkipper} + + apiKeyGroup := pp.Group("/v1") + apiKeyGroup.Use(p.apiKeyMiddleware) + apiKeyGroup.Use(p.sessionMiddlewareWithConfig(apiKeyGroupConfig)) + apiKeyGroup.Use(p.xsrfMiddlewareWithConfig(apiKeyGroupConfig)) for _, plugin := range p.Plugins { middlewarePlugin, err := plugin.GetMiddlewarePlugin() @@ -932,8 +957,8 @@ func (p *portalProxy) registerRoutes(e *echo.Echo, needSetupMiddleware bool) { sessionAuthGroup.GET("/session/verify", p.verifySession) // CNSI operations - sessionGroup.GET("/cnsis", p.listCNSIs) - sessionGroup.GET("/cnsis/registered", p.listRegisteredCNSIs) + apiKeyGroup.GET("/cnsis", p.listCNSIs) + apiKeyGroup.GET("/cnsis/registered", p.listRegisteredCNSIs) // Info sessionGroup.GET("/info", p.info) diff --git a/src/jetstream/middleware.go b/src/jetstream/middleware.go index 723ed2ad49..446677f225 100644 --- a/src/jetstream/middleware.go +++ b/src/jetstream/middleware.go @@ -2,6 +2,7 @@ package main import ( "crypto/subtle" + "database/sql" "errors" "fmt" "net/http" @@ -15,6 +16,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces/config" ) const cfSessionCookieName = "JSESSIONID" @@ -28,6 +30,15 @@ const StratosSSOHeader = "x-stratos-sso-login" // Header to communicate any error during SSO const StratosSSOErrorHeader = "x-stratos-sso-error" +// APIKeySkipperContextKey - name of a context key that indicates that valid API key was supplied +const APIKeySkipperContextKey = "valid_api_key" + +// APIKeyHeader - API key authentication header name +const APIKeyHeader = "Authentication" + +// APIKeyAuthScheme - API key authentication scheme +const APIKeyAuthScheme = "Bearer" + func handleSessionError(config interfaces.PortalConfig, c echo.Context, err error, doNotLog bool, msg string) error { log.Debug("handleSessionError") @@ -65,75 +76,118 @@ func handleSessionError(config interfaces.PortalConfig, c echo.Context, err erro ) } -func (p *portalProxy) sessionMiddleware(h echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - log.Debug("sessionMiddleware") +type ( + // Skipper - skipper function for middlewares + Skipper func(echo.Context) bool - p.removeEmptyCookie(c) + // MiddlewareConfig defines the config for the middleware. + MiddlewareConfig struct { + // Skipper defines a function to skip middleware. + Skipper Skipper + } +) - userID, err := p.GetSessionValue(c, "user_id") - if err == nil { - c.Set("user_id", userID) - return h(c) - } +func (p *portalProxy) sessionMiddleware() echo.MiddlewareFunc { - // Don't log an error if we are verifying the session, as a failure is not an error - isVerify := strings.HasSuffix(c.Request().RequestURI, "/auth/session/verify") - if isVerify { - // Tell the frontend what the Cookie Domain is so it can check if sessions will work - c.Response().Header().Set(StratosDomainHeader, p.Config.CookieDomain) - } + return p.sessionMiddlewareWithConfig(MiddlewareConfig{}) +} - // Clear any session cookie - cookie := new(http.Cookie) - cookie.Name = p.SessionCookieName - cookie.Value = "" - cookie.Expires = time.Now().Add(-24 * time.Hour) - cookie.Domain = p.SessionStoreOptions.Domain - cookie.HttpOnly = p.SessionStoreOptions.HttpOnly - cookie.Secure = p.SessionStoreOptions.Secure - cookie.Path = p.SessionStoreOptions.Path - cookie.MaxAge = 0 - c.SetCookie(cookie) - - return handleSessionError(p.Config, c, err, isVerify, "User session could not be found") +func (p *portalProxy) sessionMiddlewareWithConfig(config MiddlewareConfig) echo.MiddlewareFunc { + // Default skipper function always returns false + if config.Skipper == nil { + config.Skipper = func(c echo.Context) bool { return false } } -} -// Support for Angular XSRF -func (p *portalProxy) xsrfMiddleware(h echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - log.Debug("xsrfMiddleware") + return func(h echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + log.Debug("sessionMiddleware") - // Only do this for mutating requests - i.e. we can ignore for GET or HEAD requests - if c.Request().Method == "GET" || c.Request().Method == "HEAD" { - return h(c) - } + if config.Skipper(c) { + log.Debug("Skipping sessionMiddleware") + return h(c) + } - // Routes registered with /apps are assumed to be web apps that do their own XSRF - if strings.HasPrefix(c.Request().URL.String(), "/pp/v1/apps/") { - return h(c) + p.removeEmptyCookie(c) + + userID, err := p.GetSessionValue(c, "user_id") + if err == nil { + c.Set("user_id", userID) + return h(c) + } + + // Don't log an error if we are verifying the session, as a failure is not an error + isVerify := strings.HasSuffix(c.Request().RequestURI, "/auth/session/verify") + if isVerify { + // Tell the frontend what the Cookie Domain is so it can check if sessions will work + c.Response().Header().Set(StratosDomainHeader, p.Config.CookieDomain) + } + + // Clear any session cookie + cookie := new(http.Cookie) + cookie.Name = p.SessionCookieName + cookie.Value = "" + cookie.Expires = time.Now().Add(-24 * time.Hour) + cookie.Domain = p.SessionStoreOptions.Domain + cookie.HttpOnly = p.SessionStoreOptions.HttpOnly + cookie.Secure = p.SessionStoreOptions.Secure + cookie.Path = p.SessionStoreOptions.Path + cookie.MaxAge = 0 + c.SetCookie(cookie) + + return handleSessionError(p.Config, c, err, isVerify, "User session could not be found") } + } +} - errMsg := "Failed to get stored XSRF token from user session" - token, err := p.GetSessionStringValue(c, XSRFTokenSessionName) - if err == nil { - // Check the token against the header - requestToken := c.Request().Header.Get(XSRFTokenHeader) - if len(requestToken) > 0 { - if compareTokens(requestToken, token) { - return h(c) +func (p *portalProxy) xsrfMiddleware() echo.MiddlewareFunc { + return p.xsrfMiddlewareWithConfig(MiddlewareConfig{}) +} + +func (p *portalProxy) xsrfMiddlewareWithConfig(config MiddlewareConfig) echo.MiddlewareFunc { + // Default skipper function always returns false + if config.Skipper == nil { + config.Skipper = func(c echo.Context) bool { return false } + } + + return func(h echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + log.Debug("xsrfMiddleware") + + if config.Skipper(c) { + log.Debug("Skipping xsrfMiddleware") + return h(c) + } + + // Only do this for mutating requests - i.e. we can ignore for GET or HEAD requests + if c.Request().Method == "GET" || c.Request().Method == "HEAD" { + return h(c) + } + + // Routes registered with /apps are assumed to be web apps that do their own XSRF + if strings.HasPrefix(c.Request().URL.String(), "/pp/v1/apps/") { + return h(c) + } + + errMsg := "Failed to get stored XSRF token from user session" + token, err := p.GetSessionStringValue(c, XSRFTokenSessionName) + if err == nil { + // Check the token against the header + requestToken := c.Request().Header.Get(XSRFTokenHeader) + if len(requestToken) > 0 { + if compareTokens(requestToken, token) { + return h(c) + } + errMsg = "Supplied XSRF Token does not match" + } else { + errMsg = "XSRF Token was not supplied in the header" } - errMsg = "Supplied XSRF Token does not match" - } else { - errMsg = "XSRF Token was not supplied in the header" } + return interfaces.NewHTTPShadowError( + http.StatusUnauthorized, + "XSRF Token could not be found or does not match", + "XSRF Token error: %s", errMsg, + ) } - return interfaces.NewHTTPShadowError( - http.StatusUnauthorized, - "XSRF Token could not be found or does not match", - "XSRF Token error: %s", errMsg, - ) } } @@ -254,3 +308,77 @@ func retryAfterUpgradeMiddleware(h echo.HandlerFunc, env *env.VarSet) echo.Handl return h(c) } } + +func getAPIKeyFromHeader(c echo.Context) (string, error) { + header := c.Request().Header.Get(APIKeyHeader) + + l := len(APIKeyAuthScheme) + if len(header) > l+1 && header[:l] == APIKeyAuthScheme { + return header[l+1:], nil + } + + return "", errors.New("No API key in the header") +} + +func (p *portalProxy) apiKeyMiddleware(h echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + log.Debug("apiKeyMiddleware") + + // skipping thise middleware if API keys are disabled + if p.Config.APIKeysEnabled == config.APIKeysConfigEnum.Disabled { + log.Debugf("apiKeyMiddleware: API keys are disabled, skipping") + return h(c) + } + + apiKeySecret, err := getAPIKeyFromHeader(c) + if err != nil { + log.Debugf("apiKeyMiddleware: %v", err) + return h(c) + } + + apiKey, err := p.APIKeysRepository.GetAPIKeyBySecret(apiKeySecret) + if err != nil { + switch { + case err == sql.ErrNoRows: + log.Debug("apiKeyMiddleware: Invalid API key supplied") + default: + log.Errorf("apiKeyMiddleware: %v", err) + } + + return h(c) + } + + // checking if user is an admin if API keys are enabled for admins only + if p.Config.APIKeysEnabled == config.APIKeysConfigEnum.AdminOnly { + user, err := p.StratosAuthService.GetUser(apiKey.UserGUID) + if err != nil { + log.Errorf("apiKeyMiddleware: %v", err) + return h(c) + } + + if !user.Admin { + log.Debugf("apiKeyMiddleware: user isn't admin, skipping") + return h(c) + } + } + + c.Set(APIKeySkipperContextKey, true) + c.Set("user_id", apiKey.UserGUID) + + // some endpoints check not only the context store, but also the contents of the session store + sessionValues := make(map[string]interface{}) + sessionValues["user_id"] = apiKey.UserGUID + p.setSessionValues(c, sessionValues) + + err = p.APIKeysRepository.UpdateAPIKeyLastUsed(apiKey.GUID) + if err != nil { + log.Errorf("apiKeyMiddleware: %v", err) + } + + return h(c) + } +} + +func (p *portalProxy) apiKeySkipper(c echo.Context) bool { + return c.Get(APIKeySkipperContextKey) != nil && c.Get(APIKeySkipperContextKey).(bool) == true +} diff --git a/src/jetstream/middleware_test.go b/src/jetstream/middleware_test.go new file mode 100644 index 0000000000..4a64895137 --- /dev/null +++ b/src/jetstream/middleware_test.go @@ -0,0 +1,378 @@ +package main + +import ( + "database/sql" + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/apikeys" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces/config" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/mock_interfaces" + "github.com/golang/mock/gomock" + "github.com/labstack/echo" + log "github.com/sirupsen/logrus" + . "github.com/smartystreets/goconvey/convey" + sqlmock "gopkg.in/DATA-DOG/go-sqlmock.v1" +) + +func makeMockServer(apiKeysRepo apikeys.Repository, mockStratosAuth interfaces.StratosAuth) *portalProxy { + db, _, dberr := sqlmock.New() + if dberr != nil { + log.Panicf("an error '%s' was not expected when opening a stub database connection", dberr) + } + + pp := setupPortalProxy(db) + pp.DatabaseConnectionPool = db + pp.APIKeysRepository = apiKeysRepo + pp.StratosAuthService = mockStratosAuth + + return pp +} + +func makeNewRequest() (echo.Context, *httptest.ResponseRecorder) { + req := setupMockReq("GET", "", map[string]string{}) + rec := httptest.NewRecorder() + e := echo.New() + ctx := e.NewContext(req, rec) + + return ctx, rec +} + +func makeNewRequestWithParams(httpVerb string, formValues map[string]string) (echo.Context, *httptest.ResponseRecorder) { + req := setupMockReq(httpVerb, "", formValues) + rec := httptest.NewRecorder() + e := echo.New() + ctx := e.NewContext(req, rec) + + return ctx, rec +} + +func Test_apiKeyMiddleware(t *testing.T) { + t.Parallel() + + // disabling logging noise + log.SetLevel(log.PanicLevel) + + ctrl := gomock.NewController(t) + mockAPIRepo := apikeys.NewMockRepository(ctrl) + mockStratosAuth := mock_interfaces.NewMockStratosAuth(ctrl) + pp := makeMockServer(mockAPIRepo, mockStratosAuth) + defer ctrl.Finish() + defer pp.DatabaseConnectionPool.Close() + + handlerFunc := func(c echo.Context) error { + return c.String(http.StatusOK, "test") + } + + middleware := pp.apiKeyMiddleware(handlerFunc) + apiKeySecret := "SecretMcSecretface" + + Convey("when API keys are enabled for all users", t, func() { + pp.Config.APIKeysEnabled = config.APIKeysConfigEnum.AllUsers + + Convey("when headers are present", func() { + Convey("when schema other than Bearer is used", func() { + ctx, rec := makeNewRequest() + ctx.Request().Header.Add("Authentication", "JWT "+apiKeySecret) + + err := middleware(ctx) + + Convey("should not set user_id in the context", func() { + So(ctx.Get("user_id"), ShouldBeNil) + }) + + Convey("should not set skip flag in the context", func() { + So(ctx.Get(APIKeySkipperContextKey), ShouldBeNil) + }) + + Convey("request should be successful", func() { + So(err, ShouldBeNil) + So(rec.Code, ShouldEqual, 200) + So(rec.Body.String(), ShouldEqual, "test") + }) + }) + + Convey("when Bearer schema is used", func() { + Convey("when no matching key is found", func() { + ctx, rec := makeNewRequest() + ctx.Request().Header.Add("Authentication", "Bearer "+apiKeySecret) + + mockAPIRepo. + EXPECT(). + GetAPIKeyBySecret(gomock.Eq(apiKeySecret)). + Return(nil, sql.ErrNoRows) + + err := middleware(ctx) + + Convey("should not set user_id in the context", func() { + So(ctx.Get("user_id"), ShouldBeNil) + }) + + Convey("should not set skip flag in the context", func() { + So(ctx.Get(APIKeySkipperContextKey), ShouldBeNil) + }) + + Convey("request should be successful", func() { + So(err, ShouldBeNil) + So(rec.Code, ShouldEqual, 200) + So(rec.Body.String(), ShouldEqual, "test") + }) + }) + + Convey("when APIKeysRepository returns an error", func() { + ctx, rec := makeNewRequest() + ctx.Request().Header.Add("Authentication", "Bearer "+apiKeySecret) + + mockAPIRepo. + EXPECT(). + GetAPIKeyBySecret(gomock.Eq(apiKeySecret)). + Return(nil, errors.New("Something went wrong")) + + err := middleware(ctx) + + Convey("should not set user_id in the context", func() { + So(ctx.Get("user_id"), ShouldBeNil) + }) + + Convey("should not set skip flag in the context", func() { + So(ctx.Get(APIKeySkipperContextKey), ShouldBeNil) + }) + + Convey("request should be successful", func() { + So(err, ShouldBeNil) + So(rec.Code, ShouldEqual, 200) + So(rec.Body.String(), ShouldEqual, "test") + }) + }) + + Convey("when a matching key is found", func() { + ctx, rec := makeNewRequest() + ctx.Request().Header.Add("Authentication", "Bearer "+apiKeySecret) + + apiKey := &interfaces.APIKey{ + UserGUID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + GUID: "00000000-0000-0000-0000-000000000000", + } + + mockAPIRepo. + EXPECT(). + GetAPIKeyBySecret(gomock.Eq(apiKeySecret)). + Return(apiKey, nil) + + mockAPIRepo. + EXPECT(). + UpdateAPIKeyLastUsed(gomock.Eq(apiKey.GUID)). + Return(nil) + + err := middleware(ctx) + + Convey("should set user_id in the context", func() { + So(ctx.Get("user_id"), ShouldEqual, apiKey.UserGUID) + }) + + Convey("should set skip flag in the context", func() { + So(ctx.Get(APIKeySkipperContextKey), ShouldBeTrue) + }) + + Convey("request should be successful", func() { + So(err, ShouldBeNil) + So(rec.Code, ShouldEqual, 200) + So(rec.Body.String(), ShouldEqual, "test") + }) + }) + + Convey("when a matching key is found, but last_used can't be updated", func() { + ctx, rec := makeNewRequest() + ctx.Request().Header.Add("Authentication", "Bearer "+apiKeySecret) + + apiKey := &interfaces.APIKey{ + UserGUID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + GUID: "00000000-0000-0000-0000-000000000000", + } + + mockAPIRepo. + EXPECT(). + GetAPIKeyBySecret(gomock.Eq(apiKeySecret)). + Return(apiKey, nil) + + mockAPIRepo. + EXPECT(). + UpdateAPIKeyLastUsed(gomock.Eq(apiKey.GUID)). + Return(errors.New("Something went wrong")) + + err := middleware(ctx) + + Convey("should set user_id in the context", func() { + So(ctx.Get("user_id"), ShouldEqual, apiKey.UserGUID) + }) + + Convey("should set skip flag in the context", func() { + So(ctx.Get(APIKeySkipperContextKey), ShouldBeTrue) + }) + + Convey("request should be successful", func() { + So(err, ShouldBeNil) + So(rec.Code, ShouldEqual, 200) + So(rec.Body.String(), ShouldEqual, "test") + }) + }) + + }) + }) + }) + + Convey("when API keys are enabled for all admins only", t, func() { + pp.Config.APIKeysEnabled = config.APIKeysConfigEnum.AdminOnly + + Convey("when a matching key belongs a non-admin user", func() { + ctx, rec := makeNewRequest() + ctx.Request().Header.Add("Authentication", "Bearer "+apiKeySecret) + + apiKey := &interfaces.APIKey{ + UserGUID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + GUID: "00000000-0000-0000-0000-000000000000", + } + + connectedUser := &interfaces.ConnectedUser{ + GUID: apiKey.UserGUID, + Admin: false, + } + + mockAPIRepo. + EXPECT(). + GetAPIKeyBySecret(gomock.Eq(apiKeySecret)). + Return(apiKey, nil) + + mockStratosAuth. + EXPECT(). + GetUser(gomock.Eq(apiKey.UserGUID)). + Return(connectedUser, nil) + + err := middleware(ctx) + + Convey("should not set user_id in the context", func() { + So(ctx.Get("user_id"), ShouldBeNil) + }) + + Convey("should not set skip flag in the context", func() { + So(ctx.Get(APIKeySkipperContextKey), ShouldBeNil) + }) + + Convey("request should be successful", func() { + So(err, ShouldBeNil) + So(rec.Code, ShouldEqual, 200) + So(rec.Body.String(), ShouldEqual, "test") + }) + }) + + Convey("when a matching key belongs an admin user", func() { + ctx, rec := makeNewRequest() + ctx.Request().Header.Add("Authentication", "Bearer "+apiKeySecret) + + apiKey := &interfaces.APIKey{ + UserGUID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + GUID: "00000000-0000-0000-0000-000000000000", + } + + connectedUser := &interfaces.ConnectedUser{ + GUID: apiKey.UserGUID, + Admin: true, + } + + mockAPIRepo. + EXPECT(). + GetAPIKeyBySecret(gomock.Eq(apiKeySecret)). + Return(apiKey, nil) + + mockAPIRepo. + EXPECT(). + UpdateAPIKeyLastUsed(gomock.Eq(apiKey.GUID)). + Return(nil) + + mockStratosAuth. + EXPECT(). + GetUser(gomock.Eq(apiKey.UserGUID)). + Return(connectedUser, nil) + + err := middleware(ctx) + + Convey("should set user_id in the context", func() { + So(ctx.Get("user_id"), ShouldEqual, apiKey.UserGUID) + }) + + Convey("should set skip flag in the context", func() { + So(ctx.Get(APIKeySkipperContextKey), ShouldBeTrue) + }) + + Convey("request should be successful", func() { + So(err, ShouldBeNil) + So(rec.Code, ShouldEqual, 200) + So(rec.Body.String(), ShouldEqual, "test") + }) + }) + + Convey("when StratosAuth.GetUser returns an error", func() { + ctx, rec := makeNewRequest() + ctx.Request().Header.Add("Authentication", "Bearer "+apiKeySecret) + + apiKey := &interfaces.APIKey{ + UserGUID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + GUID: "00000000-0000-0000-0000-000000000000", + } + + mockAPIRepo. + EXPECT(). + GetAPIKeyBySecret(gomock.Eq(apiKeySecret)). + Return(apiKey, nil) + + mockStratosAuth. + EXPECT(). + GetUser(gomock.Eq(apiKey.UserGUID)). + Return(nil, errors.New("Something went wrong")) + + err := middleware(ctx) + + Convey("should not set user_id in the context", func() { + So(ctx.Get("user_id"), ShouldBeNil) + }) + + Convey("should not set skip flag in the context", func() { + So(ctx.Get(APIKeySkipperContextKey), ShouldBeNil) + }) + + Convey("request should be successful", func() { + So(err, ShouldBeNil) + So(rec.Code, ShouldEqual, 200) + So(rec.Body.String(), ShouldEqual, "test") + }) + }) + }) + + Convey("when API keys are disabled", t, func() { + pp.Config.APIKeysEnabled = config.APIKeysConfigEnum.Disabled + + Convey("when an API key header is supplied", func() { + ctx, rec := makeNewRequest() + ctx.Request().Header.Add("Authentication", "Bearer "+apiKeySecret) + + err := middleware(ctx) + + Convey("should not set user_id in the context", func() { + So(ctx.Get("user_id"), ShouldBeNil) + }) + + Convey("should not set skip flag in the context", func() { + So(ctx.Get(APIKeySkipperContextKey), ShouldBeNil) + }) + + Convey("request should be successful", func() { + So(err, ShouldBeNil) + So(rec.Code, ShouldEqual, 200) + So(rec.Body.String(), ShouldEqual, "test") + }) + }) + }) +} diff --git a/src/jetstream/plugins/metrics/main.go b/src/jetstream/plugins/metrics/main.go index 535e52706d..ac3bb7e7f1 100644 --- a/src/jetstream/plugins/metrics/main.go +++ b/src/jetstream/plugins/metrics/main.go @@ -388,7 +388,11 @@ func compareURL(a, b string) bool { aPort := getPort(ua) bPort := getPort(ub) - return ua.Scheme == ub.Scheme && ua.Hostname() == ub.Hostname() && aPort == bPort && ua.Path == ub.Path + + aPath := trimPath(ua.Path) + bPath := trimPath(ub.Path) + + return ua.Scheme == ub.Scheme && ua.Hostname() == ub.Hostname() && aPort == bPort && aPath == bPath } func getPort(u *url.URL) string { @@ -407,6 +411,13 @@ func getPort(u *url.URL) string { return port } +func trimPath(path string) string { + if strings.HasSuffix(path, "/") { + return path[:len(path)-1] + } + return path +} + func (m *MetricsSpecification) getMetricsEndpoints(userGUID string, cnsiList []string) (map[string]EndpointMetricsRelation, error) { metricsProviders := make([]MetricsMetadata, 0) diff --git a/src/jetstream/plugins/metrics/main_test.go b/src/jetstream/plugins/metrics/main_test.go index 7b57d89cab..d02b5d80aa 100644 --- a/src/jetstream/plugins/metrics/main_test.go +++ b/src/jetstream/plugins/metrics/main_test.go @@ -23,6 +23,9 @@ func TestUrlComparision(t *testing.T) { So(compareURL("http://test.com/a", "http://test.com/a"), ShouldBeTrue) So(compareURL("http://test.com/a?one=two", "http://test.com/a?two=one"), ShouldBeTrue) + // Should ignore trailing slashes + So(compareURL("http://test.com/", "http://test.com"), ShouldBeTrue) + }) } diff --git a/src/jetstream/portal_proxy.go b/src/jetstream/portal_proxy.go index 7ed43b1022..4dc8ead88b 100644 --- a/src/jetstream/portal_proxy.go +++ b/src/jetstream/portal_proxy.go @@ -5,6 +5,7 @@ import ( "regexp" "time" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/apikeys" "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" "github.com/gorilla/sessions" "github.com/govau/cf-common/env" @@ -24,6 +25,7 @@ type portalProxy struct { AuthProviders map[string]interfaces.AuthProvider env *env.VarSet StratosAuthService interfaces.StratosAuth + APIKeysRepository apikeys.Repository } // HttpSessionStore - Interface for a store that can manage HTTP Sessions diff --git a/src/jetstream/repository/apikeys/apikeys.go b/src/jetstream/repository/apikeys/apikeys.go new file mode 100644 index 0000000000..bf783990a0 --- /dev/null +++ b/src/jetstream/repository/apikeys/apikeys.go @@ -0,0 +1,12 @@ +package apikeys + +import "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" + +// Repository - API keys repository +type Repository interface { + AddAPIKey(userID string, comment string) (*interfaces.APIKey, error) + GetAPIKeyBySecret(keySecret string) (*interfaces.APIKey, error) + ListAPIKeys(userID string) ([]interfaces.APIKey, error) + DeleteAPIKey(userGUID string, keyGUID string) error + UpdateAPIKeyLastUsed(keyGUID string) error +} diff --git a/src/jetstream/repository/apikeys/mock_apikeys.go b/src/jetstream/repository/apikeys/mock_apikeys.go new file mode 100644 index 0000000000..d507eb5991 --- /dev/null +++ b/src/jetstream/repository/apikeys/mock_apikeys.go @@ -0,0 +1,107 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: apikeys.go + +// Package apikeys is a generated GoMock package. +package apikeys + +import ( + interfaces "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" + gomock "github.com/golang/mock/gomock" + reflect "reflect" +) + +// MockRepository is a mock of Repository interface +type MockRepository struct { + ctrl *gomock.Controller + recorder *MockRepositoryMockRecorder +} + +// MockRepositoryMockRecorder is the mock recorder for MockRepository +type MockRepositoryMockRecorder struct { + mock *MockRepository +} + +// NewMockRepository creates a new mock instance +func NewMockRepository(ctrl *gomock.Controller) *MockRepository { + mock := &MockRepository{ctrl: ctrl} + mock.recorder = &MockRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { + return m.recorder +} + +// AddAPIKey mocks base method +func (m *MockRepository) AddAPIKey(userID, comment string) (*interfaces.APIKey, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddAPIKey", userID, comment) + ret0, _ := ret[0].(*interfaces.APIKey) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddAPIKey indicates an expected call of AddAPIKey +func (mr *MockRepositoryMockRecorder) AddAPIKey(userID, comment interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddAPIKey", reflect.TypeOf((*MockRepository)(nil).AddAPIKey), userID, comment) +} + +// GetAPIKeyBySecret mocks base method +func (m *MockRepository) GetAPIKeyBySecret(keySecret string) (*interfaces.APIKey, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAPIKeyBySecret", keySecret) + ret0, _ := ret[0].(*interfaces.APIKey) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAPIKeyBySecret indicates an expected call of GetAPIKeyBySecret +func (mr *MockRepositoryMockRecorder) GetAPIKeyBySecret(keySecret interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAPIKeyBySecret", reflect.TypeOf((*MockRepository)(nil).GetAPIKeyBySecret), keySecret) +} + +// ListAPIKeys mocks base method +func (m *MockRepository) ListAPIKeys(userID string) ([]interfaces.APIKey, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListAPIKeys", userID) + ret0, _ := ret[0].([]interfaces.APIKey) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListAPIKeys indicates an expected call of ListAPIKeys +func (mr *MockRepositoryMockRecorder) ListAPIKeys(userID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAPIKeys", reflect.TypeOf((*MockRepository)(nil).ListAPIKeys), userID) +} + +// DeleteAPIKey mocks base method +func (m *MockRepository) DeleteAPIKey(userGUID, keyGUID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteAPIKey", userGUID, keyGUID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteAPIKey indicates an expected call of DeleteAPIKey +func (mr *MockRepositoryMockRecorder) DeleteAPIKey(userGUID, keyGUID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAPIKey", reflect.TypeOf((*MockRepository)(nil).DeleteAPIKey), userGUID, keyGUID) +} + +// UpdateAPIKeyLastUsed mocks base method +func (m *MockRepository) UpdateAPIKeyLastUsed(keyGUID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateAPIKeyLastUsed", keyGUID) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateAPIKeyLastUsed indicates an expected call of UpdateAPIKeyLastUsed +func (mr *MockRepositoryMockRecorder) UpdateAPIKeyLastUsed(keyGUID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAPIKeyLastUsed", reflect.TypeOf((*MockRepository)(nil).UpdateAPIKeyLastUsed), keyGUID) +} diff --git a/src/jetstream/repository/apikeys/psql_apikeys.go b/src/jetstream/repository/apikeys/psql_apikeys.go new file mode 100644 index 0000000000..8e95327aaa --- /dev/null +++ b/src/jetstream/repository/apikeys/psql_apikeys.go @@ -0,0 +1,184 @@ +package apikeys + +import ( + "database/sql" + "encoding/base64" + "errors" + "fmt" + "reflect" + "time" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/crypto" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/datastore" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" + uuid "github.com/satori/go.uuid" + log "github.com/sirupsen/logrus" +) + +var sqlQueries = struct { + InsertAPIKey string + GetAPIKeyBySecret string + ListAPIKeys string + DeleteAPIKey string + UpdateAPIKeyLastUsed string +}{ + InsertAPIKey: `INSERT INTO api_keys (guid, secret, user_guid, comment) VALUES ($1, $2, $3, $4)`, + GetAPIKeyBySecret: `SELECT guid, user_guid, comment, last_used FROM api_keys WHERE secret = $1`, + ListAPIKeys: `SELECT guid, user_guid, comment, last_used FROM api_keys WHERE user_guid = $1`, + DeleteAPIKey: `DELETE FROM api_keys WHERE user_guid = $1 AND guid = $2`, + UpdateAPIKeyLastUsed: `UPDATE api_keys SET last_used = $1 WHERE guid = $2`, +} + +// PgsqlAPIKeysRepository - Postgresql-backed API keys repository +type PgsqlAPIKeysRepository struct { + db *sql.DB +} + +// NewPgsqlAPIKeysRepository - get a reference to the API keys data source +func NewPgsqlAPIKeysRepository(dcp *sql.DB) (Repository, error) { + log.Debug("NewPgsqlAPIKeysRepository") + return &PgsqlAPIKeysRepository{db: dcp}, nil +} + +// InitRepositoryProvider - One time init for the given DB Provider +func InitRepositoryProvider(databaseProvider string) { + // Modify the database statements if needed, for the given database type + // Iterating over the struct to ensure that all of the queries are updated + v := reflect.ValueOf(sqlQueries) + for i := 0; i < v.NumField(); i++ { + q := v.Field(i).Interface().(string) + + reflect. + ValueOf(&sqlQueries). + Elem(). + FieldByIndex([]int{i}). + SetString( + datastore.ModifySQLStatement(q, databaseProvider), + ) + } +} + +// AddAPIKey - Add a new API key to the datastore. +func (p *PgsqlAPIKeysRepository) AddAPIKey(userID string, comment string) (*interfaces.APIKey, error) { + log.Debug("AddAPIKey") + + var err error + + // Validate args + if len(comment) > 255 { + msg := "comment maximum length is 255 characters" + log.Debug(msg) + err = errors.New(msg) + } + + if err != nil { + return nil, err + } + + randomBytes, err := crypto.GenerateRandomBytes(48) + if err != nil { + return nil, err + } + + keyGUID := uuid.NewV4().String() + keySecret := base64.URLEncoding.EncodeToString(randomBytes) + + err = execQuery(p, sqlQueries.InsertAPIKey, keyGUID, keySecret, userID, comment) + if err != nil { + return nil, fmt.Errorf("AddAPIKey: %v", err) + } + + apiKey := &interfaces.APIKey{ + GUID: keyGUID, + Secret: keySecret, + UserGUID: userID, + Comment: comment, + } + + return apiKey, err +} + +// GetAPIKeyBySecret - gets user ID for an API key +func (p *PgsqlAPIKeysRepository) GetAPIKeyBySecret(keySecret string) (*interfaces.APIKey, error) { + log.Debug("GetAPIKeyBySecret") + + var apiKey interfaces.APIKey + + err := p.db.QueryRow(sqlQueries.GetAPIKeyBySecret, keySecret).Scan( + &apiKey.GUID, + &apiKey.UserGUID, + &apiKey.Comment, + &apiKey.LastUsed, + ) + + if err != nil { + return nil, err + } + + return &apiKey, nil +} + +// ListAPIKeys - list API keys for a given user GUID +func (p *PgsqlAPIKeysRepository) ListAPIKeys(userID string) ([]interfaces.APIKey, error) { + log.Debug("ListAPIKeys") + + rows, err := p.db.Query(sqlQueries.ListAPIKeys, userID) + if err != nil { + log.Errorf("unable to list API keys: %v", err) + return nil, err + } + + result := []interfaces.APIKey{} + for rows.Next() { + var apiKey interfaces.APIKey + err = rows.Scan(&apiKey.GUID, &apiKey.UserGUID, &apiKey.Comment, &apiKey.LastUsed) + if err != nil { + log.Errorf("Scan: %v", err) + return nil, err + } + result = append(result, apiKey) + } + + return result, nil +} + +// DeleteAPIKey - delete an API key identified by its GUID +func (p *PgsqlAPIKeysRepository) DeleteAPIKey(userGUID string, keyGUID string) error { + log.Debug("DeleteAPIKey") + + err := execQuery(p, sqlQueries.DeleteAPIKey, userGUID, keyGUID) + if err != nil { + return fmt.Errorf("DeleteAPIKey: %v", err) + } + + return nil +} + +// UpdateAPIKeyLastUsed - sets API key last_used field to current time +func (p *PgsqlAPIKeysRepository) UpdateAPIKeyLastUsed(keyGUID string) error { + log.Debug("UpdateAPIKeyLastUsed") + + err := execQuery(p, sqlQueries.UpdateAPIKeyLastUsed, time.Now().UTC(), keyGUID) + if err != nil { + return fmt.Errorf("UpdateAPIKeyLastUsed: %v", err) + } + + return nil +} + +// A wrapper around db.Exec that validates that exactly 1 row has been inserted/deleted/updated +func execQuery(p *PgsqlAPIKeysRepository, query string, args ...interface{}) error { + result, err := p.db.Exec(query, args...) + if err != nil { + return err + } + + rowsUpdates, err := result.RowsAffected() + if err != nil { + return errors.New("could not determine number of rows that were updated") + } else if rowsUpdates < 1 { + return errors.New("no rows were updated") + } + + return nil +} diff --git a/src/jetstream/repository/apikeys/psql_apikeys_test.go b/src/jetstream/repository/apikeys/psql_apikeys_test.go new file mode 100644 index 0000000000..3534e4619c --- /dev/null +++ b/src/jetstream/repository/apikeys/psql_apikeys_test.go @@ -0,0 +1,339 @@ +package apikeys + +import ( + "errors" + "strings" + "testing" + "time" + + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" + . "github.com/smartystreets/goconvey/convey" + "gopkg.in/DATA-DOG/go-sqlmock.v1" +) + +func TestNewPgsqlAPIKeysRepository(t *testing.T) { + Convey("Given a request for a new reference to a API keys Repository", t, func() { + db, _, err := sqlmock.New() + if err != nil { + t.Errorf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + + repository, err := NewPgsqlAPIKeysRepository(db) + + Convey("no error should be returned", func() { + So(err, ShouldBeNil) + }) + + Convey("result should be of valid type", func() { + So(repository, ShouldHaveSameTypeAs, &PgsqlAPIKeysRepository{}) + }) + }) +} + +func TestAddAPIKey(t *testing.T) { + var ( + userID = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" + insertIntoAPIKeys = `INSERT INTO api_keys` + comment = "test" + ) + + Convey("Given a request to add an API key", t, func() { + db, mock, err := sqlmock.New() + if err != nil { + t.Errorf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + + repository, _ := NewPgsqlAPIKeysRepository(db) + + Convey("when the comment exceeds maximal length", func() { + _, err := repository.AddAPIKey(userID, strings.Repeat("a", 256)) + + Convey("an error should be returned", func() { + So(err, ShouldResemble, errors.New("comment maximum length is 255 characters")) + }) + }) + + Convey("when a key can't be inserted", func() { + mock.ExpectExec(insertIntoAPIKeys). + WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), userID, comment). + WillReturnResult(sqlmock.NewResult(0, 0)) + + apiKey, err := repository.AddAPIKey(userID, comment) + + Convey("an error should be returned", func() { + So(err, ShouldResemble, errors.New("AddAPIKey: no rows were updated")) + }) + + Convey("should return nil", func() { + So(apiKey, ShouldBeNil) + }) + }) + + Convey("when the comment is not empty", func() { + mock.ExpectExec(insertIntoAPIKeys). + WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), userID, comment). + WillReturnResult(sqlmock.NewResult(1, 1)) + + apiKey, err := repository.AddAPIKey(userID, comment) + + Convey("there should be no error returned", func() { + So(err, ShouldBeNil) + }) + + Convey("should return an API key", func() { + So(apiKey, ShouldHaveSameTypeAs, &interfaces.APIKey{}) + }) + + Convey("API key secret should not be empty", func() { + So(len(apiKey.Secret), ShouldBeGreaterThan, 0) + }) + }) + }) +} + +func TestListAPIKeys(t *testing.T) { + var ( + userID = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" + rowFields = []string{"guid", "user_guid", "comment", "last_used"} + selectUserAPIKeys = `SELECT (.+) FROM api_keys WHERE user_guid = (.+)` + ) + + Convey("Given a request to list API keys", t, func() { + db, mock, err := sqlmock.New() + if err != nil { + t.Errorf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + + repository, err := NewPgsqlAPIKeysRepository(db) + + Convey("if no records exist in the DB", func() { + rs := sqlmock.NewRows(rowFields) + mock.ExpectQuery(selectUserAPIKeys).WillReturnRows(rs) + results, err := repository.ListAPIKeys(userID) + + Convey("DB query expectations should be met", func() { + So(mock.ExpectationsWereMet(), ShouldBeNil) + }) + + Convey("there should be no error returned", func() { + So(err, ShouldBeNil) + }) + + Convey("result should be empty", func() { + So(len(results), ShouldEqual, 0) + }) + + Convey("result should be of correct type", func() { + expectedList := make([]interfaces.APIKey, 0) + So(results, ShouldResemble, expectedList) + }) + }) + + Convey("if records exist in the DB", func() { + t := time.Now() + + r1 := &interfaces.APIKey{ + GUID: "00000000-0000-0000-0000-000000000000", + Secret: "", + UserGUID: userID, + Comment: "First key", + LastUsed: &t, + } + + r2 := &interfaces.APIKey{ + GUID: "11111111-1111-1111-1111-111111111111", + Secret: "", + UserGUID: userID, + Comment: "Second key", + LastUsed: nil, + } + + expectedList := []interfaces.APIKey{*r1, *r2} + + mockRows := sqlmock.NewRows(rowFields). + AddRow(r1.GUID, r1.UserGUID, r1.Comment, r1.LastUsed). + AddRow(r2.GUID, r2.UserGUID, r2.Comment, r2.LastUsed) + + mock.ExpectQuery(selectUserAPIKeys).WillReturnRows(mockRows) + + results, err := repository.ListAPIKeys(userID) + + Convey("DB query expectations should be met", func() { + So(mock.ExpectationsWereMet(), ShouldBeNil) + }) + + Convey("there should be no error returned", func() { + So(err, ShouldBeNil) + }) + + Convey("2 API keys should be returned", func() { + So(len(results), ShouldEqual, 2) + }) + + Convey("result should be of correct type", func() { + So(results, ShouldResemble, expectedList) + }) + }) + }) +} + +func TestGetAPIKeyBySecret(t *testing.T) { + var ( + rowFields = []string{"guid", "user_guid", "comment", "last_used"} + selectAPIKeyBySecret = `SELECT (.+) FROM api_keys WHERE secret = (.+)` + ) + + Convey("Given a request to get an API key by its secret", t, func() { + db, mock, err := sqlmock.New() + if err != nil { + t.Errorf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + + repository, err := NewPgsqlAPIKeysRepository(db) + + Convey("if no matching record exists in the DB", func() { + rs := sqlmock.NewRows(rowFields) + mock.ExpectQuery(selectAPIKeyBySecret).WillReturnRows(rs) + results, err := repository.GetAPIKeyBySecret("test") + + Convey("DB query expectations should be met", func() { + So(mock.ExpectationsWereMet(), ShouldBeNil) + }) + + Convey("an error should be returned", func() { + So(err, ShouldResemble, errors.New("sql: no rows in result set")) + }) + + Convey("result should be nil", func() { + So(results, ShouldBeNil) + }) + }) + + Convey("if records exist in the DB", func() { + t := time.Now() + + r := &interfaces.APIKey{ + GUID: "00000000-0000-0000-0000-000000000000", + Secret: "", + UserGUID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + Comment: "First key", + LastUsed: &t, + } + + mockRows := sqlmock.NewRows(rowFields). + AddRow(r.GUID, r.UserGUID, r.Comment, r.LastUsed) + + mock.ExpectQuery(selectAPIKeyBySecret).WillReturnRows(mockRows) + + results, err := repository.GetAPIKeyBySecret("test") + + Convey("DB query expectations should be met", func() { + So(mock.ExpectationsWereMet(), ShouldBeNil) + }) + + Convey("there should be no error returned", func() { + So(err, ShouldBeNil) + }) + + Convey("result should be of correct type", func() { + So(results, ShouldResemble, r) + }) + }) + }) +} + +func TestDeleteAPIKey(t *testing.T) { + var ( + userID = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" + keyID = "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" + deleteFromAPIKeys = `DELETE FROM api_keys WHERE user_guid = (.+) AND guid = (.+)` + ) + + Convey("Given a request to add an API key", t, func() { + db, mock, err := sqlmock.New() + if err != nil { + t.Errorf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + + repository, _ := NewPgsqlAPIKeysRepository(db) + + Convey("when a matching key doesn't exist", func() { + mock.ExpectExec(deleteFromAPIKeys). + WithArgs(userID, keyID). + WillReturnResult(sqlmock.NewResult(0, 0)) + + err := repository.DeleteAPIKey(userID, keyID) + + Convey("an error should be returned", func() { + So(err, ShouldResemble, errors.New("DeleteAPIKey: no rows were updated")) + }) + }) + + Convey("when a matching key exists", func() { + mock.ExpectExec(deleteFromAPIKeys). + WithArgs(userID, keyID). + WillReturnResult(sqlmock.NewResult(1, 1)) + + err := repository.DeleteAPIKey(userID, keyID) + + Convey("DB query expectations should be met", func() { + So(mock.ExpectationsWereMet(), ShouldBeNil) + }) + + Convey("there should be no error returned", func() { + So(err, ShouldBeNil) + }) + }) + }) +} + +// +func TestUpdateAPIKeyLastUsed(t *testing.T) { + var ( + keyID = "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" + updateLastUsed = `UPDATE api_keys SET last_used = (.+) WHERE guid = (.+)` + ) + + Convey("Given a request to update API key last used", t, func() { + db, mock, err := sqlmock.New() + if err != nil { + t.Errorf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + + repository, _ := NewPgsqlAPIKeysRepository(db) + + Convey("when a matching key doesn't exist", func() { + mock.ExpectExec(updateLastUsed). + WithArgs(sqlmock.AnyArg(), keyID). + WillReturnResult(sqlmock.NewResult(0, 0)) + + err := repository.UpdateAPIKeyLastUsed(keyID) + + Convey("an error should be returned", func() { + So(err, ShouldResemble, errors.New("UpdateAPIKeyLastUsed: no rows were updated")) + }) + }) + + Convey("when a matching key exists", func() { + mock.ExpectExec(updateLastUsed). + WithArgs(sqlmock.AnyArg(), keyID). + WillReturnResult(sqlmock.NewResult(1, 1)) + + err := repository.UpdateAPIKeyLastUsed(keyID) + + Convey("DB query expectations should be met", func() { + So(mock.ExpectationsWereMet(), ShouldBeNil) + }) + + Convey("there should be no error returned", func() { + So(err, ShouldBeNil) + }) + }) + }) +} diff --git a/src/jetstream/repository/interfaces/apikeys.go b/src/jetstream/repository/interfaces/apikeys.go new file mode 100644 index 0000000000..a0ead69e35 --- /dev/null +++ b/src/jetstream/repository/interfaces/apikeys.go @@ -0,0 +1,12 @@ +package interfaces + +import "time" + +// APIKey - represents API key DB entry +type APIKey struct { + GUID string `json:"guid"` + Secret string `json:"secret"` + UserGUID string `json:"user_guid"` + Comment string `json:"comment"` + LastUsed *time.Time `json:"last_used"` +} diff --git a/src/jetstream/repository/interfaces/config/config.go b/src/jetstream/repository/interfaces/config/config.go index 872e3470d4..0e4dbe75ad 100644 --- a/src/jetstream/repository/interfaces/config/config.go +++ b/src/jetstream/repository/interfaces/config/config.go @@ -20,6 +20,39 @@ import ( const secretsDir = "/etc/secrets" +// APIKeysConfigValue - special type for configuring whether API keys feature is enabled +type APIKeysConfigValue string + +// APIKeysConfigEnum - defines possible configuration values for Stratos API keys feature +var APIKeysConfigEnum = struct { + Disabled APIKeysConfigValue + AdminOnly APIKeysConfigValue + AllUsers APIKeysConfigValue +}{ + Disabled: "disabled", + AdminOnly: "admin_only", + AllUsers: "all_users", +} + +// verifies that given string is a valid config value (i.e., present in APIKeysConfigEnum) +func parseAPIKeysConfigValue(input string) (APIKeysConfigValue, error) { + t := reflect.TypeOf(APIKeysConfigEnum) + v := reflect.ValueOf(APIKeysConfigEnum) + + var allowedValues []string + + for i := 0; i < t.NumField(); i++ { + allowedValue := string(v.Field(i).Interface().(APIKeysConfigValue)) + if allowedValue == input { + return APIKeysConfigValue(input), nil + } + + allowedValues = append(allowedValues, allowedValue) + } + + return "", fmt.Errorf("Invalid value %q, allowed values: %q", input, allowedValues) +} + var urlType *url.URL // Load the given pointer to struct with values from the environment and the @@ -119,7 +152,12 @@ func SetStructFieldValue(value reflect.Value, field reflect.Value, val string) e b, err = strconv.ParseBool(val) newVal = b case reflect.String: - newVal = val + apiKeysConfigType := reflect.TypeOf((*APIKeysConfigValue)(nil)).Elem() + if typ == apiKeysConfigType { + newVal, err = parseAPIKeysConfigValue(val) + } else { + newVal = val + } default: if typ == reflect.TypeOf(urlType) { newVal, err = url.Parse(val) diff --git a/src/jetstream/repository/interfaces/structs.go b/src/jetstream/repository/interfaces/structs.go index 909c91048b..20512c58a3 100644 --- a/src/jetstream/repository/interfaces/structs.go +++ b/src/jetstream/repository/interfaces/structs.go @@ -4,6 +4,7 @@ import ( "net/http" "net/url" + "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces/config" "github.com/gorilla/sessions" "github.com/labstack/echo" ) @@ -214,9 +215,10 @@ type Info struct { PluginConfig map[string]string `json:"plugin-config,omitempty"` Diagnostics *Diagnostics `json:"diagnostics,omitempty"` Configuration struct { - TechPreview bool `json:"enableTechPreview"` - ListMaxSize int64 `json:"listMaxSize,omitempty"` - ListAllowLoadMaxed bool `json:"listAllowLoadMaxed,omitempty"` + TechPreview bool `json:"enableTechPreview"` + ListMaxSize int64 `json:"listMaxSize,omitempty"` + ListAllowLoadMaxed bool `json:"listAllowLoadMaxed,omitempty"` + APIKeysEnabled string `json:"APIKeysEnabled"` } `json:"config"` } @@ -369,6 +371,7 @@ type PortalConfig struct { DatabaseProviderName string EnableTechPreview bool `configName:"ENABLE_TECH_PREVIEW"` CanMigrateDatabaseSchema bool + APIKeysEnabled config.APIKeysConfigValue `configName:"API_KEYS_ENABLED"` // CanMigrateDatabaseSchema indicates if we can safely perform migrations // This depends on the deployment mechanism and the database config // e.g. if running in Cloud Foundry with a shared DB, then only the 0-index application instance diff --git a/src/jetstream/repository/mock_interfaces/mock_auth.go b/src/jetstream/repository/mock_interfaces/mock_auth.go new file mode 100644 index 0000000000..801847baa9 --- /dev/null +++ b/src/jetstream/repository/mock_interfaces/mock_auth.go @@ -0,0 +1,107 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: repository/interfaces/auth.go + +// Package mock_interfaces is a generated GoMock package. +package mock_interfaces + +import ( + interfaces "github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces" + gomock "github.com/golang/mock/gomock" + echo "github.com/labstack/echo" + reflect "reflect" +) + +// MockStratosAuth is a mock of StratosAuth interface +type MockStratosAuth struct { + ctrl *gomock.Controller + recorder *MockStratosAuthMockRecorder +} + +// MockStratosAuthMockRecorder is the mock recorder for MockStratosAuth +type MockStratosAuthMockRecorder struct { + mock *MockStratosAuth +} + +// NewMockStratosAuth creates a new mock instance +func NewMockStratosAuth(ctrl *gomock.Controller) *MockStratosAuth { + mock := &MockStratosAuth{ctrl: ctrl} + mock.recorder = &MockStratosAuthMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockStratosAuth) EXPECT() *MockStratosAuthMockRecorder { + return m.recorder +} + +// Login mocks base method +func (m *MockStratosAuth) Login(c echo.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Login", c) + ret0, _ := ret[0].(error) + return ret0 +} + +// Login indicates an expected call of Login +func (mr *MockStratosAuthMockRecorder) Login(c interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Login", reflect.TypeOf((*MockStratosAuth)(nil).Login), c) +} + +// Logout mocks base method +func (m *MockStratosAuth) Logout(c echo.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Logout", c) + ret0, _ := ret[0].(error) + return ret0 +} + +// Logout indicates an expected call of Logout +func (mr *MockStratosAuthMockRecorder) Logout(c interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logout", reflect.TypeOf((*MockStratosAuth)(nil).Logout), c) +} + +// GetUsername mocks base method +func (m *MockStratosAuth) GetUsername(userGUID string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUsername", userGUID) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUsername indicates an expected call of GetUsername +func (mr *MockStratosAuthMockRecorder) GetUsername(userGUID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUsername", reflect.TypeOf((*MockStratosAuth)(nil).GetUsername), userGUID) +} + +// GetUser mocks base method +func (m *MockStratosAuth) GetUser(userGUID string) (*interfaces.ConnectedUser, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUser", userGUID) + ret0, _ := ret[0].(*interfaces.ConnectedUser) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUser indicates an expected call of GetUser +func (mr *MockStratosAuthMockRecorder) GetUser(userGUID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUser", reflect.TypeOf((*MockStratosAuth)(nil).GetUser), userGUID) +} + +// VerifySession mocks base method +func (m *MockStratosAuth) VerifySession(c echo.Context, sessionUser string, sessionExpireTime int64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifySession", c, sessionUser, sessionExpireTime) + ret0, _ := ret[0].(error) + return ret0 +} + +// VerifySession indicates an expected call of VerifySession +func (mr *MockStratosAuthMockRecorder) VerifySession(c, sessionUser, sessionExpireTime interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifySession", reflect.TypeOf((*MockStratosAuth)(nil).VerifySession), c, sessionUser, sessionExpireTime) +} diff --git a/src/test-e2e/apikeys/apikey-e2e-helper.ts b/src/test-e2e/apikeys/apikey-e2e-helper.ts new file mode 100644 index 0000000000..2db25a8dd4 --- /dev/null +++ b/src/test-e2e/apikeys/apikey-e2e-helper.ts @@ -0,0 +1,29 @@ +import { promise } from 'protractor'; + +import { E2ESetup } from '../e2e'; +import { RequestHelpers } from '../helpers/request-helpers'; + +const reqHelpers = new RequestHelpers(); + +export class ApiKeyE2eHelper { + + + constructor(public e2eSetup: E2ESetup) { + } + + addKey(comment: string, isAdmim: boolean): promise.Promise<any> { + return reqHelpers.sendRequest(isAdmim ? this.e2eSetup.adminReq : this.e2eSetup.userReq, { method: 'POST', url: 'pp/v1/api-key' }, null, { + comment + }); + } + + deleteKey(guid: string, isAdmim: boolean): promise.Promise<any> { + return reqHelpers.sendRequest(isAdmim ? this.e2eSetup.adminReq : this.e2eSetup.userReq, { method: 'DELETE', url: 'pp/v1/api-key' }, null, { + guid + }); + } + + getAllKeys(isAdmim: boolean): promise.Promise<any> { + return reqHelpers.sendRequest(isAdmim ? this.e2eSetup.adminReq : this.e2eSetup.userReq, { method: 'GET', url: 'pp/v1/api-key' }, null, undefined); + } +} \ No newline at end of file diff --git a/src/test-e2e/apikeys/apikeys-e2e.spec.ts b/src/test-e2e/apikeys/apikeys-e2e.spec.ts new file mode 100644 index 0000000000..aebe505b4c --- /dev/null +++ b/src/test-e2e/apikeys/apikeys-e2e.spec.ts @@ -0,0 +1,119 @@ +import { promise } from 'protractor'; + +import { MessageNoContentPo } from '../application/po/message-no-autoscaler-policy'; +import { e2e } from '../e2e'; +import { ConsoleUserType, E2EHelpers } from '../helpers/e2e-helpers'; +import { Component } from '../po/component.po'; +import { ConfirmDialogComponent } from '../po/confirm-dialog'; +import { ApiKeyE2eHelper } from './apikey-e2e-helper'; +import { ApiKeyAddDialogPo } from './po/apikey-add-dialog.po'; +import { APIKeyListPage } from './po/apikeys-list-page.po'; + +const customApiKeyLabel = E2EHelpers.e2eItemPrefix + (process.env.CUSTOM_APP_LABEL || process.env.USER) + '-api-key'; + +describe('API Keys -', () => { + + let helper: ApiKeyE2eHelper; + let newKeysComment: string; + let page = new APIKeyListPage(); + let currentKeysCount = promise.fullyResolved(0); + + beforeAll(() => { + const setup = e2e.setup(ConsoleUserType.admin) + .clearAllEndpoints() + .getInfo(ConsoleUserType.admin); + helper = new ApiKeyE2eHelper(setup); + + newKeysComment = E2EHelpers.createCustomName(customApiKeyLabel).toLowerCase() + }) + + // Should be ran in sequence + describe('Ordered Tests - ', () => { + it('Navigate to api key page', () => { + page.header.clickUserMenuItem('API Keys'); + page.waitForPage(); + }) + + it('New key does not exist', () => { + // Validation check + return page.list.isPresent().then(isDisplayed => { + if (isDisplayed) { + expect(page.list.table.findRow('comment', newKeysComment, false)).toBeLessThan(0); + currentKeysCount = page.list.table.getRowCount(); + } else { + const noContentComponent = new MessageNoContentPo(); + expect(noContentComponent.isDisplayed()).toBeTruthy() + } + }) + }); + + describe('Add Dialog - ', () => { + it('Basic Dialog tests', () => { + expect(page.addKeyButton().isDisplayed()).toBeTruthy(); + page.addKeyButton().click(); + + const dialog = new ApiKeyAddDialogPo(); + expect(dialog.isDisplayed()).toBeTruthy(); + expect(dialog.canClose()).toBeTruthy(); + expect(dialog.canCreate()).toBeFalsy(); + + dialog.close(); + + dialog.waitUntilNotShown(); + + page.waitForPage(); + + }); + + it('Add a new key', () => { + expect(page.addKeyButton().isDisplayed()).toBeTruthy(); + page.addKeyButton().click(); + + const dialog = new ApiKeyAddDialogPo(); + dialog.waitUntilShown(); + expect(dialog.canCreate()).toBeFalsy(); + + dialog.form.fill({ + comment: newKeysComment + }) + + expect(dialog.canClose()).toBeTruthy(); + expect(dialog.canCreate()).toBeTruthy(); + + dialog.create(); + dialog.waitUntilNotShown(); + }); + }) + + it('New key has a secret', () => { + const secret = new Component(page.getKeySecret()); + secret.waitUntilShown(); + expect(secret.getComponent().getText()).toBeDefined(); + page.closeKeySecret(); + secret.waitUntilNotShown() + }) + + it('New key is in updated table', () => { + expect(page.list.table.findRow('description', newKeysComment, true)).toBeGreaterThanOrEqual(0); + }) + + it('Delete new key', () => { + return page.list.table.findRow('description', newKeysComment, true) + .then(rowIndex => { + page.list.table.openRowActionMenuByIndex(rowIndex).clickItem('Delete'); + ConfirmDialogComponent.expectDialogAndConfirm('Delete', 'Delete Key'); + page.waitForPage(); + return page.list.isPresent(); + }) + .then(isListDisplayed => { + if (isListDisplayed) { + page.list.waitForNoLoadingIndicator(); + expect(page.list.table.getRowCount()).toEqual(currentKeysCount); + } else { + expect(0).toEqual(currentKeysCount) + } + }) + }) + }) + +}); \ No newline at end of file diff --git a/src/test-e2e/apikeys/po/apikey-add-dialog.po.ts b/src/test-e2e/apikeys/po/apikey-add-dialog.po.ts new file mode 100644 index 0000000000..07b70c4ce8 --- /dev/null +++ b/src/test-e2e/apikeys/po/apikey-add-dialog.po.ts @@ -0,0 +1,35 @@ +import { by, element } from 'protractor'; + +import { Component } from '../../po/component.po'; +import { FormComponent } from '../../po/form.po'; +import { MenuComponent } from '../../po/menu.po'; + +export class ApiKeyAddDialogPo extends Component { + + public form: FormComponent; + + public buttons: MenuComponent; + + constructor() { + super(element(by.tagName('app-add-api-key-dialog'))); + this.form = new FormComponent(this.locator.element(by.css('.key-dialog'))); + this.buttons = new MenuComponent(this.locator.element(by.css('.key-dialog__actions'))); + } + + close() { + return this.buttons.getItemMap().then(btns => btns.cancel.click()); + } + + canClose() { + return this.buttons.getItemMap().then(btns => !btns.cancel.disabled); + } + + create() { + return this.buttons.getItemMap().then(btns => btns.create.click()); + } + + canCreate() { + return this.buttons.getItemMap().then(btns => !btns.create.disabled); + } + +} diff --git a/src/test-e2e/apikeys/po/apikeys-list-page.po.ts b/src/test-e2e/apikeys/po/apikeys-list-page.po.ts new file mode 100644 index 0000000000..c6977bc06e --- /dev/null +++ b/src/test-e2e/apikeys/po/apikeys-list-page.po.ts @@ -0,0 +1,34 @@ +import { browser, by, element, ElementFinder, promise, protractor } from 'protractor'; + +import { ListComponent } from '../../po/list.po'; +import { Page } from '../../po/page.po'; + +const until = protractor.ExpectedConditions; + +export class APIKeyListPage extends Page { + + list = new ListComponent(); + private locator: ElementFinder; + + constructor() { + super('/api-keys'); + this.locator = element(by.css('app-api-keys-page')); + } + + addKeyButton(): ElementFinder { + return element(by.css('#stratos-api-key')); + } + + + waitForSecret(): promise.Promise<any> { + return browser.wait(until.presenceOf(this.getKeySecret())); + } + + getKeySecret(): ElementFinder { + return this.locator.element(by.css('.keys-page__card li')); + } + + closeKeySecret(): promise.Promise<void> { + return this.locator.element(by.css('.keys-page__card button')).click(); + } +} \ No newline at end of file diff --git a/src/test-e2e/application/application-autoscaler-e2e.spec.ts b/src/test-e2e/application/application-autoscaler-e2e.spec.ts index 996487c8d4..8c88c4aeb2 100644 --- a/src/test-e2e/application/application-autoscaler-e2e.spec.ts +++ b/src/test-e2e/application/application-autoscaler-e2e.spec.ts @@ -1,4 +1,4 @@ -import * as moment from 'moment-timezone'; +import moment from 'moment-timezone'; import { browser, promise } from 'protractor'; import { timer } from 'rxjs'; import { switchMap } from 'rxjs/operators'; @@ -22,6 +22,17 @@ let applicationE2eHelper: ApplicationE2eHelper; describe('Autoscaler -', () => { + const validateFormDate = (fromForm: promise.Promise<any>, toEqual: moment.Moment, label: string) => { + fromForm.then(fieldInput => { + const momentFieldInput = moment(fieldInput) + expect(momentFieldInput.year).toBe(toEqual.year, `Failed for '${label}'`); + expect(momentFieldInput.month).toBe(toEqual.month, `Failed for '${label}'`); + expect(momentFieldInput.day).toBe(toEqual.day, `Failed for '${label}'`); + expect(momentFieldInput.hour).toBe(toEqual.hour, `Failed for '${label}'`); + expect(momentFieldInput.minute).toBe(toEqual.minute, `Failed for '${label}'`); + }) + } + beforeAll(() => { const setup = e2e.setup(ConsoleUserType.user) .clearAllEndpoints() @@ -92,6 +103,7 @@ describe('Autoscaler -', () => { beforeAll(() => new LocaleHelper().getWindowDateTimeFormats().then(formats => { timeFormat = formats.timeFormat; + // Note - sendMultipleKeys interprets this as containing a right arrow key to skip between date and time dateAndTimeFormat = `${formats.dateFormat},${timeFormat}`; })); @@ -229,7 +241,7 @@ describe('Autoscaler -', () => { expect(createPolicy.stepper.canNext()).toBeFalsy(); // Schedule dates should not overlap - // scheduleStartDate1 should be set close to time it's entered, this is what triggers the scaling event tested below + // scheduleStartDate1 should be set close to time it's entered, this is what triggers the scaling event tested below (is this true given rules are deleted in edit?) const scheduleStartDate1 = moment().tz('UTC').add(1, 'minutes').add(30, 'seconds'); scheduleEndDate1 = moment().tz('UTC').add(2, 'days'); scheduleStartDate2 = moment().tz('UTC').add(3, 'days'); @@ -237,14 +249,18 @@ describe('Autoscaler -', () => { // Fill in form -- valid inputs createPolicy.stepper.getStepperForm().fill({ start_date_time: scheduleStartDate1.format(dateAndTimeFormat) }); + validateFormDate(createPolicy.stepper.getStepperForm().getText('start_date_time'), scheduleStartDate1, 'Create Policy: Schedule 1: Start Date'); createPolicy.stepper.getStepperForm().fill({ end_date_time: scheduleEndDate1.format(dateAndTimeFormat) }); + validateFormDate(createPolicy.stepper.getStepperForm().getText('end_date_time'), scheduleEndDate1, 'Create Policy: Schedule 1: End Date'); createPolicy.stepper.getStepperForm().fill({ instance_min_count: '2' }); createPolicy.stepper.getStepperForm().fill({ initial_min_instance_count: '2' }); createPolicy.stepper.getStepperForm().fill({ instance_max_count: '10' }); - expect(createPolicy.stepper.getMatErrorsCount()).toBe(0); + expect(createPolicy.stepper.getMatErrorsCount()).toBe(0, 'Form errors are shown where there should be none'); expect(createPolicy.stepper.getDoneButtonDisabledStatus()).toBe(null); createPolicy.stepper.clickDoneButton(); + expect(createPolicy.stepper.getRuleTilesCount()).toBe(1, 'There should be one specific date rules, rest of test will fail'); + // Bad form, have seen errors where the first schedule isn't shown in ux but is submitted. When submitted the start time is already // in the past and the create policy request fails. Needs a better solution e2e.sleep(1000); @@ -260,10 +276,14 @@ describe('Autoscaler -', () => { createPolicy.stepper.getStepperForm().fill({ instance_min_count: '2' }); // Fill in form -- valid inputs createPolicy.stepper.getStepperForm().fill({ start_date_time: scheduleStartDate2.format(dateAndTimeFormat) }); + validateFormDate(createPolicy.stepper.getStepperForm().getText('start_date_time'), scheduleStartDate2, 'Create Policy: Schedule 2: Start Date'); createPolicy.stepper.getStepperForm().fill({ end_date_time: scheduleEndDate2.format(dateAndTimeFormat) }); + validateFormDate(createPolicy.stepper.getStepperForm().getText('end_date_time'), scheduleEndDate2, 'Create Policy: Schedule 2: Start Date'); expect(createPolicy.stepper.getMatErrorsCount()).toBe(0); createPolicy.stepper.clickDoneButton(); + expect(createPolicy.stepper.getRuleTilesCount()).toBe(2, 'There should be two specific date rules, rest of test will fail'); + expect(createPolicy.stepper.canNext()).toBeTruthy(); createPolicy.stepper.next(); }); @@ -384,6 +404,8 @@ describe('Autoscaler -', () => { }); it('Should pass SpecificDates Step', () => { + expect(createPolicy.stepper.getRuleTilesCount()).toBe(2, 'There should be two specific date rules, rest of test will fail'); + createPolicy.stepper.clickDeleteButton(1); expect(createPolicy.stepper.canNext()).toBeTruthy(); @@ -397,7 +419,9 @@ describe('Autoscaler -', () => { console.log(`Setting schedule. Now: ${moment().tz('UTC').toString()}. Start: ${scheduleStartDate1.toString()}. End ${scheduleEndDate1.toString()}`); createPolicy.stepper.getStepperForm().waitUntilShown(); createPolicy.stepper.getStepperForm().fill({ start_date_time: scheduleStartDate1.format(dateAndTimeFormat) }); + validateFormDate(createPolicy.stepper.getStepperForm().getText('start_date_time'), scheduleStartDate1, 'Edit Policy: Schedule 1: Start Date'); createPolicy.stepper.getStepperForm().fill({ end_date_time: scheduleEndDate1.format(dateAndTimeFormat) }); + validateFormDate(createPolicy.stepper.getStepperForm().getText('end_date_time'), scheduleEndDate1, 'Edit Policy: Schedule 1: Start Date'); createPolicy.stepper.clickDoneButton(); expect(createPolicy.stepper.canNext()).toBeTruthy(); diff --git a/src/test-e2e/application/application-deploy-local-e2e.spec.ts b/src/test-e2e/application/application-deploy-local-e2e.spec.ts index 463e0b71cf..6e51367f7b 100644 --- a/src/test-e2e/application/application-deploy-local-e2e.spec.ts +++ b/src/test-e2e/application/application-deploy-local-e2e.spec.ts @@ -1,4 +1,4 @@ -import * as path from 'path'; +import path from 'path'; import { browser, by, element, protractor } from 'protractor'; import { ApplicationsPage } from '../applications/applications.po'; diff --git a/src/test-e2e/application/po/application-page-autoscaler.po.ts b/src/test-e2e/application/po/application-page-autoscaler.po.ts index b887b1e1ca..242f833e64 100644 --- a/src/test-e2e/application/po/application-page-autoscaler.po.ts +++ b/src/test-e2e/application/po/application-page-autoscaler.po.ts @@ -1,18 +1,18 @@ -import { promise, browser } from 'protractor'; +import { browser, promise } from 'protractor'; import { ApplicationBasePage } from './application-page.po'; -import { MessageNoAutoscalePolicy } from './message-no-autoscaler-policy'; import { BannerAutoscalerTab } from './banner-autoscaler-tab'; import { CardAutoscalerDefault } from './card-autoscaler-default.po'; import { CardAutoscalerMetric } from './card-autoscaler-metric'; +import { MessageNoContentPo } from './message-no-autoscaler-policy'; import { TableAutoscalerEvents } from './table-autoscaler-events.po'; -import { TableAutoscalerTriggers } from './table-autoscaler-triggers'; import { TableAutoscalerSchedules } from './table-autoscaler-schedules'; +import { TableAutoscalerTriggers } from './table-autoscaler-triggers'; export class ApplicationPageAutoscalerTab extends ApplicationBasePage { bannerAutoscalerTab: BannerAutoscalerTab; - messageNoPolicy: MessageNoAutoscalePolicy; + messageNoPolicy: MessageNoContentPo; cardDefault: CardAutoscalerDefault; cardMetric: CardAutoscalerMetric; tableTriggers: TableAutoscalerTriggers; @@ -21,7 +21,7 @@ export class ApplicationPageAutoscalerTab extends ApplicationBasePage { constructor(public cfGuid: string, public appGuid: string) { super(cfGuid, appGuid, 'autoscale'); - this.messageNoPolicy = new MessageNoAutoscalePolicy(); + this.messageNoPolicy = new MessageNoContentPo(); this.bannerAutoscalerTab = new BannerAutoscalerTab(cfGuid, appGuid); this.cardDefault = new CardAutoscalerDefault(cfGuid, appGuid); this.cardMetric = new CardAutoscalerMetric(cfGuid, appGuid); diff --git a/src/test-e2e/application/po/create-autoscaler-policy-step.po.ts b/src/test-e2e/application/po/create-autoscaler-policy-step.po.ts index 0622a87f44..fcc609b406 100644 --- a/src/test-e2e/application/po/create-autoscaler-policy-step.po.ts +++ b/src/test-e2e/application/po/create-autoscaler-policy-step.po.ts @@ -1,4 +1,4 @@ -import * as moment from 'moment-timezone'; +import moment from 'moment-timezone'; import { by, ElementArrayFinder, ElementFinder, promise } from 'protractor'; import { FormComponent } from '../../po/form.po'; diff --git a/src/test-e2e/application/po/message-no-autoscaler-policy.ts b/src/test-e2e/application/po/message-no-autoscaler-policy.ts index fd9ffeef9c..76d7290ce8 100644 --- a/src/test-e2e/application/po/message-no-autoscaler-policy.ts +++ b/src/test-e2e/application/po/message-no-autoscaler-policy.ts @@ -2,7 +2,7 @@ import { by, element, ElementFinder, promise } from 'protractor'; import { Component } from '../../po/component.po'; -export class MessageNoAutoscalePolicy extends Component { +export class MessageNoContentPo extends Component { constructor(locator: ElementFinder = element(by.tagName('app-no-content-message'))) { super(locator); diff --git a/src/test-e2e/endpoints/endpoints-register-e2e.spec.ts b/src/test-e2e/endpoints/endpoints-register-e2e.spec.ts index 68c931fe85..18b5832930 100644 --- a/src/test-e2e/endpoints/endpoints-register-e2e.spec.ts +++ b/src/test-e2e/endpoints/endpoints-register-e2e.spec.ts @@ -167,7 +167,6 @@ describe('Endpoints', () => { // Handle the error case for invalid certificate - could be unknown authority or bad certificate // so check for correct message at the start and the helpful message for the user to correct the problem expect(snackBar.hasMessage('SSL error - x509: certificate')).toBeTruthy(); - /* tslint:disable-line:max-line-length*/ expect(snackBar.messageContains('Please check "Skip SSL validation for the endpoint" if the certificate issuer is trusted')) .toBeTruthy(); }); diff --git a/src/test-e2e/helpers/request-helpers.ts b/src/test-e2e/helpers/request-helpers.ts index 2d440ef58a..714eabb5dc 100644 --- a/src/test-e2e/helpers/request-helpers.ts +++ b/src/test-e2e/helpers/request-helpers.ts @@ -1,7 +1,7 @@ import { existsSync, readFileSync } from 'fs'; import { join } from 'path'; import { browser, promise } from 'protractor'; -import * as request from 'request-promise-native'; +import request from 'request-promise-native'; import { E2E, e2e } from '../e2e'; import { ConsoleUserType } from './e2e-helpers'; @@ -125,8 +125,8 @@ export class RequestHelpers { Object.keys(options).forEach(key => { const v = options[key]; if (typeof v === 'string' && key === 'password') { - options[key]='******'; - } else if (typeof v === 'object') { + options[key] = '******'; + } else if (typeof v === 'object') { this.sanitizeOption(options[key]); } }); diff --git a/src/test-e2e/helpers/uaa-request-helpers.ts b/src/test-e2e/helpers/uaa-request-helpers.ts index 41947e061f..8bd80a429d 100644 --- a/src/test-e2e/helpers/uaa-request-helpers.ts +++ b/src/test-e2e/helpers/uaa-request-helpers.ts @@ -1,5 +1,5 @@ import { promise } from 'protractor'; -import * as request from 'request-promise-native'; +import request from 'request-promise-native'; import { e2e } from '../e2e'; import { E2EUaa } from '../e2e.types'; diff --git a/src/test-e2e/locale.helper.ts b/src/test-e2e/locale.helper.ts index 53aa76e018..af483bc5cd 100644 --- a/src/test-e2e/locale.helper.ts +++ b/src/test-e2e/locale.helper.ts @@ -1,4 +1,4 @@ -import * as moment from 'moment'; +import moment from 'moment'; import { browser, promise } from 'protractor'; export class LocaleHelper { diff --git a/src/test-e2e/po/form.po.ts b/src/test-e2e/po/form.po.ts index 1e676db7ac..83c8141c56 100644 --- a/src/test-e2e/po/form.po.ts +++ b/src/test-e2e/po/form.po.ts @@ -325,6 +325,7 @@ export class FormComponent extends Component { keyString.split(/[ ,:\/]/).forEach((key) => { ctrl.sendKeys(key); if (key.length === 4) { + browser.sleep(500); ctrl.sendKeys(Key.ARROW_RIGHT); } }); diff --git a/src/test-e2e/po/page-header.po.ts b/src/test-e2e/po/page-header.po.ts index e323f29098..92d058bdaa 100644 --- a/src/test-e2e/po/page-header.po.ts +++ b/src/test-e2e/po/page-header.po.ts @@ -50,17 +50,25 @@ export class PageHeader extends Component { browser.wait(this.until.textToBePresentInElement(this.getTitle(), text), 10000, `Failed to wait for page header with text ${text}`); } - logout(): promise.Promise<any> { + getUserMenu(): ElementFinder { return this.locator.element(by.id('userMenu')) + } + + clickUserMenuItem(itemText: string): promise.Promise<any> { + return this.getUserMenu() .click() .then(() => { // browser.driver.sleep(2000); const menu = new MenuComponent(); menu.waitUntilShown(); - menu.clickItem('Logout'); + menu.clickItem(itemText); // browser.driver.sleep(2000); return browser.waitForAngular(); }); } + logout(): promise.Promise<any> { + return this.clickUserMenuItem('Logout'); + } + } diff --git a/src/tsconfig.json b/src/tsconfig.json index 0ee21fc3d1..a086487d1f 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -12,6 +12,8 @@ "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, "importHelpers": true, "target": "es2015", "module": "esnext", diff --git a/website/README.md b/website/README.md index b70a5be1d0..157538b94c 100644 --- a/website/README.md +++ b/website/README.md @@ -26,7 +26,7 @@ This command starts a local development server and open up a browser window. Mos $ ./deploy.sh -b ``` -This command generates static content into the `build` directory and can be served using any static contents hosting service. +This command generates static content into the `build` directory and can be served using any static contents hosting service, or `npm run serve` to see locally. ### Deployment @@ -37,4 +37,50 @@ $ ./deploy.sh ``` -> Note: The website is deployed to the GitHub Repository `cf-stratos/wesbite` which hosts https://stratos.app \ No newline at end of file +> Note: The website is deployed to the GitHub Repository `cf-stratos/wesbite` which hosts https://stratos.app + +### Version + +Versions is handled automatically by `npm run versions` which is called as part of `npm run build`. The `versions` target runs `build-versions.sh` which + +> The files is `docs/` will be marked as `next`. + +1. clones a local copy of the repo +1. cleans up any previous run (repo aside) +1. Loop through each version defined in `internal-versions.json` (latest version is highest) + - checkout that version in temp repo + - tag that version with it's version label using docusaurus + - copy the files docusaurus creates back into the main repo + - store the label +1. remove the local clone + +#### Add a new version + +1. Open `internal-versions.json` +1. Add to the top `<label of version to be displayed in website>:<version of repo to checkout that contains required docs>:<show version in versions drop down`. For example `[ "4.0.0:4.0.0:true"]` +1. Commit, push and merge changes + +> Note - the most recent version (first in array) MUST be visible in the drop down. This is the version that is picked by Docusaurus as the default + +Everything else should be handled by the CI process (building with all versions in file and publishing) + + +# Updating Docusaurus Version +If the version of Docusaurus is updated be careful of changes to components that have been 'swizzled`. These are theme components that we have copied using the swizzle command and tweaked locally. + +For example + +``` +npm run docusaurus -- swizzle @docusaurus/theme-classic Navbar +``` + +Currently these components are in `./website/src/theme` +- DocsVersionDropdownNavbarItem (trims out versions in drop down, shows link to all versions) +- DocVersionSuggestions (fixes text shown when not on last released version) +- NavBar (dynamically hide the versions drop down if not in the docs section) + +After Docusaurus is updated these will remain the Stratos version of the old Docusaurus version. Therefore may need to be recreated using swizzle and applying the same changes. + +Note, there are also two non-swizzled custom pages that may also need updating. Current these components are in `./website/src/pages/*.js` +- index.js (home page) +- versions.js (all versions page) diff --git a/website/add-version.sh b/website/add-version.sh new file mode 100755 index 0000000000..b8fd451e87 --- /dev/null +++ b/website/add-version.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -e +set -o pipefail + +echo "Determining version Label & Tag" + +# GITHUB_EVENT_PATH points to json containing event data. This contains the github release object +# See https://developer.github.com/webhooks/event-payloads/#release +versionLabel=$(jq -r '.release.name' $GITHUB_EVENT_PATH) +versionSha=$(jq -r '.release.tag_name' $GITHUB_EVENT_PATH) +showVersionInDropDown=true +internalVersionsFile="internal-versions.json" + +echo Adding the following as latest docs version +echo "Label : $versionLabel" +echo "Tag/Sha : $versionSha" +echo "Show In Dropdown : $showVersionInDropDown" + +interalVersion=$versionLabel:$versionSha:$showVersionInDropDown +echo "New Version : $interalVersion" + +# Write to temp env var. Not sure why, but a simple `> $internalVersionsFile` wipes out content +res=$(jq --arg argval $interalVersion -r '. |= [$argval] + .' $internalVersionsFile) +echo "$res" > $internalVersionsFile + +echo "Result :" +cat $internalVersionsFile diff --git a/website/build-versions.sh b/website/build-versions.sh new file mode 100755 index 0000000000..2959642ebe --- /dev/null +++ b/website/build-versions.sh @@ -0,0 +1,151 @@ +#!/usr/bin/env bash +set -e +set -o pipefail + +function logInner() ( + echo ..... $1 +) + +function cleanUpBefore() ( + currentWebsite=$1 + + rm -rf $currentWebsite/versioned_docs + mkdir -p $currentWebsite/versioned_docs + + rm -rf $currentWebsite/versioned_sidebars + mkdir -p $currentWebsite/versioned_sidebars + + echo "[]" > $currentWebsite/versions.json + + cleanUpGit +) + +function gitClone() ( + rurl="$1" localdir="$2" + echo "Cloning from $rurl into $localdir" + # git clone --depth 1 --no-single-branch $rurl $localdir + git clone $rurl $localdir + pushd $localdir/website > /dev/null + npm install + popd > /dev/null +) + +function checkoutAndTag() ( + logInner "Checking out: $versionsHash" + pushd $tempDirForGit > /dev/null + git checkout $versionsHash + pushd website > /dev/null + logInner "Tagging with version $versionsLabel" + npm run version -- $versionsLabel + popd > /dev/null + popd > /dev/null +) + +function createVersionedDocs() ( + logInner "Updating versioned docs folder" + checkedOutRepo=$1 + currentWebsite=$2 + label=$3 + + mkdir -p $currentWebsite/versioned_docs + cp -r $checkedOutRepo/website/versioned_docs/version-$label $currentWebsite/versioned_docs +) + +function createVersiondSidebar() ( + logInner "Updating versioned sidebar" + checkedOutRepo=$1 + currentWebsite=$2 + label=$3 + + mkdir -p $currentWebsite/versioned_sidebars + cp $checkedOutRepo/website/versioned_sidebars/version-$label-sidebars.json $currentWebsite/versioned_sidebars +) + +function updateVersionsFile() ( + vString=$1 + if [ $vString = "]" ]; then + echo "No content for versions file" + return + fi + echo Updating versions file from $vString + versions="[${vString:1}" + + echo to $versions + echo $versions > $versionsFile +) + +function cleanUpGit() ( + echo Removing folder: $tempDirForGit + rm -rf $tempDirForGit +) + + +# wesbite folder +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" + +# tempDirForGit=$(mktemp -d) +tempDirForGit=$DIR/versions-repo +mkdir -p $tempDirForGit + +gitUrl=$(git remote get-url origin) +versionsFile="versions.json" +internalVersionsFile="internal-versions.json" + +echo ---------- Values ------------- +# echo Hashes to treat as version: $hashes +echo Temp Dir: $tempDirForGit. This will be removed +echo GIT Url: $gitUrl +echo Versions File: $versionsFile +echo Internal Versions File: $internalVersionsFile +echo Current Directory: $DIR + +cleanUpBefore $DIR + +gitClone $gitUrl $tempDirForGit + +versions="]" + +internalVersions=$(jq -r '.[]' $internalVersionsFile) +export internalVersionsArray=($internalVersions) + +# Loop through each version +# .. checkout that version in the temp dir +# .. tag that version with it's label using docusaurus +# .. copy the files docusaurus creates back into the main repo +# .. store the label +# The versions are reveresed, not so important at the moment but it's useful for future improvements +# if the docusaurus labelling works from oldest to newest. The order in the versions.json file should +# go from newest (first in array) to oldest (last in array) +for ((i = ${#internalVersionsArray[@]} - 1;i >= 0;i--)); do + row=${internalVersionsArray[i]} + IFS=: read versionsLabel versionsHash includeInDropDown <<< $row + + # This is donw via the website now + # if [ $includeInDropDown != "true" ]; then + # echo Skipping version: $versionsLabel \"$includeInDropDown\" + # continue + # fi + + if [ -z "$versionsLabel" ]; then + echo Invalid row \(no version label\): $row + exit 1 + fi + + if [ -z "$versionsHash" ]; then + echo Invalid row \(no version hash\): $row + exit 1 + fi + + echo Process version \'$versionsLabel\' with checkout target of \'$versionsHash\' + + checkoutAndTag + createVersionedDocs $tempDirForGit/ $DIR $versionsLabel + createVersiondSidebar $tempDirForGit $DIR $versionsLabel + versions=,\"$versionsLabel\"$versions + + echo Finished processing \'$versionsLabel\' + +done + +updateVersionsFile $versions +cleanUpGit $tempDirForGit diff --git a/website/docs/advanced/sso.md b/website/docs/advanced/sso.md index 6dfb23a357..db4dd92193 100644 --- a/website/docs/advanced/sso.md +++ b/website/docs/advanced/sso.md @@ -67,4 +67,19 @@ SSO_WHITELIST=https://your.domain/*,https://your.other.domain/* When set, any requests to log in with a different `state` will be denied. -In order for the SSO `state` to match an entry from the whitelist the schema, hostname, port and path must match exactly. A wildcard `*` can be provided for the path to match anything. \ No newline at end of file +In order for the SSO `state` to match an entry from the whitelist the schema, hostname, port and path must match exactly. A wildcard `*` can be provided for the path to match anything. + +## Troubleshooting + +1. User has selected the incorrect application authorities when logging in to Stratos via SSO for the first time. + - The user can update their permissions and other account settings via https://login.< uaa address >/profile +2. Administrator wants to remove the application authorities selection users see when logging in to Stratos via SSO for the first time + - This is carried out at the Admins discretion + - Using the `uaac` cli update the 'autoapprove' property of the client used by Stratos to either `true` for all authorities or a comma separated list for the authorities to be removed. + + ``` + uaac client update <console client> --autoapprove true + ``` +3. User sees the error message `No scopes were granted` when trying to log in to Stratos via SSO + - User may not have selected any of the application authorities when logging in to Stratos via SSO for the first time + - Either of the resolutions to 1 and 2 can be made diff --git a/website/docs/deploy/cloud-foundry/cloud-foundry.md b/website/docs/deploy/cloud-foundry/cloud-foundry.md index 1f876ced45..88961f85b8 100644 --- a/website/docs/deploy/cloud-foundry/cloud-foundry.md +++ b/website/docs/deploy/cloud-foundry/cloud-foundry.md @@ -65,7 +65,7 @@ applications: This will set the the UAA client and UAA secret used to invite users for the default CF only. -See the [invite users guide](../guides/admin/invite-user-guide) for more information about user invites in Stratos. +See the [invite users guide](../../advanced/invite-user-guide) for more information about user invites in Stratos. #### Use of the Default Embedded SQLite Database @@ -127,7 +127,7 @@ cf push console -o splatform/stratos:stable -m 128M -k 384M Alternatively cf push using a manifest -- download [manifest-docker.yml](../../../manifest-docker.yml) or create your own manifest file: +- download [manifest-docker.yml](https://raw.githubusercontent.com/cloudfoundry/stratos/master/manifest-docker.yml) or create your own manifest file: ```yaml applications: - name: console @@ -147,7 +147,7 @@ Follow instructions [here](db-migration). ## Use SSO Login -By default Stratos will present its own login UI and only supports username and password authentication with your UAA. You can configure Stratos to use UAA's login UI by specifying the the `SSO_LOGIN` environment variable in the manifest, for example: +By default Stratos will present its own login UI and only supports username and password authentication with your UAA. You can configure Stratos to use UAA's login UI by specifying the `SSO_LOGIN` environment variable in the manifest, for example: ``` applications: diff --git a/website/docs/developer/deploy.md b/website/docs/developer/deploy.md new file mode 100644 index 0000000000..24c4e3305c --- /dev/null +++ b/website/docs/developer/deploy.md @@ -0,0 +1,8 @@ +--- +title: Deploy Dev Guide +sidebar_label: Overview +--- + +As described in the [Deploying Stratos](../overview.md) section there are a number of ways that Stratos can be deployed. From a development perspective some, like `cf push`, are simpler than others, like `helm install`. + +We're looking at expanding our documentation for all cases, what is found here is the start of that process. \ No newline at end of file diff --git a/website/docs/developer/developers-guide-helm.md b/website/docs/developer/developers-guide-helm.md new file mode 100644 index 0000000000..7a04476f8b --- /dev/null +++ b/website/docs/developer/developers-guide-helm.md @@ -0,0 +1,59 @@ +--- +title: Helm Development Guide +sidebar_label: Helm +--- + + +:::info +This document is currently in progress and will be expanded in the future. +::: + + +## Building Images + +The images references by the helm charts are built in two stages. + +### Base Images + +Changes to these happen infrequently, so not every dev cycle requires this step. + +1. Ensure your docker hub credentials are set + 1. `docker login` +1. Create and push images to docker hub + 1. `cd deploy/stratos-base-images` + 1. `./build-base-images.sh -o dockerhuborgname -p` + - `p` pushes the images to dockerhub. Replace `dockerhuborgname` with the docker hub org where test images should be pushed + 1. Wait for script to complete, this may take a while + +:::note +If you receive the error ``"--squash" is only supported on a Docker daemon with experimental features enabled`` during build enable +experimental feature by +1. Add `"experimental": true` to ~/.docker/config.json +1. Restart docker - `sudo systemctl restart docker` +1. `docker version` should show client with experimental feature enabled +::: + +### All Other Images + +Changes to these will occur more often and are built upon the base images created above. This step is executed with the command command used +to build the charts below. + +## Building Charts + +1. Ensure your docker hub credentials are set + 1. `docker login` +1. Create the charts, and create and push images to docker hub + 1. `cd deploy/kubernetes` + 1. `./build.sh -t 4.0.1 -o dockerhuborgname -p` + -`p` pushes the images to dockerhub. Replace `dockerhuborgname` with the docker hub org where test images should be pushed. This should match where your base images are + -`t` will tag the images with a version + 1. The new charts with updated image references will be created in `deploy/kubernetes/helm-charts` + +### Testing built output + +1. `cd deploy/kubernetes` +1. Install the usual way, with your namespace and values (if required). For example + ``` + helm install console helm-chart --namespace=console --values stratos-values.yaml + ``` + diff --git a/website/docs/developer/frontend.md b/website/docs/developer/frontend.md index 572dd2032a..3d8659cfb8 100644 --- a/website/docs/developer/frontend.md +++ b/website/docs/developer/frontend.md @@ -12,12 +12,12 @@ Before making changes to the frontend code you should be familiar with 1. Redux / NGRX / Observables 1. Node / NPM -There are a some introduction style resources [here](/docs/developer/developers-guide-env-tech.md). There's also some advice on helpful [VS code plugins](/docs/developer/developers-guide-env-tech#vs-code-plug-ins). If you feel comfortable with these and are happy with your dev environment please skip straight to +There are a some introduction style resources [here](./developers-guide-env-tech.md). There's also some advice on helpful [VS code plugins](./developers-guide-env-tech#vs-code-plug-ins). If you feel comfortable with these and are happy with your dev environment please skip straight to [Set up Dependencies](#set-up-dependencies). ## Set up Dependencies -* Set up a Stratos backend. Both backend and frontend exist in this same repo. Follow the [Backend Development](/docs/developer/introduction#build--run-locally) set up guide. +* Set up a Stratos backend. Both backend and frontend exist in this same repo. Follow the [Backend Development](./introduction#build--run-locally) set up guide. * Install [NodeJs](https://nodejs.org) (if not already install) (minimum node version 12.13.0) * Install [Angular CLI](https://cli.angular.io/) (if not already install) - `npm install -g @angular/cli` @@ -55,7 +55,7 @@ We use the angular material theming mechanism. See [here](https://material.angul ### Extensions -Documentation on extensions can be found [here](/docs/extensions/introduction). From a developers perspective extensions are managed by npm packages. +Documentation on extensions can be found [here](../extensions/introduction). From a developers perspective extensions are managed by npm packages. The default set are in `./src/frontend/packages`, any package added directly here will be automatically included by the build. At build time the Stratos Devkit (`./src/frontend/packages/devkit`) will ensure all packages are imported correctly and theming, both component and console level, are applied correctly. diff --git a/website/docs/developer/introduction.md b/website/docs/developer/introduction.md index 8741bb4125..e2e70572b7 100644 --- a/website/docs/developer/introduction.md +++ b/website/docs/developer/introduction.md @@ -32,6 +32,6 @@ This will build both the frontend and backend and run the backend in a mode wher You can open a web browser and navigate to (https://127.0.0.1:5443) and login with username `admin` and password `admin`. -> To develop the frontend we recommend reading through the [frontend](/docs/developer/frontend) doc. This includes a faster way to run Stratos and see your changes. +> To develop the frontend we recommend reading through the [frontend](./frontend) doc. This includes a faster way to run Stratos and see your changes. -> Additional back end docs are available [here](/docs/developer/backend) before making any changes to the code. +> Additional back end docs are available [here](./backend) before making any changes to the code. diff --git a/website/docs/extensions/frontend.md b/website/docs/extensions/frontend.md index 6bed192aae..3a72048b86 100644 --- a/website/docs/extensions/frontend.md +++ b/website/docs/extensions/frontend.md @@ -15,7 +15,7 @@ We use Decorators to annotate components to indicate that they are Stratos exten An example illustrating the various front-end extension points of Stratos is included in the folder `src/frontend/packages/example-extensions`. -To run Stratos with these customizations see [here](/docs/extensions/introduction#acme). +To run Stratos with these customizations see [here](./introduction#acme). For a walk-through of extending Stratos, see [Example: Adding a Custom Tab](#example-adding-a-custom-tab). @@ -60,7 +60,7 @@ Your route should have the following metadata in the `data` field of the route: Where `<TITLE>` is the text label to show in the side navigation and `<ICON NAME>` is the icon to use. -> The routing module must be, or referenced by, the core routing module as described [above](/docs/extensions/frontend#including-modules-and-routes) +> The routing module must be, or referenced by, the core routing module as described [above](./frontend#including-modules-and-routes) An example routing module would be: @@ -101,11 +101,11 @@ Tabs can be added to the following views in Stratos: - The Cloud Foundry Org view that shows detail for a Cloud Foundry organization - The Cloud Foundry Space view that shows detail for a Cloud Foundry space -A step by step guide on how to create a custom tab can be found [below](/docs/extensions/frontend#example-adding-a-custom-tab). +A step by step guide on how to create a custom tab can be found [below](./frontend#example-adding-a-custom-tab). For example: -![Example Application tab extension](../../images/extensions/app-tab-example.png) +![Example Application tab extension](/images/extensions/app-tab-example.png) The approach for all of these is the same: @@ -130,14 +130,13 @@ The approach for all of these is the same: - < LABEL > is the text label to use for the tab - < LINK > is the name to use for the route (this must only contain characters permitted in URLs) An example is included in the file `src/frontend/packages/example-extensions/src/app-tab-extension/app-tab-extension.component.ts`. -1. Declare the component to avoid Angular tree shaking - - In the same module that the component is 'declared' in add the following to `imports` +1. Declare the component to avoid Angular tree shaking. In the same module that the component is 'declared' in add the following to `imports` ``` ExtensionService.declare([ <component name>, ]) ``` -> The module referencing the component, or another referencing it, must be imported by the core module as described [above](/docs/extensions/frontend#including-modules-and-routes). + > The module referencing the component, or another referencing it, must be imported by the core module as described [above](./frontend#including-modules-and-routes). ### Custom Actions @@ -152,7 +151,7 @@ Actions can be added to the following views in Stratos: An action is a icon button that appears at the top-right of a View. For example: -![Example Application action extension](../../images/extensions/appwall-action-example.png) +![Example Application action extension](/images/extensions/appwall-action-example.png) The approach for all of these is the same: @@ -179,15 +178,14 @@ The approach for all of these is the same: - < LABEL > is the text label to use for the tooltip of the icon (optional) - < LINK > is the name to use for the route (this must only contain characters permitted in URLs) An example is included in the file `src/frontend/packages/example-extensions/src/app-action-extension/app-action-extension.component.ts`. -1. Declare the component to avoid Angular tree shaking - - In the same module that the component is 'declared' in add the following to `imports`. - ``` - ExtensionService.declare([ - <component name>, +1. Declare the component to avoid Angular tree shaking. In the same module that the component is 'declared' in add the following to `imports`. + ``` + ExtensionService.declare([ + <component name>, ]) - ``` + ``` -> The module referencing the component, or another referencing it, must be imported by the core module as described in [above](/docs/extensions/frontend#including-modules-and-routes) + > The module referencing the component, or another referencing it, must be imported by the core module as described in [above](./frontend#including-modules-and-routes) ### Loading Indicator @@ -268,7 +266,7 @@ A customization service provides a number of smaller extension points. |Property | Description| |--|--| -|hasEula| True if there's a EULA to show. When set to true the asset `/core/eula.html` must exist. For information about custom package assets see the images section [here](/docs/extensions/theming#new-images). | +|hasEula| True if there's a EULA to show. When set to true the asset `/core/eula.html` must exist. For information about custom package assets see the images section [here](./theming#new-images). | |copyright| Text shown at the bottom of the side nav| |logoText| Text shown with the side nav logo| |aboutInfoComponent| Replace the component used in the Stratos `About` page| @@ -456,4 +454,4 @@ export class MyExampleModule { } ### Run it -You should now be able to run Stratos [locally](/docs/developer/introduction) and see this new tab on the application page for an application. +You should now be able to run Stratos [locally](../developer/introduction#build--run-locally) and see this new tab on the application page for an application. diff --git a/website/docs/extensions/introduction.md b/website/docs/extensions/introduction.md index b36474090c..775068c4c9 100644 --- a/website/docs/extensions/introduction.md +++ b/website/docs/extensions/introduction.md @@ -5,7 +5,7 @@ sidebar_label: Introduction Stratos can be customized in a number of ways. Colors and fonts can be updated to add unique branding, additional menu items can be added in a number of places, custom EULAs can be used, new Stratos Jetstream (backend) endpoints, and much more. -> For those with existing customization migrating to 4.0 please see our [upgrade guide](/docs/extensions/v4-migration). +> For those with existing customization migrating to 4.0 please see our [upgrade guide](./v4-migration). ## Approach @@ -15,16 +15,16 @@ In order to customize Stratos, you will need to fork the Stratos GitHub reposito Frontend customizations are placed in angular packages in the folder named `src/frontend/packages`. In the future you will be able to host these packages in npm and bring them into Stratos in the usual npm dependency way. There are no additional processes or build steps required for Stratos to then integrate these packages. All steps will be automatically applied under the hood during `npm install` and when `ng build`/`ng serve` runs. -Information on custom theming can be found in the [theming page](/docs/extensions/theming). +Information on custom theming can be found in the [theming page](./theming). -Information on additional functionality can be found in the [extensions page](/docs/extensions/frontend). +Information on additional functionality can be found in the [extensions page](./frontend). ### Backend (Jetstream) Jetstream customizations are written in go and can be added in `src/jetstream/plugins`. In the future we hope to combine this work with the frontend changes, such that all functionality for a feature can be applied to Stratos in a similar way. -More information can be found in the [custom plugins page](/docs/extensions/backend). +More information can be found in the [custom plugins page](./backend). ## Examples @@ -48,14 +48,14 @@ To run Stratos with these customizations - '@example/extensions' - '@example/theme' ``` -1. Run Stratos the usual way, for more information see the [Developer Guide](/docs/developer/introduction). +1. Run Stratos the usual way, for more information see the [Developer Guide](../developer/introduction). ### SUSE More advanced, real world examples can be found the in SUSE Stratos repository, again containing [theming](https://github.com/SUSE/stratos/tree/master/src/frontend/packages/suse-theme) and [functionality](https://github.com/SUSE/stratos/tree/master/src/frontend/packages/suse-extensions) customizations. -To run Stratos with these customizations simply start the console the usual way from that repo, for more information see the [Developer Guide](/docs/developer/introduction). +To run Stratos with these customizations simply start the console the usual way from that repo, for more information see the [Developer Guide](../developer/introduction). ## Further Reading -Detailed instructions on how to customize the [theme](/docs/extensions/theming), [frontend functionality](/docs/extensions/frontend) and [backend](/docs/extensions/backend) can be found in this section. +Detailed instructions on how to customize the [theme](./theming), [frontend functionality](./frontend) and [backend](./backend) can be found in this section. diff --git a/website/docs/extensions/v4-migration.md b/website/docs/extensions/v4-migration.md index 1317327c26..06f87d5647 100644 --- a/website/docs/extensions/v4-migration.md +++ b/website/docs/extensions/v4-migration.md @@ -8,14 +8,14 @@ extensions by opening the door to npm style plugins. Prominent changes include - `custom-src` has been removed along with the symlink approach of including files. Custom code is now added as npm packages in `src/frontend/packages`. -Modules and routes are now exposed in a more standard package way. More info [here](/docs/extensions/frontend#including-modules-and-routes). +Modules and routes are now exposed in a more standard package way. More info [here](./frontend#including-modules-and-routes). Some existing components will not be included in some production style builds without also declaring them to the extension service, see usages of `ExtensionService.declare`. -- Material Design theming approach has been expanded to include many common colors, this removes the need to apply custom styles in a lot of cases. More info [here](/docs/extensions/theming#colors). -- Dark theme is applied in a different way. More info [here](/docs/extensions/theming#dark-theme). -- Image assets are replaced in a different way. More info [here](/docs/extensions/theming#images). -- Custom component can now be themed, so theme colors can be accessed from within and applied. More info [here](/docs/extensions/theming#components). -- A new 'loading' indicator has been added that you may wish to customize, more info [here](/docs/extensions/frontend#loading-indicator). +- Material Design theming approach has been expanded to include many common colors, this removes the need to apply custom styles in a lot of cases. More info [here](./theming#colors). +- Dark theme is applied in a different way. More info [here](./theming#dark-theme). +- Image assets are replaced in a different way. More info [here](./theming#images). +- Custom component can now be themed, so theme colors can be accessed from within and applied. More info [here](./theming#components). +- A new 'loading' indicator has been added that you may wish to customize, more info [here](./frontend#loading-indicator). ## Basic Migration Steps To aid in migrating we've provided these instructions. diff --git a/website/docs/introduction.md b/website/docs/introduction.md index f9706d79a5..b78743dc07 100644 --- a/website/docs/introduction.md +++ b/website/docs/introduction.md @@ -12,7 +12,7 @@ For Cloud Foundry, it allows users and administrators to both manage application For Kubernetes, it provides Developers with views of their Kubernetes resources, the ability to view and deploy Helm Charts and view Workloads. -![Stratos Application view](../images/screenshots/app-summary.png) +![Stratos Application view](/images/screenshots/app-summary.png) ## Quick Start diff --git a/website/docs/overview.md b/website/docs/overview.md index a0c2c39d92..83e9cac93c 100644 --- a/website/docs/overview.md +++ b/website/docs/overview.md @@ -12,7 +12,7 @@ Stratos stores endpoint metadata in a relational database. Administrators of Str The high-level architecture of Stratos is shown in the diagram below: -![Stratos High-Level Architecture](../images/high-level-arch.png) +![Stratos High-Level Architecture](/images/high-level-arch.png) The main components: diff --git a/website/docs/status_updates.md b/website/docs/status_updates.md deleted file mode 100644 index e8751ec4ba..0000000000 --- a/website/docs/status_updates.md +++ /dev/null @@ -1,721 +0,0 @@ -# Status Updates - -Weekly status updates are published here. - -## 12 April 2019 -This week the Stratos team has been... -- Presenting Stratos v2.4 features to the community in the latest project update -- Working towards phase two of the extension project. -- Finishing off the remove roles from cf user feature. -- Finishing off adding an optional 'connect' step to the register endpoint stepper. -- Reviewing and shepherding PRs into master, minor bug fixes. - -## 5 April 2019 -This week the Stratos team have mostly been ... -- Improving e2e reliability and merging existing PRs. -- Improving the create/deploy app flow. Users will now only be presented with a single button to start the flow. Their first step is to decide where the source for the app will come from (github, gitlab, locally, etc) or to create without source. -- Removing the endpoints page when there's no persistent storage. This is now on par with the home/favourites page. -- Adding an option that will allow the user to stay signed in to the console (if option is checked and the page remains visible). -- Starting working towards the ability to remove all roles from a cf user with a single click (after confirmation). -- Starting working towards an optional 'connect' step when registering an endpoint. -- Fixed a few minor trivial bugs - -## 22 March 2019 - -This week: - -- Work continues to complete the addition of support for Cloud Foundry User Provided Services. This is almost done. A few small issues and corner-cases cropped up which we have been working on. -- Added support for slide-in help content and wired this into the endpoint connection dialog -- We continued to refine the visual presentation of endpoints within Stratos - -## 15 March 2019 - -This week: - -- Enhancing the endpoint registration view to use a tile selector -- Updating our docs on our use cases and requirements for the Cloud Foundry V3 API - -## 8 March 2019 - -This week: - -- Added a card view to the endpoints view -- Continued to refine the support for User Provided Service Instances -- Working on support to allow list views to show different entity types in the same list -- Ensured features requiring persistence database are not enabled when using SQLite - -## 1 March 2019 - -This week: - -- Updated the front-end code to use Angular 7 (in review) -- Continued to add support for User Provided Service Instances -- Reviewed, tweaked and merged our first phase of work on improving extensions support - -## 22 February 2019 - -This week: - -- The user invite feature has been reviewed and merged -- Front-end code restructuring to support next phase of extensions work -- Pre-work for update to Angular 7 -- Add support for User Provided Services -- Updates to our documentation on our needs for the Cloud Foundry V3 API - -**Important:** - -Hello All - Two CVEs in Stratos were recently discovered - both are fixed in 2.3.0 - so we **encourage you to update** to that latest version to address these issues. Both relate to the session cookie: - -https://www.cloudfoundry.org/blog/cve-2019-3783/ -https://www.cloudfoundry.org/blog/cve-2019-3784/ - -**Deployment Survey** - -We want to understand more about how everyone is deploying Stratos - so we can better support the most used deployment scenarios and (possibly) remove some of the deployment mechanisms that aren’t being used - we’ve got a quick survey we’d love it if you could fill out: https://www.surveymonkey.co.uk/r/MLXDMKG - -## 15 February 2019 - -This week: - -- 2.3.0 was Released! -- The user Favourites feature has been reviewed and merged -- We continue to fix and merge many of the outstanding PRs around User Favourites and Scalability -- We spent half a day reviewing our next steps to re-structure the front-end code as part of our work towards the next phase of extensibility. -- We continue to improve the suite of automated deployment tests that run continuosuly - -## 8 February 2019 - -Release 2.3.0 is all ready to go and we will publish this on Monday. - -Otherwise, we've been working on: - -- Finishing a first version of the User Favourites feature -- Finishing a first version of the User Invitation feature -- Investigating possible security issue: https://github.com/cloudfoundry/stratos/issues/3385 -- Updates to our extensions approach - -## 1 February 2019 - -We are looking to release 2.3.0 next week having completed testing. - -Work has otherwise continued on four tracks: - -- Closing out existing PRs -- Finishing a first version of the User Favourites feature -- Finishing a first version of the User Invitation feature -- Improving test automation - -## 25 January 2019 - -This week: - -- Published a release candidate for Stratos 2.3.0 which we continue to test -- Worked to update a few outstanding PRs - we are aiming to close out some of the remaining PRs this sprint, ahead of a re-org of our front-end code to better support extensions to Stratos in the future -- Continued to refine the User Favourites feature -- Worked to add the ability to invite a user into a Cloud Foundry organisation -- Continued to improve the reliability of automated deployment tests in Jenkins - -## 18 January 2019 - -We created a test release of 2.3.0 and identified a number of issues to be resolved for release. - -The remainder of this week was spent fixing the issues identified. - -A 2.3.0 Release Candidate will be published later today or on Monday of next week. - -We hope to finish testing and publish the 2.3.0 release next week. - -The 2.3.0 Change Log has been updated and is available [here](https://github.com/cloudfoundry/stratos/blob/master/CHANGELOG.md) - -## 11 January 2019 - -Happy New Year! - -The focus of the week was in preparing 2.3.0 release: - -- We reviewed, updated and merged over 20 outstanding PRs that were targeted at 2.3.0 release -- We’ve made some improvements to our Helm chart to the external service configuration -- We’ve tweaked our release process and updated and improved our Concourse release pipelines -- Performed initial testing of 2.3.0 build - -We expect to create a Release Candidate next for 2.3.0 so QA can kick the tyres as well. - -We’re also looking at the roadmap for this year and planning our releases throughout 2019. - -## 21st December 2018 - -Rounding off our year, here's some highlights from the last few weeks. - -- User Favourites - Great progress has been made and we're almost ready to merge to master. -- Page Tabs & Side Nav Update - The horizontal tabs shown in sections such as Application, Cloud Foundry, Organisation, etc have found a smart new home in a side nav. For screenshots see PR #3289 - -PRs: -- Add a routes list to the CF tabs, Routes Refactor & Route Bug Fixes (#3292) -- Add deployment info for apps deployed via docker & fix info for local/archive apps (#3291) -- Improve focus & tabbing (#3288) -- Application Env Var fix & improvements (#3286) -- Fix exception when navigating away from the first deploy app step (#3277) -- Add service plan tab to service pages (#3275) -- List Multifilter Improvements (don't show cloud foundry selector if there's only one connected) (#3270) -- Update side nav logo with light version of new logo (#2921) -- Hide app vars tab if user is not a space developer (#3247) -- Show app chip list for space routes list bound apps (#3041) - -Community PRs: - -- Fixes Async Service Provisioning Bug (#3090) (merged) -- [autoscaler] add autoscaler tab in app page (#3266) - - -## 7th December 2018 - -The team are continuing to tackle some of the smaller issues form the backlog as well as the following two features: - -- User Favorites - Allowing users to favorite Apps, Orgs, Spaces etc and to provide quick access to these items. -- Inviting Users - Support for inviting users to a Cloud Foundry system via email and to grant appropriate rights to an Org/Space - -PRs: - -- Reduce size of Docker All-in-one image [\#3261](https://github.com/cloudfoundry/stratos/pull/3261) -- Tidy up CLI login info [\#3269](https://github.com/cloudfoundry/stratos/pull/3269) -- Add service provider name to marketplace service card [\#3268](https://github.com/cloudfoundry/stratos/pull/3268) -- Add link to dashboard in service instance table [\#3267](https://github.com/cloudfoundry/stratos/pull/3267) -- Add Org and Space status bar to Org/Space Cards [\#3265](https://github.com/cloudfoundry/stratos/pull/3265) -- Helm port configuration improvements and unit tests [\#3264](https://github.com/cloudfoundry/stratos/pull/3265) -- Add confirmation dialog to `Restage` app [\#3263](https://github.com/cloudfoundry/stratos/pull/3263) -- Remove global manage apps link [\#3259](https://github.com/cloudfoundry/stratos/pull/3259) -- Add user has roles filter to users tables [\#3258](https://github.com/cloudfoundry/stratos/pull/3258) - - -## 30th November 2018 - -A quieter week for the Stratos team - with some out on vacation as we head towards the festive season. - -PRs this week: - -- Make the table multi actions more obvious [\#3251](https://github.com/cloudfoundry/stratos/pull/3251) -- Hide app vars tab if user is not a space developer [\#3247](https://github.com/cloudfoundry/stratos/pull/3247) -- Improve resilience of e2e tests [\#3246](https://github.com/cloudfoundry/stratos/pull/3246) -- App Wall filter and sort controls size tweaks [\#3243](https://github.com/cloudfoundry/stratos/pull/3243) -- Deploy App: Add notification toast [\#3242](https://github.com/cloudfoundry/stratos/pull/3242) - -## 23rd November 2018 - -We've been continuing to test Stratos 2.2.0 and improving test automation and E2E test reliability. - -We've started to work through some of the GitHub issues - the focus for the next couple of sprints is to fix and close out some of the smaller issues and bugs. - -We're also working to improve the scalability of Stratos with large numbers of Apps/Orgs/Spaces/Users. - -PRs this week: - -- App Deploy: Add Public GitLab Repository support [\#3239](https://github.com/cloudfoundry/stratos/pull/3239) -- Fix CLI info formatting [\#3237](https://github.com/cloudfoundry/stratos/pull/3237) -- Show better error message on login screen when account locked [\#3235](https://github.com/cloudfoundry/stratos/pull/3235) -- Add Route: Use correct label for submit button [\#3231](https://github.com/cloudfoundry/stratos/pull/3231) -- Fix for cancel broken on add route [\#3228](https://github.com/cloudfoundry/stratos/pull/3228) -- Backend: Update to latest version of Echo Web Server [\#3216](https://github.com/cloudfoundry/stratos/pull/3216) -- Show refresh button for latest modified application lists [\#3213](https://github.com/cloudfoundry/stratos/pull/3213) -- Scalability: Handle large number of apps in cf dashboards [\#3212](https://github.com/cloudfoundry/stratos/pull/3212) -- Support prometheus-boshrelease as a metrics endpoint [\#3202](https://github.com/cloudfoundry/stratos/pull/3202) -- Fix alignment of arrow when no endpoints registered [\#3234](https://github.com/cloudfoundry/stratos/pull/3234) -- Run customize when prebuild and log node and npm versions [\#3232](https://github.com/cloudfoundry/stratos/pull/3232) -- Add stricter check for jetstream error object [\#3223](https://github.com/cloudfoundry/stratos/pull/3223) -- Improve paginated/pagination action naming [\#3218](https://github.com/cloudfoundry/stratos/pull/3218) -- Fix display of generic error bar [\#3214](https://github.com/cloudfoundry/stratos/pull/3214) -- Only show + icon when we have at least one connected CF [\#3211](https://github.com/cloudfoundry/stratos/pull/3211) - -## 16th November 2018 - -This week the team has been working on: - -- Testing Stratos 2.2.0 -- Enabling Extensions to be published separately -- Test automation -- E2E Test reliability -- Space service tidy up [\#3197](https://github.com/cloudfoundry/stratos/pull/3197) - -## 9th November 2018 - -This week the team has been: - -- Testing Stratos 2.2.0 -- Working towards enabling Extensions to be published separately -- Updating developer documents: [\#3184](https://github.com/cloudfoundry/stratos/pull/3184) -- Adding initial document for back-end plugins: [\#3189](https://github.com/cloudfoundry/stratos/pull/3189) -- Fixed bug with deploying with folder upload: [\#3191](https://github.com/cloudfoundry/stratos/pull/3191) - -## 2nd November 2018 - -This week we have mostly been: - -- Releasing Stratos 2.2.0. More details can be found at https://github.com/cloudfoundry/stratos/releases/tag/2.2.0. -- Discussing integration of the Autoscaler (https://github.com/cloudfoundry-incubator/app-autoscaler) UI (https://github.com/cloudfoundry-incubator/app-autoscaler-ui) into Stratos. We’ve provided a first round of guidance to the Autoscaler team and are looking forward to the second round next week. The initial designs look great. -- Knowledge sharing activities revolving helm charts and concourse release pipelines - -## 26th October 2018 - -The team were away attending a workshop this week. - -The only item of note is that we have now forked the go-flags package that was causing broken dependency issues for us and updated Stratos to use this package. This should allow us to avoid future issues with this package. - -## 19th October 2018 - -We are preparing for this month's 2.2.0 release which will be out towards the end of the month. - -Highlights for this week: - -- Extensions: Fix for side nav. Add initial docs [\#3140](https://github.com/cloudfoundry/stratos/pull/3140) -- User menu improvements [\#3136](https://github.com/cloudfoundry/stratos/pull/3136) -- Metrics: Show table of cell health state changes instead of chart [\#3135](https://github.com/cloudfoundry/stratos/pull/3135) -- Metrics: Update app instance cell data when scaling up [\#3133](https://github.com/cloudfoundry/stratos/pull/3133) -- Metrics: Ensure CF Cells info is shown for non cf admins [\#3121](https://github.com/cloudfoundry/stratos/pull/3121) -- Add 'type text to continue' to confirmation modal [\#3131](https://github.com/cloudfoundry/stratos/pull/3131) -- Delete App Stepper: Disable delete of routes and services that are bound to other app/s [\#3129](https://github.com/cloudfoundry/stratos/pull/3129) -- Endpoints Table: Only show 'Admin' check icon for cf endpoints [\#3132](https://github.com/cloudfoundry/stratos/pull/3132) - - -## 12th October 2018 - -The team have been preparing for and attending the CF Summit in Basel. - -## 5th October 2018 - -The team have been preparing for the CF Summit in Basel next week. - -Highlights for this week (continuing from last week): - -- E2E Tests and Automation - Focusing on making the E2E tests more resilient to timing issues. - -- Metrics - Added Diego Cell to the App Instances table when metrics are available and added a Cell view which shows metrics for a given Diego cell. - -Fixes: - -- Ensure we handle orgs with no users [\#3098](https://github.com/cloudfoundry/stratos/pull/3098) - fixes a bug creating a space in a new org. - -- Endpoint Registration: Only show SSO option for CF Endpoint type [#\3105](https://github.com/cloudfoundry/stratos/pull/3105) - -## 28th September 2018 - -This week saw the release of 2.1.1 and an update 2.1.2. It was necessary to tag a 2.1.2 release to resolve a broken backend dependency that would affect users deploying via 'cf push'. You should use 2.1.2 and not 2.1.1. - -*2.1.2 Highlights* - -- Fix go-flags dependency pinned version broken [\#3071](https://github.com/cloudfoundry/stratos/pull/3071) - -- App wall filtering can stop working with some filter combinations [\#3043](https://github.com/cloudfoundry/stratos/pull/3043) - -- Can not connect a metrics endpoint [\#3035](https://github.com/cloudfoundry/stratos/issues/3035) - -- Backend build issue due to the pinned commit for a dependency being removed [\#3060](https://github.com/cloudfoundry/stratos/pull/3060) - -- Metrics: Wrong job can be matched up when there are multiple jobs [\#3057](https://github.com/cloudfoundry/stratos/pull/3057) - - -Highlights for this week (continuing from last week): - -- Extensions - Work continues on adding extension points in the UI. Extensions is targeted for 2.2.0 in October. - -- E2E Tests and Automation - Focusing on making the E2E tests more resilient to timing issues. - -- Metrics - Work to tidy up the existing Cloud Foundry Application metrics is complete. - -## 21st September 2018 - -This week saw the release of 2.1.0 - the highlights are: - -- Stratos frontend can be pre-built before pushing to Cloud Foundry to enable AOT and reduce push time -- SSO support refinements with the ability to now connect a Cloud Foundry endpoint using SSO in addition to SSO login to Straos itself -- Ability to specify manifest overrides when deploying an application -- Ability to optionally specify Client ID anc Client Secret when registering an endpoint -- Add ability to restage an application -- Endpoints list now shows logged in user's username and whether they're an admin -- Switched to new Stratos logo for login and about pages -- Backend improvements to make it easier for developers to develop with -- Security fixes - -Full release information is available [here](https://github.com/cloudfoundry/stratos/releases/tag/2.1.0). - -Highlights for this week (continuing from last week): - -- Extensions - Work continues on adding extension points in the UI. Extensions is targeted for 2.2.0 in October. - -- E2E Tests and Automation - Focusing on making the E2E tests more resilient to timing issues. We have also spent time getting the E2E tests to run against BrowserStack to allow us to automate testing of Stratos across multiple browser types/platforms. - -- Metrics - Work to tidy up the existing Cloud Foundry Application metrics is complete. - -- JSON Schema support for Service Instance binding - We have been working to update PR [#2997](https://github.com/cloudfoundry/stratos/pull/2997) to get this feature into Stratos. - -Fixes/Misc: - -- Fix app wall filtering issue [#3043](https://github.com/cloudfoundry/stratos/pull/3043) -- Fix for Connect button not being enabled when SSO is the default [#3004](https://github.com/cloudfoundry/stratos/pull/3004) -- Version number always reports 'dev' [#3001](https://github.com/cloudfoundry/stratos/pull/3001) -- Diagnostics does not show GitHub details when cloned via HTTPS [#3007](https://github.com/cloudfoundry/stratos/pull/3007) -- Fix for NULL Client Secret bug [#3015](https://github.com/cloudfoundry/stratos/pull/3015) -- Fix bug where token IDs are not set properly when upgrading from a previous version [#3017](https://github.com/cloudfoundry/stratos/pull/3017) - -## 14th September 2018 - -We've been preparing the 2.1.0 release this week. This will be published next week. Going forward we will be publishing a new release every month. The release notes for 2.1.0 have been created and are [here](https://github.com/cloudfoundry/stratos/blob/master/CHANGELOG.md#210). - -Highlights for this week: - -- Extensions - Work continues on adding extension points in the UI. We have PRs up to support adding new side-nav items, tabs and actions. This already goes beyond what could be done in V1 of Stratos. The extension mechanism is also much cleaner due to the use of decorators. Extensions is targeted for 2.2.0 in October. - -- E2E Tests and Automation - Continuing to build out the E2E test suite and automate the deploy and test of Stratos in different environments (pushed to Cloud Foundry, pushed to Cloud Foundry with MySQL and Postgres, pushed to Cloud Foundry with SSO, Docker compose, Docker All-in-One) - -- Metrics - Work is almost complete on improving the metrics integration and presentation in the UI. Rather than display a timeline under each graph, we are switching to a control to allow you to choose the time period. - -Other updates: - -- Allow auto-registration name to be configured [#2986](https://github.com/cloudfoundry/stratos/pull/2986) -- SSO: Refinements [#2982](https://github.com/cloudfoundry/stratos/pull/2982) - - Fix consistency of SSO casing and hyphenation => 'Single Sign-On' - - Make SSO default when connecting if enabled for the endpoint - - Move the SSO Allowed checkbox to the bottom in 'Advanced Options', so that the form does not move when the checkbox is checked and the advisory text appears -- Add check to make sure DB Schema migrations have completed (fixes an issue with diagnostics and a possible race-condition when deployed via Helm or Docker Compose) [#2977](https://github.com/cloudfoundry/stratos/pull/2977) - - -## 7th September 2018 - -Update for this week: - -- Single Sign-On - Added the ability to enable SSO Login when using the Setup screen. - -- Metrics - Work continues to improve the metrics integration and presentation in the UI - -- Extensions - Work has started on adding extension points in the UI. This will be accomplished via decorators, in the same way that Angular uses decorators for Components etc. [#2962](https://github.com/cloudfoundry/stratos/pull/2962) - -- Deploy App Manifest overrides - Added an extra step in the 'Deploy App' flow to allow you to override manifest settings before deploying [#2924](https://github.com/cloudfoundry/stratos/pull/2924) - -- Show Service plan cost when selecting a service plan (if not free) [#2959](https://github.com/cloudfoundry/stratos/pull/2959) - -- E2E Tests - Resolved GitHub rate limits issues when running E2E Tests [#2949](https://github.com/cloudfoundry/stratos/pull/2949) - -- Travis - The Travis build has been restructured to run jobs in parallel and E2E tests now run against PRs in addition to branches. - - -## 31st August 2018 - -Update for this week: - -- Go-backend re-structure - The additional cleanup work has been merged. - -- Single-Sign-On - Further work on Single-Sign On: - - Improvements - improve error handling, add navigate straight to login option [#2522](https://github.com/cloudfoundry/stratos/pull/2522) - - Link tokens rather than copying them [#2916](https://github.com/cloudfoundry/stratos/pull/2916) - - Allow a Cloud Foundry endpoint to be connected with SSO login [#2928](https://github.com/cloudfoundry/stratos/pull/2928) - -- Allow Client ID and Secret to be set when registering an endpoint [#2920](https://github.com/cloudfoundry/stratos/pull/2920) - -- Scalability: - - Change application list in service instance table row from vertical to chip list [#2915](https://github.com/cloudfoundry/stratos/pull/2915) - - Convert space apps list from local to remote [#2913](https://github.com/cloudfoundry/stratos/pull/2913) - -- Metrics - Work has started on tidying up and improving the metrics views - -## 24th August 2018 - -Update for this week: - -- Go-backend re-structure - This work has now been merged and should make it easier to develop with and contribute to. Some additional clean up was done and is awaiting PR review. - -- Extended end-to-end test suite - Further work to build-out the E2E test suite. See: - - Services E2E: Service Instance creation with App Binding [#2855](https://github.com/cloudfoundry/stratos/pull/2855) - - E2E: Basic Application Routes tests [#2862](https://github.com/cloudfoundry/stratos/pull/2862) - - E2E: Basic Application Instances tests [#2863](https://github.com/cloudfoundry/stratos/pull/2863) - - E2E: Basic test for Cf/Org/Space users tables [#2904](https://github.com/cloudfoundry/stratos/pull/2904) - -- Small improvements and fixes for the Diagnostics page [#2860](https://github.com/cloudfoundry/stratos/pull/2860) - - -## 17th August 2018 - -Update for this week: - -- 2.0.1 - We tagged a 2.0.1 release. This is identical to 2.0.0 and only fixes an issue with a broken dependency - one of the pinned dependencies was no longer working. This issue only affects you if you were pushing to Cloud Foundry from the 2.0.0 tag. - -- Pre-built UI - We added the ability to pre-build the UI before pushing to Cloud Foundry. This allows you to build the UI with AOT (Ahead-of-time) compilation enabled and push to CF. This will also reduce push time. See the doc [Pre-building the UI](https://github.com/cloudfoundry/stratos/tree/master/deploy/cloud-foundry#pre-building-the-ui). - -- Go-backend re-structure. Work is almost complete on re-structuring the go backend to make it easier to develop with and contribute to. See issue [#2815](https://github.com/cloudfoundry/stratos/issues/2815). - -- Extended end-to-end test suite - -## 10th August 2018 -A similar update to last week, focusing on metrics, testing and the community. - -- Testing the new metrics deployment process. -- Improving the end to end test setup process, this will enable better coverage of service tests. -- Improving test coverage, both unit and end to end, of the often used list component. -- Improving test coverage of entity validation, another often used component. -- Manage application stats requests better in the application wall when there are many many started apps. -- Investigating, solving and shepherding community issues. Some of the stand out ones.. - - Stratos URLs are now consistent. Previously for the same CF the id would change every time it's registered, meaning bookmarks would need updating following every re-registration. Now as long as the CF API url doesn't change the CF id in the url will remain the same. - https://github.com/cloudfoundry/stratos/issues/2798 - - Correctly report application instance counts with respect to app state - https://github.com/cloudfoundry/stratos/issues/2797 - - Apply permissions to application actions (start, restart, etc) - https://github.com/cloudfoundry/stratos/issues/2806 - -## 3 August 2018 -This week we've focused on.. - -- Making metrics components easier to deploy. This will help us apply polish and publicise CF metrics in the coming weeks. -- Improving test suites and coverage. -- Responding to community issues. There's been an increase in use and some great contributions. -- Fixing older, but still valid, issues that haven't quite been important to address at the time. - -In addition we’ve created a short (a few minutes) survey to get a better idea how the community plan to use Stratos. Answers and feedback will directly impact the direction we take Stratos, so the quicker we have responses the quicker we can act on them. Follow this link to start answering https://www.surveymonkey.com/r/2L8XWST - -## 27 July 2018 - -The main news this week is that we have released a first version of Stratos 2.0. - -We've been focused over the past weeks on fixing bugs and improving tests and we're delighted to have reached this milestone. - -The release details are available here - https://github.com/cloudfoundry/stratos/releases/tag/2.0.0. - -We've also started to catch up on some of the outstanding PRs, especially submitted from the community, for example: - -- Change DB schema and backend to support storing a client/secret for each Endpoint [\#2622](https://github.com/cloudfoundry/stratos/pull/2622) -- Fix migrate script to work in Postgresql [\#2601](https://github.com/cloudfoundry/stratos/pull/2601) - -## 20 July 2018 - -This week the focus has been on further testing and refinement of Release Candidate 2. - -We will be publishing a Release Candidate 3 on Monday 23 July. We expect this to be the final RC and will most likely promote this to be the final Version 2 release later next week. - -We've been focused on more test automation and building out more end-to-end tests, in addition to fixing a few bugs. - -We had an issue with the Angular AOT + build optimization which pushed the final RC into next week. This took a while to get to the bottom of. - -For a full list of merged PRs this week, see: [Merged PRs](https://github.com/cloudfoundry/stratos/pulls?page=1&q=is%3Apr+is%3Amerged+updated%3A%3E%3D2018-07-13&utf8=%E2%9C%93). - -## 13 July 2018 - -The SUSE team has been participating in SUSE Hack Week, so there is no change in status this week. - -Work resumes next week on getting a first 2.0 release published. - -## 06 July 2018 - -This week the focus has been on further testing and refinement of Release Candidate 2. - -We've been identifying and fixing issues - see: [Merged PRs]( -https://github.com/cloudfoundry/stratos/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aclosed+updated%3A%3E%3D2018-07-02+). - -## 29 June 2018 - -This week the focus has been on creating a Release Candidate of Version 2. - -In fact, we published two Release Candidates - at the start and end of the week - full details here: -https://github.com/cloudfoundry/stratos/releases - -A large number of bugs and smaller issues have been resolved since last week's Beta 2 release - full details here: https://github.com/cloudfoundry/stratos/compare/2.0.0-beta-002...2.0.0-rc2 - -Work has been focused on testing the Release Candidates and fixing defects. - -## 22 June 2018 - -This week the focus has been on creating a second Beta release of Version 2. - -A large number of bugs and smaller issues have been resolved - full details here: https://github.com/cloudfoundry/stratos/releases/tag/2.0.0-beta-002. - - -## 15 June 2018 - -We are working towards a release of V2. We are now functionally complete and are working through priority 1 issues and defects. - -- The release schedule is updated here - [Roadmap](roadmap.md). -- Priority 1 defects are labelled P1 - you can view them with this [Filter](https://github.com/cloudfoundry/stratos/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3AP1) -- We have tagged a Beta 1 of Version 2 of Stratos today - https://github.com/cloudfoundry/stratos/releases/tag/v2.0.0-beta-001 - -We've been working through a large number of defects this week - - -- Deploy info card shows both file and folder info [\#2351](https://github.com/cloudfoundry/stratos/issues/2351) -- App Service Instances: Modal appears off screen [\#2347](https://github.com/cloudfoundry/stratos/issues/2347) -- Users list is empty after cancel [\#2339](https://github.com/cloudfoundry/stratos/issues/2339) -- We still show large no connected CF endpoints even when you connect a metrics endpoint [\#2332](https://github.com/cloudfoundry/stratos/issues/2332) -- Connected endpoint does not show 'unregister' action [\#2321](https://github.com/cloudfoundry/stratos/issues/2321) -- Service instance page shows no 'no cf connected' warning [\#2320](https://github.com/cloudfoundry/stratos/issues/2320) -- No page header on Cloud Foundry pages when you go directly there [\#2319](https://github.com/cloudfoundry/stratos/issues/2319) -- Service Instance entity is not updated after unbinding an app [\#2317](https://github.com/cloudfoundry/stratos/issues/2317) -- Service broker card intermittently appears immediately below service summary card [\#2314](https://github.com/cloudfoundry/stratos/issues/2314) -- Select cf/org/space in create service instance stepper mentions `app` [\#2313](https://github.com/cloudfoundry/stratos/issues/2313) -- Adding a space scoped service should not show org/space selection step [\#2311](https://github.com/cloudfoundry/stratos/issues/2311) -- Front-end unit tests are unreliable in Travis [\#2308](https://github.com/cloudfoundry/stratos/issues/2308) -- Not all backend tests are run [\#2300](https://github.com/cloudfoundry/stratos/issues/2300) -- Log Stream token refresh does not work [\#2299](https://github.com/cloudfoundry/stratos/issues/2299) -- Marketplace: Service Broker card should be hidden if the broker isn't returned by API [\#2279](https://github.com/cloudfoundry/stratos/issues/2279) -- Endpoint Users are not refetched when an endpoint is reconnected as a different user. [\#2274](https://github.com/cloudfoundry/stratos/issues/2274) -- Marketplace service cards tags list fails to expand [\#2263](https://github.com/cloudfoundry/stratos/issues/2263) -- App bound service is shown in services to bind to list [\#2253](https://github.com/cloudfoundry/stratos/issues/2253) -- Error response handling is broken [\#2242](https://github.com/cloudfoundry/stratos/issues/2242) -- Investigate issue with space-scoped services being returned incorrectly by the `List Services for Space` request [\#2240](https://github.com/cloudfoundry/stratos/issues/2240) -- App Log Stream shows `Connecting....` for apps that aren't running [\#2235](https://github.com/cloudfoundry/stratos/issues/2235) -- Map Existing Routes: Sort by apps attached is broken [\#2210](https://github.com/cloudfoundry/stratos/issues/2210) -- Application: OFFLINE WHILE UPDATING state only allows the delete action. [\#2208](https://github.com/cloudfoundry/stratos/issues/2208) -- Cf/Org/Space filters are not updating on endpoint change [\#2162](https://github.com/cloudfoundry/stratos/issues/2162) -- Component effects are cropped in steppers [\#2116](https://github.com/cloudfoundry/stratos/issues/2116) -- Cloud Foundry orgs and space views contain multiple app-headers [\#2051](https://github.com/cloudfoundry/stratos/issues/2051) -- 2nd row of tabs sometimes disappears [\#2005](https://github.com/cloudfoundry/stratos/issues/2005) -- Instances tab shows Unknown when scaling \(with crashed app\) [\#2002](https://github.com/cloudfoundry/stratos/issues/2002) -- Console setup improvements \#2 [\#1974](https://github.com/cloudfoundry/stratos/issues/1974) -- Services page does not update after connecting/disconnected an SCF [\#1973](https://github.com/cloudfoundry/stratos/issues/1973) -- White bar flashes at top of page on Cloud Foundry page [\#1963](https://github.com/cloudfoundry/stratos/issues/1963) -- Show service type in service wall card [\#2315](https://github.com/cloudfoundry/stratos/issues/2315) -- Show space name in space broker card [\#2312](https://github.com/cloudfoundry/stratos/issues/2312) -- Add confirmation modals where required [\#2257](https://github.com/cloudfoundry/stratos/issues/2257) -- Add git commit id and whether user is an admin on the about page [\#2246](https://github.com/cloudfoundry/stratos/issues/2246) -- Update base images for git vulnerability CVE 2018-11235 [\#2241](https://github.com/cloudfoundry/stratos/issues/2241) -- Update service steppers following async stepper changes [\#2234](https://github.com/cloudfoundry/stratos/issues/2234) -- CF Permissions - Apply to user management [\#2226](https://github.com/cloudfoundry/stratos/issues/2226) -- CF Permissions - Apply to services [\#2225](https://github.com/cloudfoundry/stratos/issues/2225) -- CF Permissions - Apply to App Wall + Summary [\#2224](https://github.com/cloudfoundry/stratos/issues/2224) -- V1 e2e fix [\#2316](https://github.com/cloudfoundry/stratos/pull/2316) ([nwmac](https://github.com/nwmac)) - - -## 08 June 2018 - -The team have been working on the following issues and PRs this week: - -- Front-end unit tests are unreliable in Travis [#3208](https://github.com/cloudfoundry/stratos/issues/2308) - we're seeing a lot of problems with the front-end unit tests when running in Travis - we're continuing to dig into this issue to understand what the cause is, since this is affecting reliability of PR gate checks. - -- Services permissions [#2284](https://github.com/cloudfoundry/stratos/pull/2284) - wiring the user permissions service into the Service UI to ensure users are only presented with actions that they are permitted to perform. - -- Allow metrics endpoint token to be shared [#2283](https://github.com/cloudfoundry/stratos/pull/2283) - adding support for the admin user to connect to a Prometheus metrics endpoint and then make that connection available to all users. Note that non-admins can only see metrics for applications that they have permission to view. - -- Show whether user is an admin on the about page [#2306](https://github.com/cloudfoundry/stratos/pull/2306) - we now indicate on the about page if the current user is an administrator of Stratos. - -- Add Permissions to CF Users tables [#2291](https://github.com/cloudfoundry/stratos/pull/2291) - wired in the user permissions service into the Cloud Foundry user management UI. - -- Wire in actions to app state [#2288](https://github.com/cloudfoundry/stratos/pull/2288) - actions on the application view now use the same rules as in V1 to determine which actions should be shown based on the current application state. - -- Quicker e2e tests for PRs [#2273](https://github.com/cloudfoundry/stratos/pull/2273) - changed the way e2e tests run for PRs. They will now use a quicker local deployment rather than a full deployment in docker. - -- Only show add and deploy buttons when there is at least 1 connected CF [#2285](https://github.com/cloudfoundry/stratos/pull/2285) - we now only show the add and deploy buttons on the application wall when there is a Cloud Foundry available. - -- Fetch cf users when not cf admin [#2282](https://github.com/cloudfoundry/stratos/pull/2282) - ensuring that we use different APIs call when the user is not an admin in order to retrieve the data to display for the user list. - -- Hide service broker card if broker information isn't available [#2287](https://github.com/cloudfoundry/stratos/pull/2287) - we now hide the service broker card if we can not retrieve the broker metadata. - -- Only allow password change if user has password.write scope [#2278](https://github.com/cloudfoundry/stratos/pull/2278) - user is now only presented with the option to change their password if they have permission to do so. - -- Backend logging improvements [#2267](https://github.com/cloudfoundry/stratos/pull/2267) - first round of tidy up to the back-end logging, including not logging an error when verifying the user's seesion when they don't have a valid session. - -- Use local fonts [#2260](https://github.com/cloudfoundry/stratos/pull/2260) - all fonts are now served up by the app itself to allow air-gapped deployment. - -- Endpoint confirmation modals [#2258](https://github.com/cloudfoundry/stratos/pull/2258) - added confirmation modals when disconnecting or un-registering and endpoint. - -- Added theming section to developer guide readme [#2249](https://github.com/cloudfoundry/stratos/pull/2249) - added documentation on how theming is done for Stratos. - -- Update permissions when when entities are updated [#2221](https://github.com/cloudfoundry/stratos/pull/2221) - we now ensure that permissions are updated when endpoints (and other entities) are updated in Stratos. - -## 01 June 2018 - -The team have been working on the following issues and PRs this week: - -- Upgrade to Angular 6 [#2227](https://github.com/cloudfoundry/stratos/pull/2227) - Completed work and testing. Will merge early next week. - -- Edit service instance from Services Wall [#2233](https://github.com/cloudfoundry/stratos/pull/2233) - Added ability to edit an existing service instance. - -- E2E Tests [#1523](https://github.com/cloudfoundry/stratos/issues/1523) - Continuing to extend E2E test suite. - -- Fix compression issue [#2248](https://github.com/cloudfoundry/stratos/pull/2248) - Fixed an issue when Stratos accessed a Cloud Foundry instance with gzip compression enabled. Thanks to everyone for their help with this one. - -- Fix App SSH (Broken when auth and token endpoints are different) [#2250](https://github.com/cloudfoundry/stratos/pull/2250) - Fixed an issue with Application SSH for some CF deplyoments. - -- Fix application issue on reload when served by backend [#2238](https://github.com/cloudfoundry/stratos/pull/2238) - Fixed an issue where refreshing the browser on application pages resulted in a 404 (when deployed via cf push) - - - -## 25 May 2018 - -The team have been working on the following issues and PRs this week: - -- Upgrade to Angular 6 [#2227](https://github.com/cloudfoundry/stratos/pull/2227) - -- Handle async request progress/success/failure in modals [#2223](https://github.com/cloudfoundry/stratos/pull/2223) - Improving busy state and error feedback in modals - e.g. when creating an application, creating a space etc - -- Service Summary tab [#2219](https://github.com/cloudfoundry/stratos/pull/2219) - add a summary tab to the view for a service, to show summary metadata - -- Add support for back-end custom plugins [#2217](https://github.com/cloudfoundry/stratos/pull/2217) - -- Apply user permissions to CF pages (2) [#2212](https://github.com/cloudfoundry/stratos/pull/2212) - Completion of work to wire in user permissions into the Cloud Foundry view - - -## 18 May 2018 - -The team have been working on the following issues and PRs this week: - -- User permissions [#2147](https://github.com/cloudfoundry/stratos/pull/2147) - adding in the framework to control UI elements based on the user's permissions - -- Apply user permissions to CF pages [#2198](https://github.com/cloudfoundry/stratos/pull/2198) - appropriately show the CF actions a user can perform based on their permissions - -- Service instances view [#2074](https://github.com/cloudfoundry/stratos/issues/2074) - adding a view to show service instances - -- Services Wall: Create Services instance [#2163](https://github.com/cloudfoundry/stratos/pull/2163) - adding support for creating service instances from the service marketplace view - -- App Services tab: Allow user to bind a service instance [#2188](https://github.com/cloudfoundry/stratos/pull/2188) - -- E2E Tests and E2E Test setup improvements [#2183](https://github.com/cloudfoundry/stratos/pull/2183) - -- Add support for Angular XSRF protection [#2153](https://github.com/cloudfoundry/stratos/pull/2153) - adding support for the Angular XSRF protection mechanism - -- Remove deprecated API & Add confirmation dialogs when detaching/removing service bindings [#2193](https://github.com/cloudfoundry/stratos/pull/2193) - - - - -## 11 May 2018 - -The work to get V2 to the same level of functionality as V1 is going well and we're nearing completion - the team have been working on the following issues and PRs this week: - -- Add restart app button [#2140](https://github.com/cloudfoundry/stratos/pull/2140) - adding restart action to applications - -- CF Push: Bump up memory further [#2135](https://github.com/cloudfoundry/stratos/pull/2135) - increase memory when pushing to work around the memory-hungry Angular compiler - -- Service instances view [#2074](https://github.com/cloudfoundry/stratos/issues/2074) - adding a view to show service instances - -- User permissions [#2147](https://github.com/cloudfoundry/stratos/pull/2147) - adding in the framework to control UI elements based on the user's permissions - -- Customizations [#2133](https://github.com/cloudfoundry/stratos/pull/2133) - initial support for customizing Stratos (theme etc) - -- E2E Tests [#1523](https://github.com/cloudfoundry/stratos/issues/1523) - putting in place the E2E framework for V2, getting this working in Travis and porting over the V1 Endpoints tests. - -- Delete App should show dependencies and allow optional deletion [#2044](https://github.com/cloudfoundry/stratos/pull/2044) - when deleting an application the user is shown the application dependencies (routes, service instances) and is able to delete these with the application or leave them in place for use by other applications - -- Cloud Foundry: Manage Users [#1541](https://github.com/cloudfoundry/stratos/issues/1541) - re-introducing the equivalent features that V1 has allowing user to manage user roles across Cloud Foundry - -## 4 May 2018 - -The team have been working on the following issues and PRs this week: - -- E2E Tests [#1523](https://github.com/cloudfoundry/stratos/issues/1523) - putting in place the E2E framework for V2, getting this working in Travis and porting over the V1 Endpoints tests. - -- Delete App should show dependencies and allow optional deletion [#2044](https://github.com/cloudfoundry/stratos/pull/2044) - when deleting an application the user is shown the application dependencies (routes, service instances) and is able to delete these with the application or leave them in place for use by other applications - -- Cloud Foundry: Manage Users [#1541](https://github.com/cloudfoundry/stratos/issues/1541) - re-introducing the equivalent features that V1 has allowing user to manage user roles across Cloud Foundry - -- Implement Create Service Instance [#2043](https://github.com/cloudfoundry/stratos/issues/2043) - adding support for creating service instances - -- Service Instance creation: Support space-scoped broker provided plans [#2111](https://github.com/cloudfoundry/stratos/pull/2111) - -- Make Service Instance creation wizard service plan visibility aware [#2109](https://github.com/cloudfoundry/stratos/pull/2109) - -- Return better error information from API passthroughs [#2084](https://github.com/cloudfoundry/stratos/pull/2085) - -## 27 April 2018 - -The team have been working on the following issues this week: - -- GitHub tab/deploy updates [#2067](https://github.com/cloudfoundry/stratos/issues/2067) - When deploying an application from GitHub, we now allow the user to select a commit from their selected branch. When viewing the GitHub tab of an application, the user can see the list of commits and update the application from a different commit on the branch. - -- Deploy App: Add support for an archive file or local folder [#2040](https://github.com/cloudfoundry/stratos/issues/2040) - In addition to Git deployment, users can now browse to a local application archive file or folder and deploy using that. - -- User Profile: Implement edit and password change as per V1 [#2062](https://github.com/cloudfoundry/stratos/issues/2040) - Users can now edit their profile metadata and change their password. - -- Create & List Service Instances - [#2086](https://github.com/cloudfoundry/stratos/pull/2086) - adding the ability to view and create Service Instances. - -- Delete App should show dependencies and allow optional deletion [#2044](https://github.com/cloudfoundry/stratos/pull/2044) - when deleting and application the user is shown the application dependencies(routes, service instances) and is able to delete these with the application or leave them in place for use by other applications - -- Cloud Foundry: Manage Users [#1541](https://github.com/cloudfoundry/stratos/issues/1541) - re-introducing the equivalent features that V1 has allowing user to manage user roles across Cloud Foundry diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 989742ae37..767c50520e 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -7,70 +7,80 @@ module.exports = { organizationName: 'cloudfoundry', projectName: 'stratos', themeConfig: { - disableDarkMode: true, navbar: { title: 'STRATOS', logo: { alt: 'Stratos', src: 'img/logo.png', }, - links: [ - { - to: 'docs/', - activeBasePath: 'docs', - label: 'Docs', - position: 'right', - }, - // {to: 'blog', label: 'Blog', position: 'left'}, - { - href: 'https://github.com/cloudfoundry/stratos', - label: 'GitHub', - position: 'right', - }, - ], + items: [{ + type: 'docsVersionDropdown', + position: 'right', + nextVersionLabel: 'Latest', + }, + { + to: 'docs/', + activeBasePath: 'docs', + label: 'Docs', + position: 'right', + }, { + href: 'https://github.com/cloudfoundry/stratos', + label: 'GitHub', + position: 'right', + }], }, footer: { style: 'dark', - links: [ - { - title: 'Docs', - items: [ - { - label: 'Getting Started', - to: 'docs/', - }, - { - label: 'Deploying Stratos', - to: 'docs/deploy/overview', - }, - ], + links: [{ + title: 'Docs', + items: [{ + label: 'Getting Started', + to: 'docs/', }, { - title: 'Community', - items: [ - { - label: 'Slack', - href: 'https://cloudfoundry.slack.com/?redir=%2Fmessages%2Fstratos', - }, - { - label: 'GitHub', - href: 'https://github.com/cloudfoundry/stratos', - }, - ], + label: 'Deploying Stratos', + to: 'docs/deploy/overview', + }, + ], + }, + { + title: 'Community', + items: [{ + label: 'Slack', + href: 'https://cloudfoundry.slack.com/?redir=%2Fmessages%2Fstratos', }, - { - title: 'More', - items: [ - { - label: 'Presentations and Talks', - to: 'docs/talks', - }, - ], + label: 'GitHub', + href: 'https://github.com/cloudfoundry/stratos', }, + ], + }, + + { + title: 'More', + items: [{ + label: 'Presentations and Talks', + to: 'docs/talks', + },], + }, ], copyright: `Copyright © ${new Date().getFullYear()} Cloud Foundry Foundation`, }, + colorMode: { + defaultMode: 'light', + disableSwitch: false, + respectPrefersColorScheme: true, + switchConfig: { + darkIcon: '🌙', + darkIconStyle: { + marginLeft: '2px', + }, + lightIcon: '☀️', + lightIconStyle: { + marginLeft: '1px', + }, + }, + }, }, presets: [ [ @@ -81,8 +91,7 @@ module.exports = { homePageId: 'introduction', sidebarPath: require.resolve('./sidebars.js'), // Please change this to your repo. - editUrl: - 'https://github.com/cloudfoundry/stratos/edit/master/website/', + editUrl: 'https://github.com/cloudfoundry/stratos/edit/master/website/', }, theme: { customCss: require.resolve('./src/css/custom.css'), diff --git a/website/internal-versions.json b/website/internal-versions.json new file mode 100644 index 0000000000..2635ffaea2 --- /dev/null +++ b/website/internal-versions.json @@ -0,0 +1,4 @@ +[ + "4.0.1:372682177452d8d:true", + "4.0.0:372682177452d8d:true" +] diff --git a/website/package-lock.json b/website/package-lock.json index b25d4a0c8c..58729d2303 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -4,6 +4,121 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@algolia/cache-browser-local-storage": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.4.0.tgz", + "integrity": "sha512-2AiKgN7DpFypkRCRkpqH7waXXyFdcnsPWzmN8sLHrB/FfXqgmsQb3pGft+9YHZIDQ0vAnfgMxSGgMhMGW+0Qnw==", + "requires": { + "@algolia/cache-common": "4.4.0" + } + }, + "@algolia/cache-common": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.4.0.tgz", + "integrity": "sha512-PrIgoMnXaDWUfwOekahro543pgcJfgRu/nd/ZQS5ffem3+Ow725eZY6HDpPaQ1k3cvLii9JH6V2sNJConjqUKA==" + }, + "@algolia/cache-in-memory": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.4.0.tgz", + "integrity": "sha512-9+XlUB0baDU/Dp9URRHPp6Q37YmTO0QmgPWt9+n+wqZrRL0jR3Jezr4jCT7RemqGMxBiR+YpnqaUv0orpb0ptw==", + "requires": { + "@algolia/cache-common": "4.4.0" + } + }, + "@algolia/client-account": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.4.0.tgz", + "integrity": "sha512-Kynu3cMEs0clTLf674rtrCF+FWR/JwlQxKlIWsPzvLBRmNXdvYej9YBcNaOr4OTQFCCZn9JVE8ib91Z7J4IL1Q==", + "requires": { + "@algolia/client-common": "4.4.0", + "@algolia/client-search": "4.4.0", + "@algolia/transporter": "4.4.0" + } + }, + "@algolia/client-analytics": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.4.0.tgz", + "integrity": "sha512-GQyjQimKAc9sZbafxln9Wk7j4pEYiORv28MZkZ+0Bjt7WNXIeO7OgOOECVpQHm9buyV6hCKpNtJcbb5/syRzdQ==", + "requires": { + "@algolia/client-common": "4.4.0", + "@algolia/client-search": "4.4.0", + "@algolia/requester-common": "4.4.0", + "@algolia/transporter": "4.4.0" + } + }, + "@algolia/client-common": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.4.0.tgz", + "integrity": "sha512-a3yr6UhzjWPHDG/8iGp9UvrDOm1aeHVWJIf0Nj/cIvqX5tNCEIo4IMe59ovApkDgLOIpt/cLsyhn9/FiPXRhJA==", + "requires": { + "@algolia/requester-common": "4.4.0", + "@algolia/transporter": "4.4.0" + } + }, + "@algolia/client-recommendation": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@algolia/client-recommendation/-/client-recommendation-4.4.0.tgz", + "integrity": "sha512-sBszbQH46rko6w2fdEG77ma8+fAg0SDkLZGxWhv4trgcnYGUBFl2dcpEPt/6koto9b4XYlf+eh+qi6iGvYqRPg==", + "requires": { + "@algolia/client-common": "4.4.0", + "@algolia/requester-common": "4.4.0", + "@algolia/transporter": "4.4.0" + } + }, + "@algolia/client-search": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.4.0.tgz", + "integrity": "sha512-jqWcxCUyPPHnHreoMb2PnN9iHTP+V/nL62R84XuTRDE3VgTnhm4ZnqyuRdzZQqaz+gNy5znav64TmQ9FN9WW5g==", + "requires": { + "@algolia/client-common": "4.4.0", + "@algolia/requester-common": "4.4.0", + "@algolia/transporter": "4.4.0" + } + }, + "@algolia/logger-common": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.4.0.tgz", + "integrity": "sha512-2vjmSENLaKNuF+ytRDysfWxxgFG95WXCHwHbueThdPMCK3hskkwqJ0Y/pugKfzl+54mZxegb4BYfgcCeuaHVUw==" + }, + "@algolia/logger-console": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.4.0.tgz", + "integrity": "sha512-st/GUWyKvr6YM72OOfF+RmpdVGda3BPXbQ+chpntUq1WyVkyZXGjSmH1IcBVlua27GzxabwOUYON39cF3x10/g==", + "requires": { + "@algolia/logger-common": "4.4.0" + } + }, + "@algolia/requester-browser-xhr": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.4.0.tgz", + "integrity": "sha512-V3a4hXlNch355GnWaT1f5QfXhROpsjT6sd0Znq29gAhwLqfBExhLW6Khdkv5pENC0Qy7ClVhdXFrBL9QCQer1g==", + "requires": { + "@algolia/requester-common": "4.4.0" + } + }, + "@algolia/requester-common": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.4.0.tgz", + "integrity": "sha512-jPinHlFJEFokxQ5b3JWyjQKKn+FMy0hH99PApzOgQAYOSiFRXiPEZp6LeIexDeLLu7Y3eRt/3nHvjPKa6PmRRw==" + }, + "@algolia/requester-node-http": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.4.0.tgz", + "integrity": "sha512-b7HC9C/GHxiV4+0GpCRTtjscvwarPr3dGm4CAhb6AkNjgjRcFUNr1NfsF75w3WVmzmt79/7QZihddztDdVMGjw==", + "requires": { + "@algolia/requester-common": "4.4.0" + } + }, + "@algolia/transporter": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.4.0.tgz", + "integrity": "sha512-Xxzq91DEEeKIzT3DU46n4LEyTGAKZNtSHc2H9wvIY5MYwhZwEribmXXZ6k8W1FvBvzggv3juu0SP+xwGoR7F0w==", + "requires": { + "@algolia/cache-common": "4.4.0", + "@algolia/logger-common": "4.4.0", + "@algolia/requester-common": "4.4.0" + } + }, "@babel/code-frame": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", @@ -13,9 +128,9 @@ } }, "@babel/compat-data": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.10.4.tgz", - "integrity": "sha512-t+rjExOrSVvjQQXNp5zAIYDp00KjdvGl/TpDX5REPr0S9IAIPQMTilcfG6q8c0QFmj9lSTVySV2VTsyggvtNIw==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.11.0.tgz", + "integrity": "sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ==", "requires": { "browserslist": "^4.12.0", "invariant": "^2.2.4", @@ -30,23 +145,23 @@ } }, "@babel/core": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.4.tgz", - "integrity": "sha512-3A0tS0HWpy4XujGc7QtOIHTeNwUgWaZc/WuS5YQrfhU67jnVmsD6OGPc1AKHH0LJHQICGncy3+YUjIhVlfDdcA==", + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.1.tgz", + "integrity": "sha512-XqF7F6FWQdKGGWAzGELL+aCO1p+lRY5Tj5/tbT3St1G8NaH70jhhDIKknIZaDans0OQBG5wRAldROLHSt44BgQ==", "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.10.4", - "@babel/helper-module-transforms": "^7.10.4", + "@babel/generator": "^7.11.0", + "@babel/helper-module-transforms": "^7.11.0", "@babel/helpers": "^7.10.4", - "@babel/parser": "^7.10.4", + "@babel/parser": "^7.11.1", "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4", + "@babel/traverse": "^7.11.0", + "@babel/types": "^7.11.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", "json5": "^2.1.2", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" @@ -60,13 +175,12 @@ } }, "@babel/generator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.4.tgz", - "integrity": "sha512-toLIHUIAgcQygFZRAQcsLQV3CBuX6yOIru1kJk/qqqvcRmZrYe6WavZTSG+bB8MxhnL9YPf+pKQfuiP161q7ng==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.0.tgz", + "integrity": "sha512-fEm3Uzw7Mc9Xi//qU20cBKatTfs2aOtKqmvy/Vm7RkJEGFQ4xc9myCfbXxqK//ZS8MR/ciOHw6meGASJuKmDfQ==", "requires": { - "@babel/types": "^7.10.4", + "@babel/types": "^7.11.0", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" } }, @@ -97,13 +211,13 @@ } }, "@babel/helper-builder-react-jsx-experimental": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.10.4.tgz", - "integrity": "sha512-LyacH/kgQPgLAuaWrvvq1+E7f5bLyT8jXCh7nM67sRsy2cpIGfgWJ+FCnAKQXfY+F0tXUaN6FqLkp4JiCzdK8Q==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.10.5.tgz", + "integrity": "sha512-Buewnx6M4ttG+NLkKyt7baQn7ScC/Td+e99G914fRU8fGIUivDDgVIQeDHFa5e4CRSJQt58WpNHhsAZgtzVhsg==", "requires": { "@babel/helper-annotate-as-pure": "^7.10.4", "@babel/helper-module-imports": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/types": "^7.10.5" } }, "@babel/helper-compilation-targets": { @@ -126,12 +240,12 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.4.tgz", - "integrity": "sha512-9raUiOsXPxzzLjCXeosApJItoMnX3uyT4QdM2UldffuGApNrF8e938MwNpDCK9CPoyxrEoCgT+hObJc3mZa6lQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz", + "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==", "requires": { "@babel/helper-function-name": "^7.10.4", - "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-member-expression-to-functions": "^7.10.5", "@babel/helper-optimise-call-expression": "^7.10.4", "@babel/helper-plugin-utils": "^7.10.4", "@babel/helper-replace-supers": "^7.10.4", @@ -149,13 +263,13 @@ } }, "@babel/helper-define-map": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.4.tgz", - "integrity": "sha512-nIij0oKErfCnLUCWaCaHW0Bmtl2RO9cN7+u2QT8yqTywgALKlyUVOvHDElh+b5DwVC6YB1FOYFOTWcN/+41EDA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", + "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", "requires": { "@babel/helper-function-name": "^7.10.4", - "@babel/types": "^7.10.4", - "lodash": "^4.17.13" + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" } }, "@babel/helper-explode-assignable-expression": { @@ -194,11 +308,11 @@ } }, "@babel/helper-member-expression-to-functions": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.4.tgz", - "integrity": "sha512-m5j85pK/KZhuSdM/8cHUABQTAslV47OjfIB9Cc7P+PvlAoBzdb79BGNfw8RhT5Mq3p+xGd0ZfAKixbrUZx0C7A==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", "requires": { - "@babel/types": "^7.10.4" + "@babel/types": "^7.11.0" } }, "@babel/helper-module-imports": { @@ -210,17 +324,17 @@ } }, "@babel/helper-module-transforms": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.4.tgz", - "integrity": "sha512-Er2FQX0oa3nV7eM1o0tNCTx7izmQtwAQsIiaLRWtavAAEcskb0XJ5OjJbVrYXWOTr8om921Scabn4/tzlx7j1Q==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", "requires": { "@babel/helper-module-imports": "^7.10.4", "@babel/helper-replace-supers": "^7.10.4", "@babel/helper-simple-access": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4", - "lodash": "^4.17.13" + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" } }, "@babel/helper-optimise-call-expression": { @@ -237,11 +351,11 @@ "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" }, "@babel/helper-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.4.tgz", - "integrity": "sha512-inWpnHGgtg5NOF0eyHlC0/74/VkdRITY9dtTpB2PrxKKn+AkVMRiZz/Adrx+Ssg+MLDesi2zohBW6MVq6b4pOQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz", + "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==", "requires": { - "lodash": "^4.17.13" + "lodash": "^4.17.19" } }, "@babel/helper-remap-async-to-generator": { @@ -276,12 +390,20 @@ "@babel/types": "^7.10.4" } }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz", + "integrity": "sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q==", + "requires": { + "@babel/types": "^7.11.0" + } + }, "@babel/helper-split-export-declaration": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", - "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", "requires": { - "@babel/types": "^7.10.4" + "@babel/types": "^7.11.0" } }, "@babel/helper-validator-identifier": { @@ -333,14 +455,14 @@ } }, "@babel/parser": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.4.tgz", - "integrity": "sha512-8jHII4hf+YVDsskTF6WuMB3X4Eh+PsUkC2ljq22so5rHvH+T8BzyL94VOdyFLNR8tBSVXOTbNHOKpR4TfRxVtA==" + "version": "7.11.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.3.tgz", + "integrity": "sha512-REo8xv7+sDxkKvoxEywIdsNFiZLybwdI7hcT5uEPyQrSMB4YQ973BfC9OOrD/81MaIjh6UxdulIQXkjmiH3PcA==" }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.4.tgz", - "integrity": "sha512-MJbxGSmejEFVOANAezdO39SObkURO5o/8b6fSH6D1pi9RZQt+ldppKPXfqgUWpSQ9asM6xaSaSJIaeWMDRP0Zg==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz", + "integrity": "sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==", "requires": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/helper-remap-async-to-generator": "^7.10.4", @@ -365,6 +487,15 @@ "@babel/plugin-syntax-dynamic-import": "^7.8.0" } }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.10.4.tgz", + "integrity": "sha512-aNdf0LY6/3WXkhh0Fdb6Zk9j1NMD8ovj3F6r0+3j837Pn1S1PdNtcwJ5EG9WkVPNHPxyJDaxMaAOVq4eki0qbg==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, "@babel/plugin-proposal-json-strings": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz", @@ -374,6 +505,15 @@ "@babel/plugin-syntax-json-strings": "^7.8.0" } }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.11.0.tgz", + "integrity": "sha512-/f8p4z+Auz0Uaf+i8Ekf1iM7wUNLcViFUGiPxKeXvxTSl63B875YPiVdUDdem7hREcI0E0kSpEhS8tF5RphK7Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, "@babel/plugin-proposal-nullish-coalescing-operator": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz", @@ -393,9 +533,9 @@ } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.4.tgz", - "integrity": "sha512-6vh4SqRuLLarjgeOf4EaROJAHjvu9Gl+/346PbDH9yWbJyfnJ/ah3jmYKYtswEyCoWZiidvVHjHshd4WgjB9BA==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz", + "integrity": "sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA==", "requires": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", @@ -412,11 +552,12 @@ } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.4.tgz", - "integrity": "sha512-ZIhQIEeavTgouyMSdZRap4VPPHqJJ3NEs2cuHs5p0erH+iz6khB0qfgU8g7UuJkG88+fBMy23ZiU+nuHvekJeQ==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz", + "integrity": "sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==", "requires": { "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0", "@babel/plugin-syntax-optional-chaining": "^7.8.0" } }, @@ -462,6 +603,14 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, "@babel/plugin-syntax-json-strings": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", @@ -478,6 +627,14 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, "@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", @@ -561,12 +718,11 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.4.tgz", - "integrity": "sha512-J3b5CluMg3hPUii2onJDRiaVbPtKFPLEaV5dOPY5OeAbDi1iU/UbbFFTgwb7WnanaDy7bjU35kc26W3eM5Qa0A==", + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz", + "integrity": "sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew==", "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "lodash": "^4.17.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-classes": { @@ -660,11 +816,11 @@ } }, "@babel/plugin-transform-modules-amd": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.4.tgz", - "integrity": "sha512-3Fw+H3WLUrTlzi3zMiZWp3AR4xadAEMv6XRCYnd5jAlLM61Rn+CRJaZMaNvIpcJpQ3vs1kyifYvEVPFfoSkKOA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz", + "integrity": "sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==", "requires": { - "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-module-transforms": "^7.10.5", "@babel/helper-plugin-utils": "^7.10.4", "babel-plugin-dynamic-import-node": "^2.3.3" } @@ -681,12 +837,12 @@ } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.4.tgz", - "integrity": "sha512-Tb28LlfxrTiOTGtZFsvkjpyjCl9IoaRI52AEU/VIwOwvDQWtbNJsAqTXzh+5R7i74e/OZHH2c2w2fsOqAfnQYQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz", + "integrity": "sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==", "requires": { "@babel/helper-hoist-variables": "^7.10.4", - "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-module-transforms": "^7.10.5", "@babel/helper-plugin-utils": "^7.10.4", "babel-plugin-dynamic-import-node": "^2.3.3" } @@ -726,9 +882,9 @@ } }, "@babel/plugin-transform-parameters": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.4.tgz", - "integrity": "sha512-RurVtZ/D5nYfEg0iVERXYKEgDFeesHrHfx8RT05Sq57ucj2eOYAP6eu5fynL4Adju4I/mP/I6SO0DqNWAXjfLQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz", + "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==", "requires": { "@babel/helper-get-function-arity": "^7.10.4", "@babel/helper-plugin-utils": "^7.10.4" @@ -789,9 +945,9 @@ } }, "@babel/plugin-transform-react-jsx-source": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.10.4.tgz", - "integrity": "sha512-FTK3eQFrPv2aveerUSazFmGygqIdTtvskG50SnGnbEUnRPcGx2ylBhdFIzoVS1ty44hEgcPoCAyw5r3VDEq+Ug==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.10.5.tgz", + "integrity": "sha512-wTeqHVkN1lfPLubRiZH3o73f4rfon42HpgxUSs86Nc+8QIcm/B9s8NNVXu/gwGcOyd7yDib9ikxoDLxJP0UiDA==", "requires": { "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-jsx": "^7.10.4" @@ -823,9 +979,9 @@ } }, "@babel/plugin-transform-runtime": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.4.tgz", - "integrity": "sha512-8ULlGv8p+Vuxu+kz2Y1dk6MYS2b/Dki+NO6/0ZlfSj5tMalfDL7jI/o/2a+rrWLqSXvnadEqc2WguB4gdQIxZw==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.11.0.tgz", + "integrity": "sha512-LFEsP+t3wkYBlis8w6/kmnd6Kb1dxTd+wGJ8MlxTGzQo//ehtqlVL4S9DNUa53+dtPSQobN2CXx4d81FqC58cw==", "requires": { "@babel/helper-module-imports": "^7.10.4", "@babel/helper-plugin-utils": "^7.10.4", @@ -849,11 +1005,12 @@ } }, "@babel/plugin-transform-spread": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.4.tgz", - "integrity": "sha512-1e/51G/Ni+7uH5gktbWv+eCED9pP8ZpRhZB3jOaI3mmzfvJTWHkuyYTv0Z5PYtyM+Tr2Ccr9kUdQxn60fI5WuQ==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz", + "integrity": "sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw==", "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0" } }, "@babel/plugin-transform-sticky-regex": { @@ -866,9 +1023,9 @@ } }, "@babel/plugin-transform-template-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.4.tgz", - "integrity": "sha512-4NErciJkAYe+xI5cqfS8pV/0ntlY5N5Ske/4ImxAVX7mk9Rxt2bwDTGv1Msc2BRJvWQcmYEC+yoMLdX22aE4VQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz", + "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==", "requires": { "@babel/helper-annotate-as-pure": "^7.10.4", "@babel/helper-plugin-utils": "^7.10.4" @@ -883,11 +1040,11 @@ } }, "@babel/plugin-transform-typescript": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.10.4.tgz", - "integrity": "sha512-3WpXIKDJl/MHoAN0fNkSr7iHdUMHZoppXjf2HJ9/ed5Xht5wNIsXllJXdityKOxeA3Z8heYRb1D3p2H5rfCdPw==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.11.0.tgz", + "integrity": "sha512-edJsNzTtvb3MaXQwj8403B7mZoGu9ElDJQZOKjGUnvilquxBA3IQoEIOvkX/1O8xfAsnHS/oQhe2w/IXrr+w0w==", "requires": { - "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-create-class-features-plugin": "^7.10.5", "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-typescript": "^7.10.4" } @@ -910,29 +1067,33 @@ } }, "@babel/preset-env": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.10.4.tgz", - "integrity": "sha512-tcmuQ6vupfMZPrLrc38d0sF2OjLT3/bZ0dry5HchNCQbrokoQi4reXqclvkkAT5b+gWc23meVWpve5P/7+w/zw==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.0.tgz", + "integrity": "sha512-2u1/k7rG/gTh02dylX2kL3S0IJNF+J6bfDSp4DI2Ma8QN6Y9x9pmAax59fsCk6QUQG0yqH47yJWA+u1I1LccAg==", "requires": { - "@babel/compat-data": "^7.10.4", + "@babel/compat-data": "^7.11.0", "@babel/helper-compilation-targets": "^7.10.4", "@babel/helper-module-imports": "^7.10.4", "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-proposal-async-generator-functions": "^7.10.4", "@babel/plugin-proposal-class-properties": "^7.10.4", "@babel/plugin-proposal-dynamic-import": "^7.10.4", + "@babel/plugin-proposal-export-namespace-from": "^7.10.4", "@babel/plugin-proposal-json-strings": "^7.10.4", + "@babel/plugin-proposal-logical-assignment-operators": "^7.11.0", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", "@babel/plugin-proposal-numeric-separator": "^7.10.4", - "@babel/plugin-proposal-object-rest-spread": "^7.10.4", + "@babel/plugin-proposal-object-rest-spread": "^7.11.0", "@babel/plugin-proposal-optional-catch-binding": "^7.10.4", - "@babel/plugin-proposal-optional-chaining": "^7.10.4", + "@babel/plugin-proposal-optional-chaining": "^7.11.0", "@babel/plugin-proposal-private-methods": "^7.10.4", "@babel/plugin-proposal-unicode-property-regex": "^7.10.4", "@babel/plugin-syntax-async-generators": "^7.8.0", "@babel/plugin-syntax-class-properties": "^7.10.4", "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", @@ -965,14 +1126,14 @@ "@babel/plugin-transform-regenerator": "^7.10.4", "@babel/plugin-transform-reserved-words": "^7.10.4", "@babel/plugin-transform-shorthand-properties": "^7.10.4", - "@babel/plugin-transform-spread": "^7.10.4", + "@babel/plugin-transform-spread": "^7.11.0", "@babel/plugin-transform-sticky-regex": "^7.10.4", "@babel/plugin-transform-template-literals": "^7.10.4", "@babel/plugin-transform-typeof-symbol": "^7.10.4", "@babel/plugin-transform-unicode-escapes": "^7.10.4", "@babel/plugin-transform-unicode-regex": "^7.10.4", "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.10.4", + "@babel/types": "^7.11.0", "browserslist": "^4.12.0", "core-js-compat": "^3.6.2", "invariant": "^2.2.2", @@ -1023,13 +1184,22 @@ } }, "@babel/runtime": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.4.tgz", - "integrity": "sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw==", + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", "requires": { "regenerator-runtime": "^0.13.4" } }, + "@babel/runtime-corejs3": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.11.2.tgz", + "integrity": "sha512-qh5IR+8VgFz83VBa6OkaET6uN/mJOhHONuy3m1sgF0CV6mXdPSEBdA7e1eUbVvyNtANjMbg22JUv71BaDXLY6A==", + "requires": { + "core-js-pure": "^3.0.0", + "regenerator-runtime": "^0.13.4" + } + }, "@babel/template": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", @@ -1041,28 +1211,28 @@ } }, "@babel/traverse": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.4.tgz", - "integrity": "sha512-aSy7p5THgSYm4YyxNGz6jZpXf+Ok40QF3aA2LyIONkDHpAcJzDUqlCKXv6peqYUs2gmic849C/t2HKw2a2K20Q==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.0.tgz", + "integrity": "sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg==", "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.10.4", + "@babel/generator": "^7.11.0", "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.0", + "@babel/types": "^7.11.0", "debug": "^4.1.0", "globals": "^11.1.0", - "lodash": "^4.17.13" + "lodash": "^4.17.19" } }, "@babel/types": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.4.tgz", - "integrity": "sha512-UTCFOxC3FsFHb7lkRMVvgLzaRVamXuAs2Tz4wajva4WxtVY82eZeaUBtC2Zt95FU9TiznuC0Zk35tsim8jeVpg==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz", + "integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==", "requires": { "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } }, @@ -1071,23 +1241,45 @@ "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==" }, + "@docsearch/css": { + "version": "1.0.0-alpha.27", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-1.0.0-alpha.27.tgz", + "integrity": "sha512-Kw6R/gAHMZW2tKZO2a0gd3I8Yf6bJgTk3Dp+L0ZFrvEHEh8v3yQKvoxVify3ML9YVyvCxxAPQQuF9u3JNUwvXw==" + }, + "@docsearch/react": { + "version": "1.0.0-alpha.27", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-1.0.0-alpha.27.tgz", + "integrity": "sha512-jcgUHZsrNNRsaVsplqKhXWheh4VzRTCdhsPuVhJMRvfsFUqXEPo/7kVt5xIybtOj9u+/FVdeSO+APJEE2rakYA==", + "requires": { + "@docsearch/css": "^1.0.0-alpha.27", + "@francoischalifour/autocomplete-core": "^1.0.0-alpha.27", + "@francoischalifour/autocomplete-preset-algolia": "^1.0.0-alpha.27", + "algoliasearch": "^4.0.0" + } + }, "@docusaurus/core": { - "version": "2.0.0-alpha.58", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-2.0.0-alpha.58.tgz", - "integrity": "sha512-7VW7IQKTxTIeCXxzraLdTqRtYSmEG2ZJDt07z26NjK+17XyNQc0IoJs7M3xhjzaeP0s/CpYe5Yl+pgp6TIXqoA==", + "version": "2.0.0-alpha.61", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-2.0.0-alpha.61.tgz", + "integrity": "sha512-Ev0v5J7L/Pm3VJMdhhyR8I9tUQo8MhVRUUT+Bf0W3TMYG6jp2cIXE88yCfxOsTDducS7EMrdtUXfvePGH9CE/A==", "requires": { "@babel/core": "^7.9.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.1", + "@babel/plugin-proposal-optional-chaining": "^7.10.3", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-transform-runtime": "^7.9.0", "@babel/preset-env": "^7.9.0", "@babel/preset-react": "^7.9.4", "@babel/preset-typescript": "^7.9.0", "@babel/runtime": "^7.9.2", - "@docusaurus/utils": "^2.0.0-alpha.58", + "@babel/runtime-corejs3": "^7.10.4", + "@docusaurus/types": "^2.0.0-alpha.61", + "@docusaurus/utils": "^2.0.0-alpha.61", "@endiliey/static-site-generator-webpack-plugin": "^4.0.0", + "@hapi/joi": "^17.1.1", "@svgr/webpack": "^5.4.0", "babel-loader": "^8.1.0", "babel-plugin-dynamic-import-node": "^2.3.0", + "boxen": "^4.2.0", "cache-loader": "^4.1.0", "chalk": "^3.0.0", "chokidar": "^3.3.0", @@ -1096,14 +1288,19 @@ "core-js": "^2.6.5", "css-loader": "^3.4.2", "del": "^5.1.0", + "detect-port": "^1.3.0", "eta": "^1.1.1", "express": "^4.17.1", + "file-loader": "^6.0.0", "fs-extra": "^8.1.0", "globby": "^10.0.1", "html-minifier-terser": "^5.0.5", "html-tags": "^3.1.0", "html-webpack-plugin": "^4.0.4", "import-fresh": "^3.2.1", + "inquirer": "^7.2.0", + "is-root": "^2.1.0", + "lodash": "^4.5.2", "lodash.has": "^4.5.2", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", @@ -1112,7 +1309,6 @@ "null-loader": "^3.0.0", "optimize-css-assets-webpack-plugin": "^5.0.3", "pnp-webpack-plugin": "^1.6.4", - "portfinder": "^1.0.25", "postcss-loader": "^3.0.0", "postcss-preset-env": "^6.7.0", "react-dev-utils": "^10.2.1", @@ -1122,10 +1318,14 @@ "react-router": "^5.1.2", "react-router-config": "^5.1.1", "react-router-dom": "^5.1.2", + "resolve-pathname": "^3.0.0", "semver": "^6.3.0", + "serve-handler": "^6.1.3", "shelljs": "^0.8.4", "std-env": "^2.2.1", "terser-webpack-plugin": "^2.3.5", + "update-notifier": "^4.1.0", + "url-loader": "^4.1.0", "wait-file": "^1.0.5", "webpack": "^4.41.2", "webpack-bundle-analyzer": "^3.6.1", @@ -1135,15 +1335,17 @@ } }, "@docusaurus/mdx-loader": { - "version": "2.0.0-alpha.58", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-alpha.58.tgz", - "integrity": "sha512-g/uqzaoaRkwuPmjOB7/p58vNFLev9yE3FdkVfD19mSHyaMaKAzWGUiJxkaxPFtxCs4mwFWk7tJKSLY+B8MjS2Q==", + "version": "2.0.0-alpha.61", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-alpha.61.tgz", + "integrity": "sha512-n7VMfyshgMjoVI2YdQFlPVcMTSR+XOl2UbOTgJXDmD4yCeLOSaj63g8fwVoCy+NRkPgjpWGTGCeLNs63dk9jYg==", "requires": { "@babel/parser": "^7.9.4", "@babel/traverse": "^7.9.0", + "@docusaurus/core": "^2.0.0-alpha.61", "@mdx-js/mdx": "^1.5.8", "@mdx-js/react": "^1.5.8", "escape-html": "^1.0.3", + "file-loader": "^6.0.0", "fs-extra": "^8.1.0", "github-slugger": "^1.3.0", "gray-matter": "^4.0.2", @@ -1151,7 +1353,8 @@ "mdast-util-to-string": "^1.1.0", "remark-emoji": "^2.1.0", "stringify-object": "^3.3.0", - "unist-util-visit": "^2.0.2" + "unist-util-visit": "^2.0.2", + "url-loader": "^4.1.0" }, "dependencies": { "json5": { @@ -1175,12 +1378,16 @@ } }, "@docusaurus/plugin-content-blog": { - "version": "2.0.0-alpha.58", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-alpha.58.tgz", - "integrity": "sha512-2Pn15pwPdnmKLAvNd4bk2XclnGtGYCpfAECypm9tBql6W6MShy0qOHH3JGLY8dQIMySsCkuC3jCl26EwXNgARw==", - "requires": { - "@docusaurus/mdx-loader": "^2.0.0-alpha.58", - "@docusaurus/utils": "^2.0.0-alpha.58", + "version": "2.0.0-alpha.61", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-alpha.61.tgz", + "integrity": "sha512-C2U5NTKYeDm7AViMt4fqmkLuk2kwxvvkzAK84EvEA3tVy3Q58qTfRqbDyFJGN63OL1Os+1HwQvGjPjCUWVbJ3Q==", + "requires": { + "@docusaurus/core": "^2.0.0-alpha.61", + "@docusaurus/mdx-loader": "^2.0.0-alpha.61", + "@docusaurus/types": "^2.0.0-alpha.61", + "@docusaurus/utils": "^2.0.0-alpha.61", + "@docusaurus/utils-validation": "^2.0.0-alpha.61", + "@hapi/joi": "^17.1.1", "feed": "^4.1.0", "fs-extra": "^8.1.0", "globby": "^10.0.1", @@ -1211,12 +1418,16 @@ } }, "@docusaurus/plugin-content-docs": { - "version": "2.0.0-alpha.58", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-alpha.58.tgz", - "integrity": "sha512-+PHqften1daJXBxUZTN5pMIRP7NwGU7reO4QX0iy7Qp1Wqs63KPY/nSeM7dtxKCYtU+yWkLogvFlicARVqRU9A==", - "requires": { - "@docusaurus/mdx-loader": "^2.0.0-alpha.58", - "@docusaurus/utils": "^2.0.0-alpha.58", + "version": "2.0.0-alpha.61", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-alpha.61.tgz", + "integrity": "sha512-1WojgF+0ZQoARVF3I++2ghzG0sY4panxNiWv8Mzo2MdqECj3lgmR8jaVUSXj4bcTzX7uAEVS9MqKYIf3DBpgYg==", + "requires": { + "@docusaurus/core": "^2.0.0-alpha.61", + "@docusaurus/mdx-loader": "^2.0.0-alpha.61", + "@docusaurus/types": "^2.0.0-alpha.61", + "@docusaurus/utils": "^2.0.0-alpha.61", + "@docusaurus/utils-validation": "^2.0.0-alpha.61", + "@hapi/joi": "17.1.1", "execa": "^3.4.0", "fs-extra": "^8.1.0", "globby": "^10.0.1", @@ -1226,6 +1437,7 @@ "lodash.groupby": "^4.6.0", "lodash.pick": "^4.4.0", "lodash.pickby": "^4.6.0", + "lodash.sortby": "^4.6.0", "remark-admonitions": "^1.2.1", "shelljs": "^0.8.4" }, @@ -1248,9 +1460,9 @@ } }, "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "requires": { "pump": "^3.0.0" } @@ -1294,69 +1506,98 @@ } }, "@docusaurus/plugin-content-pages": { - "version": "2.0.0-alpha.58", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-alpha.58.tgz", - "integrity": "sha512-4LJn2CB83ZCkM4+0qNQWdm4gA2Og3QzmUfSqYOifTmsVsHuLSAqa5zRkkSSsZ244r+ya4e7nmS7nMd7kfK+v4w==", - "requires": { - "@docusaurus/types": "^2.0.0-alpha.58", - "@docusaurus/utils": "^2.0.0-alpha.58", - "globby": "^10.0.1" + "version": "2.0.0-alpha.61", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-alpha.61.tgz", + "integrity": "sha512-UQmXnGQCQoMltnL0Zvf0Dhqis+tKPwAdtcoBcQN4dvaDp4iEsS8eJjG9QZqvPzqJv+giVyuCT/KeZj/pxCitNw==", + "requires": { + "@docusaurus/mdx-loader": "^2.0.0-alpha.61", + "@docusaurus/types": "^2.0.0-alpha.61", + "@docusaurus/utils": "^2.0.0-alpha.61", + "@docusaurus/utils-validation": "^2.0.0-alpha.61", + "@hapi/joi": "17.1.1", + "globby": "^10.0.1", + "loader-utils": "^1.2.3", + "remark-admonitions": "^1.2.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + } } }, "@docusaurus/plugin-debug": { - "version": "2.0.0-alpha.58", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-alpha.58.tgz", - "integrity": "sha512-w7sEUzuavp5N4TxgJus1TLFwRkypvg9+mRYZN+mgV05WF3ft+aDTToWeYNZq/8FoC1IEwkezOpPpaWtG4UWM7Q==", + "version": "2.0.0-alpha.61", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-alpha.61.tgz", + "integrity": "sha512-v0qbGwT/yd5Dy/dcwn5fBCdlFE60IOOhllBDuKUsjJwKCvFDKHZ6jtxrZY+ujIlDfj/Tkc4ban0w46JGWnMj+w==", "requires": { - "@docusaurus/types": "^2.0.0-alpha.58", - "@docusaurus/utils": "^2.0.0-alpha.58" + "@docusaurus/types": "^2.0.0-alpha.61", + "@docusaurus/utils": "^2.0.0-alpha.61" } }, "@docusaurus/plugin-google-analytics": { - "version": "2.0.0-alpha.58", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-alpha.58.tgz", - "integrity": "sha512-R4I8j+XJkOl7fz8+lRnQKr8YP4zrG6dWBNZ66JquUwL6jmaavq1VRVavm7/s5Wr/vYLcpEWjynHViwDdVLegoQ==" + "version": "2.0.0-alpha.61", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-alpha.61.tgz", + "integrity": "sha512-f+KmaoM6eTledmgyijMNREvekZVLJ3nll6aUNDXPod9+MF673Hs4RGDyveMAjLiq03+VCgtXAniTSYsFIHcuAQ==" }, "@docusaurus/plugin-google-gtag": { - "version": "2.0.0-alpha.58", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-alpha.58.tgz", - "integrity": "sha512-t+B6K2/EvRofygDK5edeQAg2l069aU7H3sViG/3USpJSaY9bWNWRKjZk6BEOypC+mCW6g5HQgewQZ/bTkV+aDA==" + "version": "2.0.0-alpha.61", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-alpha.61.tgz", + "integrity": "sha512-9l/CUNtBIZqTKY7vD0dOOTrLRpbViXSQPsIOlfYDilS2AQmpsoJVQf6CcCts+GaxWMu0pTw3zeCNnFtJfDV5pA==" }, "@docusaurus/plugin-sitemap": { - "version": "2.0.0-alpha.58", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-alpha.58.tgz", - "integrity": "sha512-OS3XZG1S/USyZxSZ4e2pputW3sOl/hxvK+EAs51eOi/fYyVgO4BmFpcsoP17a5d1Cnxf8gumOkS6bLRDaG8KyQ==", + "version": "2.0.0-alpha.61", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-alpha.61.tgz", + "integrity": "sha512-7nXJl/zsnr8Hlzxn3bm9NhpwP4sRFGXWwSCWCC4FMrIw9ihXWTtMGe9hDuJx4DqC8xufyQMw26VGauH7XAWdMg==", "requires": { - "@docusaurus/types": "^2.0.0-alpha.58", + "@docusaurus/types": "^2.0.0-alpha.61", + "@hapi/joi": "17.1.1", + "fs-extra": "^8.1.0", "sitemap": "^3.2.2" } }, "@docusaurus/preset-classic": { - "version": "2.0.0-alpha.58", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-2.0.0-alpha.58.tgz", - "integrity": "sha512-XGXC5NcAkRUKpm4aho6moThPtLarGSouWW1xfYMQlT4dY6RG/FFt8n5viMXzGwOfA/H9B/s0sZpqHDw8U4FUCg==", + "version": "2.0.0-alpha.61", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-2.0.0-alpha.61.tgz", + "integrity": "sha512-/3HL468XiSZ1T4mdvqnfV6O80Qv4BxAseJGnmkBwS0u6+Q1VgkNxRxVk4B45OWPaurK/Dl+sCn4sAAUWUGRsZg==", "requires": { - "@docusaurus/plugin-content-blog": "^2.0.0-alpha.58", - "@docusaurus/plugin-content-docs": "^2.0.0-alpha.58", - "@docusaurus/plugin-content-pages": "^2.0.0-alpha.58", - "@docusaurus/plugin-debug": "^2.0.0-alpha.58", - "@docusaurus/plugin-google-analytics": "^2.0.0-alpha.58", - "@docusaurus/plugin-google-gtag": "^2.0.0-alpha.58", - "@docusaurus/plugin-sitemap": "^2.0.0-alpha.58", - "@docusaurus/theme-classic": "^2.0.0-alpha.58", - "@docusaurus/theme-search-algolia": "^2.0.0-alpha.58" + "@docusaurus/plugin-content-blog": "^2.0.0-alpha.61", + "@docusaurus/plugin-content-docs": "^2.0.0-alpha.61", + "@docusaurus/plugin-content-pages": "^2.0.0-alpha.61", + "@docusaurus/plugin-debug": "^2.0.0-alpha.61", + "@docusaurus/plugin-google-analytics": "^2.0.0-alpha.61", + "@docusaurus/plugin-google-gtag": "^2.0.0-alpha.61", + "@docusaurus/plugin-sitemap": "^2.0.0-alpha.61", + "@docusaurus/theme-classic": "^2.0.0-alpha.61", + "@docusaurus/theme-search-algolia": "^2.0.0-alpha.61" } }, "@docusaurus/theme-classic": { - "version": "2.0.0-alpha.58", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.0.0-alpha.58.tgz", - "integrity": "sha512-GvpzpsL3jTxm/3sPna7wOJaAauCha+uLZ33x/XOMKrfN02E2BLkaRphRyCliw1vvLXB3rJPlHyvBdKCTOVXaNw==", + "version": "2.0.0-alpha.61", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.0.0-alpha.61.tgz", + "integrity": "sha512-LPJwDi8iPzBe36+U65h4w5N5rXSuXuxPXWzBe/eF0/miR7VVCKydGSSubQLSMAXV0QWspGJIRSPnwuNH3DjJZg==", "requires": { + "@hapi/joi": "^17.1.1", "@mdx-js/mdx": "^1.5.8", "@mdx-js/react": "^1.5.8", "clsx": "^1.1.1", "copy-text-to-clipboard": "^2.2.0", "infima": "0.2.0-alpha.12", + "lodash": "^4.17.19", "parse-numeric-range": "^0.0.2", "prism-react-renderer": "^1.1.0", "prismjs": "^1.20.0", @@ -1366,37 +1607,40 @@ } }, "@docusaurus/theme-search-algolia": { - "version": "2.0.0-alpha.58", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-alpha.58.tgz", - "integrity": "sha512-Iug5mET733Yx46E04BfJjz4+AvxzalRo8G4ZmOjePTMiKQpE1ee39Ypbwj77c8XxEadOcZC4mJtfxB3L1RqlBA==", + "version": "2.0.0-alpha.61", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-alpha.61.tgz", + "integrity": "sha512-B47SmuBdF2kguBo3Wkx8L/jhYgHXxgxEpcf9JCLPGzK0YiRJk111z43h6PLSwirEpxb4OE+sFqr5oPvnsgnwRw==", "requires": { - "algoliasearch": "^3.24.5", + "@docsearch/react": "^1.0.0-alpha.25", + "@hapi/joi": "^17.1.1", + "algoliasearch": "^4.0.0", "algoliasearch-helper": "^3.1.1", "clsx": "^1.1.1", - "docsearch.js": "^2.6.3", "eta": "^1.1.1" } }, "@docusaurus/types": { - "version": "2.0.0-alpha.58", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-2.0.0-alpha.58.tgz", - "integrity": "sha512-OSkxoLwWJMhEHa4s6SXVCjfGwYm21yF6ZggoUo6kz+qqslTgF/JcPCVF9Y1Hf6bJJxUisi+ZHrHKEC6k4pphPA==", + "version": "2.0.0-alpha.61", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-2.0.0-alpha.61.tgz", + "integrity": "sha512-x1fBiL/KNfREvA6B40CCTABjK9KP+kj/H/7mHfiwdtOYvVt9GJSgnjThkVD62lpVFbOhQ5C0togZsSzKlw6H/w==", "requires": { "@types/webpack": "^4.41.0", "commander": "^4.0.1", - "querystring": "0.2.0" + "querystring": "0.2.0", + "webpack-merge": "^4.2.2" } }, "@docusaurus/utils": { - "version": "2.0.0-alpha.58", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.0.0-alpha.58.tgz", - "integrity": "sha512-hBhdQCyVT15mH7RE5yuDBuhwbUnM4beKq2JLvRuZS4FoNu7T2S4OGusUAtTnNZEruoUv2QTkt+GrsRDKYi2fCA==", + "version": "2.0.0-alpha.61", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.0.0-alpha.61.tgz", + "integrity": "sha512-MHvR3Rq8Kk9W6skBR3x7mLsDaNrnp6Mmobyc0ZVql+eiLrjiN7SPunvrVJDE90bQ50HZFLLoAkfgfrvbX5mecg==", "requires": { "escape-string-regexp": "^2.0.0", "fs-extra": "^8.1.0", "gray-matter": "^4.0.2", "lodash.camelcase": "^4.3.0", - "lodash.kebabcase": "^4.1.1" + "lodash.kebabcase": "^4.1.1", + "resolve-pathname": "^3.0.0" }, "dependencies": { "escape-string-regexp": { @@ -1406,6 +1650,14 @@ } } }, + "@docusaurus/utils-validation": { + "version": "2.0.0-alpha.61", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.0.0-alpha.61.tgz", + "integrity": "sha512-3QrJqZoR5eBz2XG0ijuTIp5AEOe1OHtuv7nkKArOCzFmjuBJLhUTRcECf0K+lcmdJ25zrRAWAYNgTvpVpBjaNg==", + "requires": { + "@hapi/joi": "17.1.1" + } + }, "@endiliey/static-site-generator-webpack-plugin": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@endiliey/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.0.tgz", @@ -1418,128 +1670,129 @@ "webpack-sources": "^1.4.3" } }, + "@francoischalifour/autocomplete-core": { + "version": "1.0.0-alpha.27", + "resolved": "https://registry.npmjs.org/@francoischalifour/autocomplete-core/-/autocomplete-core-1.0.0-alpha.27.tgz", + "integrity": "sha512-kpKbtrjMt9l1HIFFmmH0u88633/1oBD+mEjKg1EIRJ1zQCeOBxlQvIXZ3X6GEoud79QjLVoc8HD4HN1OMRt+OA==" + }, + "@francoischalifour/autocomplete-preset-algolia": { + "version": "1.0.0-alpha.27", + "resolved": "https://registry.npmjs.org/@francoischalifour/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.0.0-alpha.27.tgz", + "integrity": "sha512-Mp4lhlLd8vuLOCXtuw8UTUaJXGRrXYL7AN/ZmhaMwqyL9e9XSqLlcv82EWP0NAMcoz/I1E1C709h4jnbnN4llw==" + }, "@hapi/address": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", - "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.1.0.tgz", + "integrity": "sha512-SkszZf13HVgGmChdHo/PxchnSaCJ6cetVqLzyciudzZRT0jcOouIF/Q93mgjw8cce+D+4F4C1Z/WrfFN+O3VHQ==", + "requires": { + "@hapi/hoek": "^9.0.0" + } }, "@hapi/bourne": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-1.3.2.tgz", "integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==" }, + "@hapi/formula": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", + "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==" + }, "@hapi/hoek": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", - "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==" + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.4.tgz", + "integrity": "sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==" }, "@hapi/joi": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.1.1.tgz", - "integrity": "sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ==", + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.1.tgz", + "integrity": "sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg==", "requires": { - "@hapi/address": "2.x.x", - "@hapi/bourne": "1.x.x", - "@hapi/hoek": "8.x.x", - "@hapi/topo": "3.x.x" + "@hapi/address": "^4.0.1", + "@hapi/formula": "^2.0.0", + "@hapi/hoek": "^9.0.0", + "@hapi/pinpoint": "^2.0.0", + "@hapi/topo": "^5.0.0" } }, + "@hapi/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==" + }, "@hapi/topo": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", - "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", + "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", "requires": { - "@hapi/hoek": "^8.3.0" + "@hapi/hoek": "^9.0.0" } }, "@mdx-js/mdx": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.6.6.tgz", - "integrity": "sha512-Q1j/RtjNbRZRC/ciaOqQLplsJ9lb0jJhDSvkusmzCsCX+NZH7YTUvccWf7l6zKW1CAiofJfqZdZtXkeJUDZiMw==", + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.6.16.tgz", + "integrity": "sha512-jnYyJ0aCafCIehn3GjYcibIapaLBgs3YkoenNQBPcPFyyuUty7B3B07OE+pMllhJ6YkWeP/R5Ax19x0nqTzgJw==", "requires": { - "@babel/core": "7.9.6", - "@babel/plugin-syntax-jsx": "7.8.3", + "@babel/core": "7.10.5", + "@babel/plugin-syntax-jsx": "7.10.4", "@babel/plugin-syntax-object-rest-spread": "7.8.3", - "@mdx-js/util": "^1.6.6", - "babel-plugin-apply-mdx-type-prop": "^1.6.6", - "babel-plugin-extract-import-names": "^1.6.6", + "@mdx-js/util": "1.6.16", + "babel-plugin-apply-mdx-type-prop": "1.6.16", + "babel-plugin-extract-import-names": "1.6.16", "camelcase-css": "2.0.1", "detab": "2.0.3", - "hast-util-raw": "5.0.2", + "hast-util-raw": "6.0.0", "lodash.uniq": "4.5.0", "mdast-util-to-hast": "9.1.0", "remark-footnotes": "1.0.0", - "remark-mdx": "^1.6.6", - "remark-parse": "8.0.2", + "remark-mdx": "1.6.16", + "remark-parse": "8.0.3", "remark-squeeze-paragraphs": "4.0.0", "style-to-object": "0.3.0", - "unified": "9.0.0", + "unified": "9.1.0", "unist-builder": "2.0.3", - "unist-util-visit": "2.0.2" + "unist-util-visit": "2.0.3" }, "dependencies": { "@babel/core": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.6.tgz", - "integrity": "sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg==", - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.6", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helpers": "^7.9.6", - "@babel/parser": "^7.9.6", - "@babel/template": "^7.8.6", - "@babel/traverse": "^7.9.6", - "@babel/types": "^7.9.6", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.5.tgz", + "integrity": "sha512-O34LQooYVDXPl7QWCdW9p4NR+QlzOr7xShPPJz8GsuCU3/8ua/wqTr7gmnxXv+WBESiGU/G5s16i6tUvHkNb+w==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helpers": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.5", + "@babel/types": "^7.10.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", "json5": "^2.1.2", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" } }, - "@babel/plugin-syntax-jsx": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz", - "integrity": "sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "unist-util-is": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz", - "integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==" - }, - "unist-util-visit": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.2.tgz", - "integrity": "sha512-HoHNhGnKj6y+Sq+7ASo2zpVdfdRifhTgX2KTU3B/sO/TTlZchp7E3S4vjRzDJ7L60KmrCPsQkVK3lEF3cz36XQ==", - "requires": { - "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" - } } } }, "@mdx-js/react": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.6.6.tgz", - "integrity": "sha512-zOOdNreHUNSFQ0dg3wYYg9sOGg2csf7Sk8JGBigeBq+4Xk4LO0QdycGAmgKNfeme+SyBV5LBIPjt1NNsScyWEQ==" + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.6.16.tgz", + "integrity": "sha512-+FhuSVOPo7+4fZaRwWuCSRUcZkJOkZu0rfAbBKvoCg1LWb1Td8Vzi0DTLORdSvgWNbU6+EL40HIgwTOs00x2Jw==" }, "@mdx-js/util": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/@mdx-js/util/-/util-1.6.6.tgz", - "integrity": "sha512-PKTHVgMHnK5p+kcMWWNnZuoR7O19VmHiOujmVcyN50hya7qIdDb5vvsYC+dwLxApEXiABhLozq0dlIwFeS3yjg==" + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/@mdx-js/util/-/util-1.6.16.tgz", + "integrity": "sha512-SFtLGIGZummuyMDPRL5KdmpgI8U19Ble28UjEWihPjGxF1Lgj8aDjLWY8KiaUy9eqb9CKiVCqEIrK9jbnANfkw==" }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", @@ -1573,6 +1826,11 @@ "fastq": "^1.6.0" } }, + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" + }, "@svgr/babel-plugin-add-jsx-attribute": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", @@ -1682,6 +1940,14 @@ "loader-utils": "^2.0.0" } }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "requires": { + "defer-to-connect": "^1.0.1" + } + }, "@types/anymatch": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", @@ -1701,6 +1967,14 @@ "@types/node": "*" } }, + "@types/hast": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.1.tgz", + "integrity": "sha512-viwwrB+6xGzw+G1eWpF9geV3fnsDgXqHG+cqgiHrvQfDUW5hzhCyV7Sy3UJxhfRFBsgky2SSW33qi/YrIkjX5Q==", + "requires": { + "@types/unist": "*" + } + }, "@types/html-minifier-terser": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz", @@ -1725,15 +1999,20 @@ "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" }, "@types/node": { - "version": "14.0.22", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.22.tgz", - "integrity": "sha512-emeGcJvdiZ4Z3ohbmw93E/64jRzUHAItSHt8nF7M4TGgQTiWqFVGB8KNpLGFmUHmHLvjvBgFwVlqNcq+VuGv9g==" + "version": "14.0.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz", + "integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g==" }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, + "@types/parse5": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.3.tgz", + "integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==" + }, "@types/q": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", @@ -1790,9 +2069,9 @@ } }, "@types/webpack-sources": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-1.4.0.tgz", - "integrity": "sha512-c88dKrpSle9BtTqR6ifdaxu1Lvjsl3C5OsfvuUbUwdXymshv1TkufUAXBajCCUM/f/TmnkZC/Esb03MinzSiXQ==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-1.4.2.tgz", + "integrity": "sha512-77T++JyKow4BQB/m9O96n9d/UUHWLQHlcqXb9Vsf4F1+wKNrrlWNFPDLKNT92RJnCSL6CieTc+NDXtCVZswdTw==", "requires": { "@types/node": "*", "@types/source-list-map": "*", @@ -1973,11 +2252,6 @@ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -2002,11 +2276,6 @@ "resolved": "https://registry.npmjs.org/address/-/address-1.1.2.tgz", "integrity": "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==" }, - "agentkeepalive": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-2.2.0.tgz", - "integrity": "sha1-xdG9SxKQCPEWPyNvhuX66iAm4u8=" - }, "aggregate-error": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", @@ -2033,66 +2302,35 @@ "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==" }, "ajv-keywords": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.1.tgz", - "integrity": "sha512-KWcq3xN8fDjSB+IMoh2VaXVhRI0BBGxoYp3rx7Pkb6z0cFjYR9Q9l4yZqqals0/zsioCmocC5H6UvsGD4MoIBA==" + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" }, "algoliasearch": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-3.35.1.tgz", - "integrity": "sha512-K4yKVhaHkXfJ/xcUnil04xiSrB8B8yHZoFEhWNpXg23eiCnqvTZw1tn/SqvdsANlYHLJlKl0qi3I/Q2Sqo7LwQ==", - "requires": { - "agentkeepalive": "^2.2.0", - "debug": "^2.6.9", - "envify": "^4.0.0", - "es6-promise": "^4.1.0", - "events": "^1.1.0", - "foreach": "^2.0.5", - "global": "^4.3.2", - "inherits": "^2.0.1", - "isarray": "^2.0.1", - "load-script": "^1.0.0", - "object-keys": "^1.0.11", - "querystring-es3": "^0.2.1", - "reduce": "^1.0.1", - "semver": "^5.1.0", - "tunnel-agent": "^0.6.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" - }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.4.0.tgz", + "integrity": "sha512-Ag3wxe/nSodNl/1KbHibtkh7TNLptKE300/wnGVtszRjXivaWD6333nUpCumrYObHym/fHMHyLcmQYezXbAIWQ==", + "requires": { + "@algolia/cache-browser-local-storage": "4.4.0", + "@algolia/cache-common": "4.4.0", + "@algolia/cache-in-memory": "4.4.0", + "@algolia/client-account": "4.4.0", + "@algolia/client-analytics": "4.4.0", + "@algolia/client-common": "4.4.0", + "@algolia/client-recommendation": "4.4.0", + "@algolia/client-search": "4.4.0", + "@algolia/logger-common": "4.4.0", + "@algolia/logger-console": "4.4.0", + "@algolia/requester-browser-xhr": "4.4.0", + "@algolia/requester-common": "4.4.0", + "@algolia/requester-node-http": "4.4.0", + "@algolia/transporter": "4.4.0" } }, "algoliasearch-helper": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.1.2.tgz", - "integrity": "sha512-HfCVvmKH6+5OU9/SaHLdhvr39DBObA02z62RsfPhFDftzgQM6pJB2JoPyGpIteHW4RAYh8bPLiB8l4hajuy6fA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.2.2.tgz", + "integrity": "sha512-/3XvE33R+gQKaiPdy3nmHYqhF8hqIu8xnlOicVxb1fD6uMFmxW8rGLzzrRfsPfxgAfm+c1NslLb3TzQVIB8aVA==", "requires": { "events": "^1.1.1" }, @@ -2109,6 +2347,26 @@ "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=" }, + "ansi-align": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", + "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", + "requires": { + "string-width": "^3.0.0" + }, + "dependencies": { + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + } + } + }, "ansi-colors": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", @@ -2120,6 +2378,13 @@ "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", "requires": { "type-fest": "^0.11.0" + }, + "dependencies": { + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" + } } }, "ansi-html": { @@ -2128,9 +2393,9 @@ "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=" }, "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" }, "ansi-styles": { "version": "3.2.1", @@ -2205,22 +2470,15 @@ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, "asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", "requires": { "bn.js": "^4.0.0", "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" }, "dependencies": { "bn.js": { @@ -2254,11 +2512,6 @@ } } }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -2282,48 +2535,25 @@ "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, - "autocomplete.js": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/autocomplete.js/-/autocomplete.js-0.36.0.tgz", - "integrity": "sha512-jEwUXnVMeCHHutUt10i/8ZiRaCb0Wo+ZyKxeGsYwBDtw6EJHqEeDrq4UwZRD8YBSvp3g6klP678il2eeiVXN2Q==", - "requires": { - "immediate": "^3.2.3" - } - }, "autoprefixer": { - "version": "9.8.5", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.5.tgz", - "integrity": "sha512-C2p5KkumJlsTHoNv9w31NrBRgXhf6eCMteJuHZi2xhkgC+5Vm40MEtCKPhc0qdgAOhox0YPy1SQHTAky05UoKg==", + "version": "9.8.6", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", + "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", "requires": { "browserslist": "^4.12.0", - "caniuse-lite": "^1.0.30001097", - "colorette": "^1.2.0", + "caniuse-lite": "^1.0.30001109", + "colorette": "^1.2.1", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", "postcss": "^7.0.32", "postcss-value-parser": "^4.1.0" } }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", - "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==" - }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -2334,6 +2564,11 @@ "js-tokens": "^3.0.2" }, "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", @@ -2356,6 +2591,14 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -2396,19 +2639,12 @@ } }, "babel-plugin-apply-mdx-type-prop": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.6.6.tgz", - "integrity": "sha512-rUzVvkQa8/9M63OZT6qQQ1bS8P0ozhXp9e5uJ3RwRJF5Me7s4nZK5SYhyNHYc0BkAflWnCOGMP3oPQUfuyB8tg==", + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.6.16.tgz", + "integrity": "sha512-hjUd24Yhnr5NKtHpC2mcRBGjC6RUKGzSzjN9g5SdjT4WpL/JDlpmjyBf7vWsJJSXFvMIbzRyxF4lT9ukwOnj/w==", "requires": { - "@babel/helper-plugin-utils": "7.8.3", - "@mdx-js/util": "^1.6.6" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", - "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" - } + "@babel/helper-plugin-utils": "7.10.4", + "@mdx-js/util": "1.6.16" } }, "babel-plugin-dynamic-import-node": { @@ -2420,18 +2656,11 @@ } }, "babel-plugin-extract-import-names": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.6.6.tgz", - "integrity": "sha512-UtMuiQJnhVPAGE2+pDe7Nc9NVEmDdqGTN74BtRALgH+7oag88RpxFLOSiA+u5mFkFg741wW9Ut5KiyJpksEj/g==", + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.6.16.tgz", + "integrity": "sha512-Da6Ra0sbA/1Iavli8LdMbTjyrsOPaxMm4lrKl8VJN4sJI5F64qy2EpLj3+5INLvNPfW4ddwpStbfP3Rf3jIgcw==", "requires": { - "@babel/helper-plugin-utils": "7.8.3" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", - "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" - } + "@babel/helper-plugin-utils": "7.10.4" } }, "bail": { @@ -2504,14 +2733,6 @@ "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=" }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "requires": { - "tweetnacl": "^0.14.3" - } - }, "bfj": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz", @@ -2600,6 +2821,28 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, + "boxen": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", + "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", + "requires": { + "ansi-align": "^3.0.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "cli-boxes": "^2.2.0", + "string-width": "^4.1.0", + "term-size": "^2.1.0", + "type-fest": "^0.8.1", + "widest-line": "^3.1.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + } + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2673,15 +2916,15 @@ } }, "browserify-sign": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.0.tgz", - "integrity": "sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", "requires": { "bn.js": "^5.1.1", "browserify-rsa": "^4.0.1", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", - "elliptic": "^6.5.2", + "elliptic": "^6.5.3", "inherits": "^2.0.4", "parse-asn1": "^5.1.5", "readable-stream": "^3.6.0", @@ -2704,14 +2947,14 @@ } }, "browserslist": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.13.0.tgz", - "integrity": "sha512-MINatJ5ZNrLnQ6blGvePd/QOz9Xtu+Ne+x29iQSCHfkU5BugKVJwZKn/iiL8UbpIpa3JhviKjz+XxMo0m2caFQ==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.0.tgz", + "integrity": "sha512-pUsXKAF2lVwhmtpeA3LJrZ76jXuusrNyhduuQs7CDFf9foT4Y38aQOserd2lMe5DSSrjf3fx34oHwryuvxAUgQ==", "requires": { - "caniuse-lite": "^1.0.30001093", - "electron-to-chromium": "^1.3.488", - "escalade": "^3.0.1", - "node-releases": "^1.1.58" + "caniuse-lite": "^1.0.30001111", + "electron-to-chromium": "^1.3.523", + "escalade": "^3.0.2", + "node-releases": "^1.1.60" } }, "buffer": { @@ -2881,6 +3124,40 @@ } } }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" + } + } + }, "call-me-maybe": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", @@ -2945,14 +3222,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001099", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001099.tgz", - "integrity": "sha512-sdS9A+sQTk7wKoeuZBN/YMAHVztUfVnjDi4/UV3sDE8xoh7YR12hKW+pIdB3oqKGwr9XaFL2ovfzt9w8eUI5CA==" - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "version": "1.0.30001113", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001113.tgz", + "integrity": "sha512-qMvjHiKH21zzM/VDZr6oosO6Ri3U0V2tC015jRXjOecwQCJtsU5zklTNTk31jQbIOP8gha0h1ccM/g0ECP+4BA==" }, "ccount": { "version": "1.0.5", @@ -3054,9 +3326,9 @@ } }, "chokidar": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", - "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", @@ -3141,6 +3413,11 @@ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" }, + "cli-boxes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz", + "integrity": "sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==" + }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -3150,9 +3427,9 @@ } }, "cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" }, "clipboard": { "version": "2.0.6", @@ -3175,21 +3452,6 @@ "wrap-ansi": "^5.1.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -3199,14 +3461,6 @@ "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^5.1.0" } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } } } }, @@ -3232,6 +3486,14 @@ } } }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "requires": { + "mimic-response": "^1.0.0" + } + }, "clsx": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", @@ -3309,14 +3571,6 @@ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==" }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, "comma-separated-tokens": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", @@ -3419,15 +3673,38 @@ } } }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "requires": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + }, + "dependencies": { + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + } + } + } + }, "connect-history-api-fallback": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==" }, "consola": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/consola/-/consola-2.14.0.tgz", - "integrity": "sha512-A2j1x4u8d6SIVikhZROfpFJxQZie+cZOfQMyI/tu2+hWXe8iAv7R6FW6s6x04/7zBCst94lPddztot/d6GJiuQ==" + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.0.tgz", + "integrity": "sha512-vlcSGgdYS26mPf7qNi+dCisbhiyDnrN1zaRbw3CSuc2wGOMEGGPsp46PdRG5gqXwgtJfjxDkxRNAgRPr1B77vQ==" }, "console-browserify": { "version": "1.2.0", @@ -3600,6 +3877,11 @@ } } }, + "core-js-pure": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", + "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==" + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -3618,12 +3900,12 @@ } }, "create-ecdh": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", - "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", "requires": { "bn.js": "^4.1.0", - "elliptic": "^6.0.0" + "elliptic": "^6.5.3" }, "dependencies": { "bn.js": { @@ -3686,6 +3968,11 @@ "randomfill": "^1.0.3" } }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" + }, "css-blank-pseudo": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", @@ -3975,14 +4262,6 @@ "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } - }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -4001,6 +4280,14 @@ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "requires": { + "mimic-response": "^1.0.0" + } + }, "deep-equal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", @@ -4014,6 +4301,11 @@ "regexp.prototype.flags": "^1.2.0" } }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, "default-gateway": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", @@ -4023,6 +4315,11 @@ "ip-regex": "^2.1.0" } }, + "defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -4098,11 +4395,6 @@ } } }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, "delegate": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", @@ -4141,10 +4433,10 @@ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==" }, - "detect-port-alt": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", - "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", + "detect-port": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.3.0.tgz", + "integrity": "sha512-E+B1gzkl2gqxt1IhUzwjrxBKRqx1UzC3WLONHinn8S3T6lwV/agVCyitiFOsGJ/eYuEUBvD71MZHy3Pv1G9doQ==", "requires": { "address": "^1.0.1", "debug": "^2.6.0" @@ -4227,20 +4519,6 @@ "buffer-indexof": "^1.0.0" } }, - "docsearch.js": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/docsearch.js/-/docsearch.js-2.6.3.tgz", - "integrity": "sha512-GN+MBozuyz664ycpZY0ecdQE0ND/LSgJKhTLA0/v3arIS3S1Rpf2OJz6A35ReMsm91V5apcmzr5/kM84cvUg+A==", - "requires": { - "algoliasearch": "^3.24.5", - "autocomplete.js": "0.36.0", - "hogan.js": "^3.0.2", - "request": "^2.87.0", - "stack-utils": "^1.0.1", - "to-factory": "^1.0.0", - "zepto": "^1.2.0" - } - }, "dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -4258,11 +4536,6 @@ "entities": "^1.1.1" } }, - "dom-walk": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", - "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" - }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", @@ -4308,9 +4581,14 @@ } }, "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" }, "duplexify": { "version": "3.7.1", @@ -4347,15 +4625,6 @@ } } }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4367,9 +4636,9 @@ "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==" }, "electron-to-chromium": { - "version": "1.3.496", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.496.tgz", - "integrity": "sha512-TXY4mwoyowwi4Lsrq9vcTUYBThyc1b2hXaTZI13p8/FRhY2CTaq5lK+DVjhYkKiTLsKt569Xes+0J5JsVXFurQ==" + "version": "1.3.533", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.533.tgz", + "integrity": "sha512-YqAL+NXOzjBnpY+dcOKDlZybJDCOzgsq4koW3fvyty/ldTmsb4QazZpOWmVvZ2m0t5jbBf7L0lIGU3BUipwG+A==" }, "elliptic": { "version": "6.5.3", @@ -4393,9 +4662,9 @@ } }, "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" }, "emojis-list": { "version": "3.0.0", @@ -4421,9 +4690,9 @@ } }, "enhanced-resolve": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.2.0.tgz", - "integrity": "sha512-S7eiFb/erugyd1rLb6mQ3Vuq+EXHv5cpCkNqqIkYkBgN2QdFnyCZzFBleqwGEx4lgNGYij81BWnCrFNK7vxvjQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", + "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", "requires": { "graceful-fs": "^4.1.2", "memory-fs": "^0.5.0", @@ -4468,15 +4737,6 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" }, - "envify": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/envify/-/envify-4.1.0.tgz", - "integrity": "sha512-IKRVVoAYr4pIx4yIWNsz9mOsboxlNXiu7TNBnem/K/uTHdkyzXWDzHCK7UTolqBbgaBz0tQHsD3YNls0uIIjiw==", - "requires": { - "esprima": "^4.0.0", - "through": "~2.3.4" - } - }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", @@ -4521,15 +4781,15 @@ "is-symbol": "^1.0.2" } }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, "escalade": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.1.tgz", - "integrity": "sha512-DR6NO3h9niOT+MZs7bjxlj2a1k+POu5RN8CLTPX2+i78bRi9eLe7+0zXgUHMnGXWybYcL61E9hGhPKqedy8tQA==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz", + "integrity": "sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ==" + }, + "escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==" }, "escape-html": { "version": "1.0.3", @@ -4597,9 +4857,9 @@ "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==" }, "events": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", - "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", + "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==" }, "eventsource": { "version": "1.0.7", @@ -4840,11 +5100,6 @@ } } }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4868,6 +5123,14 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, + "fast-url-parser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", + "integrity": "sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=", + "requires": { + "punycode": "^1.3.2" + } + }, "fastq": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", @@ -4905,6 +5168,15 @@ "escape-string-regexp": "^1.0.5" } }, + "file-loader": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.0.0.tgz", + "integrity": "sha512-/aMOAYEFXDdjG0wytpTL5YQLfZnnTmLNjn+AIrJ/6HVnTfDqLsVKUUwkDf4I4kgex36BvjuXEn/TX9B/1ESyqQ==", + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^2.6.5" + } + }, "filesize": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.0.1.tgz", @@ -5004,9 +5276,9 @@ } }, "follow-redirects": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.12.1.tgz", - "integrity": "sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg==" + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", + "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" }, "for-in": { "version": "1.0.2", @@ -5021,16 +5293,6 @@ "for-in": "^1.0.1" } }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, "fork-ts-checker-webpack-plugin": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-3.1.1.tgz", @@ -5177,16 +5439,6 @@ } } }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -5335,14 +5587,6 @@ "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } - }, "github-slugger": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.3.0.tgz", @@ -5384,13 +5628,12 @@ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=" }, - "global": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", - "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "global-dirs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz", + "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==", "requires": { - "min-document": "^2.19.0", - "process": "^0.11.10" + "ini": "^1.3.5" } }, "global-modules": { @@ -5475,6 +5718,24 @@ "delegate": "^3.1.2" } }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -5505,20 +5766,6 @@ "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -5533,6 +5780,13 @@ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "requires": { "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + } } }, "has-flag": { @@ -5592,6 +5846,11 @@ } } }, + "has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==" + }, "hash-base": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", @@ -5619,38 +5878,30 @@ } }, "hast-to-hyperscript": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-7.0.4.tgz", - "integrity": "sha512-vmwriQ2H0RPS9ho4Kkbf3n3lY436QKLq6VaGA1pzBh36hBi3tm1DO9bR+kaJIbpT10UqaANDkMjxvjVfr+cnOA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-9.0.0.tgz", + "integrity": "sha512-NJvMYU3GlMLs7hN3CRbsNlMzusVNkYBogVWDGybsuuVQ336gFLiD+q9qtFZT2meSHzln3pNISZWTASWothMSMg==", "requires": { + "@types/unist": "^2.0.3", "comma-separated-tokens": "^1.0.0", "property-information": "^5.3.0", "space-separated-tokens": "^1.0.0", - "style-to-object": "^0.2.1", - "unist-util-is": "^3.0.0", - "web-namespaces": "^1.1.2" - }, - "dependencies": { - "style-to-object": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.2.3.tgz", - "integrity": "sha512-1d/k4EY2N7jVLOqf2j04dTc37TPOv/hHxZmvpg8Pdh8UYydxeu/C1W1U4vD8alzf5V2Gt7rLsmkr4dxAlDm9ng==", - "requires": { - "inline-style-parser": "0.1.1" - } - } + "style-to-object": "^0.3.0", + "unist-util-is": "^4.0.0", + "web-namespaces": "^1.0.0" } }, "hast-util-from-parse5": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-5.0.3.tgz", - "integrity": "sha512-gOc8UB99F6eWVWFtM9jUikjN7QkWxB3nY0df5Z0Zq1/Nkwl5V4hAAsl0tmwlgWl/1shlTF8DnNYLO8X6wRV9pA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-6.0.0.tgz", + "integrity": "sha512-3ZYnfKenbbkhhNdmOQqgH10vnvPivTdsOJCri+APn0Kty+nRkDHArnaX9Hiaf8H+Ig+vkNptL+SRY/6RwWJk1Q==", "requires": { - "ccount": "^1.0.3", + "@types/parse5": "^5.0.0", + "ccount": "^1.0.0", "hastscript": "^5.0.0", "property-information": "^5.0.0", - "web-namespaces": "^1.1.2", - "xtend": "^4.0.1" + "vfile": "^4.0.0", + "web-namespaces": "^1.0.0" } }, "hast-util-parse-selector": { @@ -5659,26 +5910,28 @@ "integrity": "sha512-gW3sxfynIvZApL4L07wryYF4+C9VvH3AUi7LAnVXV4MneGEgwOByXvFo18BgmTWnm7oHAe874jKbIB1YhHSIzA==" }, "hast-util-raw": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-5.0.2.tgz", - "integrity": "sha512-3ReYQcIHmzSgMq8UrDZHFL0oGlbuVGdLKs8s/Fe8BfHFAyZDrdv1fy/AGn+Fim8ZuvAHcJ61NQhVMtyfHviT/g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-6.0.0.tgz", + "integrity": "sha512-IQo6tv3bMMKxk53DljswliucCJOQxaZFCuKEJ7X80249dmJ1nA9LtOnnylsLlqTG98NjQ+iGcoLAYo9q5FRhRg==", "requires": { - "hast-util-from-parse5": "^5.0.0", - "hast-util-to-parse5": "^5.0.0", + "@types/hast": "^2.0.0", + "hast-util-from-parse5": "^6.0.0", + "hast-util-to-parse5": "^6.0.0", "html-void-elements": "^1.0.0", - "parse5": "^5.0.0", + "parse5": "^6.0.0", "unist-util-position": "^3.0.0", + "vfile": "^4.0.0", "web-namespaces": "^1.0.0", "xtend": "^4.0.0", "zwitch": "^1.0.0" } }, "hast-util-to-parse5": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-5.1.2.tgz", - "integrity": "sha512-ZgYLJu9lYknMfsBY0rBV4TJn2xiwF1fXFFjbP6EE7S0s5mS8LIKBVWzhA1MeIs1SWW6GnnE4In6c3kPb+CWhog==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz", + "integrity": "sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ==", "requires": { - "hast-to-hyperscript": "^7.0.0", + "hast-to-hyperscript": "^9.0.0", "property-information": "^5.0.0", "web-namespaces": "^1.0.0", "xtend": "^4.0.0", @@ -5729,22 +5982,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "hogan.js": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/hogan.js/-/hogan.js-3.0.2.tgz", - "integrity": "sha1-TNnhq9QpQUbnZ55B14mHMrAse/0=", - "requires": { - "mkdirp": "0.3.0", - "nopt": "1.0.10" - }, - "dependencies": { - "mkdirp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", - "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=" - } - } - }, "hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -5895,6 +6132,11 @@ "readable-stream": "^3.1.1" } }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, "http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", @@ -6056,16 +6298,6 @@ } } }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, "https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", @@ -6107,11 +6339,6 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" }, - "immediate": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", - "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==" - }, "immer": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz", @@ -6149,6 +6376,11 @@ } } }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" + }, "import-local": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", @@ -6208,46 +6440,80 @@ "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" }, "inquirer": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.4.tgz", - "integrity": "sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", "requires": { "ansi-escapes": "^4.2.1", - "chalk": "^2.4.2", + "chalk": "^4.1.0", "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", + "cli-width": "^3.0.0", "external-editor": "^3.0.3", "figures": "^3.0.0", - "lodash": "^4.17.15", + "lodash": "^4.17.19", "mute-stream": "0.0.8", - "run-async": "^2.2.0", - "rxjs": "^6.5.3", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", "string-width": "^4.1.0", - "strip-ansi": "^5.1.0", + "strip-ansi": "^6.0.0", "through": "^2.3.6" }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" } }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "requires": { - "ansi-regex": "^4.1.0" + "has-flag": "^4.0.0" } } } @@ -6354,6 +6620,21 @@ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==" }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "requires": { + "ci-info": "^2.0.0" + }, + "dependencies": { + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + } + } + }, "is-color-stop": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", @@ -6418,9 +6699,9 @@ "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" }, "is-docker": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz", - "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", + "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==" }, "is-extendable": { "version": "0.1.1", @@ -6433,9 +6714,9 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "is-glob": { "version": "4.0.1", @@ -6450,6 +6731,20 @@ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==" }, + "is-installed-globally": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", + "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "requires": { + "global-dirs": "^2.0.1", + "is-path-inside": "^3.0.1" + } + }, + "is-npm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", + "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==" + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -6502,9 +6797,9 @@ } }, "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "requires": { "has-symbols": "^1.0.1" } @@ -6573,6 +6868,11 @@ "is-docker": "^2.0.0" } }, + "is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -6588,11 +6888,6 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, "jest-worker": { "version": "25.5.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.5.0.tgz", @@ -6631,36 +6926,26 @@ "esprima": "^4.0.0" } }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" + }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, "json3": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", @@ -6682,15 +6967,12 @@ "graceful-fs": "^4.1.6" } }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" + "json-buffer": "3.0.0" } }, "killable": { @@ -6712,6 +6994,14 @@ "webpack-sources": "^1.1.0" } }, + "latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "requires": { + "package-json": "^6.3.0" + } + }, "lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", @@ -6735,11 +7025,6 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" }, - "load-script": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz", - "integrity": "sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ=" - }, "loader-runner": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", @@ -6942,6 +7227,11 @@ "tslib": "^1.10.0" } }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -7174,13 +7464,10 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, - "min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", - "requires": { - "dom-walk": "^0.1.0" - } + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" }, "mini-create-react-context": { "version": "0.4.0", @@ -7287,9 +7574,9 @@ } }, "minipass-pipeline": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.3.tgz", - "integrity": "sha512-cFOknTvng5vqnwOpDsZTWhNll6Jf8o2x+/diplafmxpuIymAjzoOolZG0VvQf3V2HgqzJNhnuKHYp2BqDgz8IQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "requires": { "minipass": "^3.0.0" } @@ -7522,17 +7809,9 @@ } }, "node-releases": { - "version": "1.1.59", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.59.tgz", - "integrity": "sha512-H3JrdUczbdiwxN5FuJPyCHnGHIFqQ0wWxo+9j1kAXAzqNMAHlo+4I/sYYxpyK0irQ73HgdiyzD32oqQDcU2Osw==" - }, - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "requires": { - "abbrev": "1" - } + "version": "1.1.60", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz", + "integrity": "sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==" }, "normalize-path": { "version": "3.0.0", @@ -7627,11 +7906,6 @@ "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=" }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -7758,17 +8032,17 @@ } }, "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "requires": { "mimic-fn": "^2.1.0" } }, "open": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/open/-/open-7.0.4.tgz", - "integrity": "sha512-brSA+/yq+b08Hsr4c8fsEW2CRzk1BmfN3SAK/5VCHQ9bdoZJ4qa/+AfR0xHjlbbZUyPkUHs1b8x1RqdyZdkVqQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-7.1.0.tgz", + "integrity": "sha512-lLPI5KgOwEYCDKXf4np7y1PBEkj7HYIyP2DY8mVDRnx0VIIu6bNrRB0R66TuO7Mack6EnTNLm4uvcl1UoklTpA==", "requires": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" @@ -7821,6 +8095,11 @@ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" + }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -7863,6 +8142,17 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, + "package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "requires": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + } + }, "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -7920,13 +8210,12 @@ } }, "parse-asn1": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", - "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", "requires": { - "asn1.js": "^4.0.0", + "asn1.js": "^5.2.0", "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.0", "pbkdf2": "^3.0.3", "safe-buffer": "^5.1.1" @@ -7946,9 +8235,9 @@ } }, "parse-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", - "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.1.tgz", + "integrity": "sha512-ztoZ4/DYeXQq4E21v169sC8qWINGpcosGv9XhTDvg9/hWvx/zrFkc9BiWxR58OJLHGk28j5BL0SDLeV2WmFZlQ==", "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -7962,9 +8251,9 @@ "integrity": "sha1-tPCdQTx6282Yf26SM8e0shDJOOQ=" }, "parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" }, "parseurl": { "version": "1.3.3", @@ -8042,11 +8331,6 @@ "sha.js": "^2.4.8" } }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", @@ -8095,13 +8379,13 @@ } }, "portfinder": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz", - "integrity": "sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", "requires": { "async": "^2.6.2", "debug": "^3.1.1", - "mkdirp": "^0.5.1" + "mkdirp": "^0.5.5" }, "dependencies": { "debug": { @@ -8174,9 +8458,9 @@ } }, "postcss-calc": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.2.tgz", - "integrity": "sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.3.tgz", + "integrity": "sha512-IB/EAEmZhIMEIhG7Ov4x+l47UaXOS1n2f4FBUk/aKllQhtSCxWhTzn0nJgkqN7fo/jcWySvWTSB6Syk9L+31bA==", "requires": { "postcss": "^7.0.27", "postcss-selector-parser": "^6.0.2", @@ -8675,14 +8959,14 @@ } }, "postcss-modules-local-by-default": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz", - "integrity": "sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", + "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", "requires": { "icss-utils": "^4.1.1", - "postcss": "^7.0.16", + "postcss": "^7.0.32", "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.0" + "postcss-value-parser": "^4.1.0" } }, "postcss-modules-scope": { @@ -9107,9 +9391,9 @@ "integrity": "sha512-MgMhSdHuHymNRqD6KM3eGS0PNqgK9q4QF5P0yoQQvpB6jNjeSAi3jcSAz0Sua/t9fa4xDOMar9HJbLa08gl9ug==" }, "prismjs": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.20.0.tgz", - "integrity": "sha512-AEDjSrVNkynnw6A+B1DsFkd6AVdTnp+/WoUixFRULlCLZVRZlVQMVWio/16jv7G1FscUxQxOQhWwApgbnxr6kQ==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.21.0.tgz", + "integrity": "sha512-uGdSIu1nk3kej2iZsLyDoJ7e9bnPzIgY0naW/HdknGj61zScaprVEVGHrPoXqI+M9sP0NDnTK2jpkvmldpuqDw==", "requires": { "clipboard": "^2.0.0" } @@ -9161,11 +9445,6 @@ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" - }, "public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -9221,6 +9500,14 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" }, + "pupa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz", + "integrity": "sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==", + "requires": { + "escape-goat": "^2.0.0" + } + }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -9288,6 +9575,17 @@ "unpipe": "1.0.0" } }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, "react": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", @@ -9342,11 +9640,6 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - }, "braces": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", @@ -9402,6 +9695,28 @@ } } }, + "cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "detect-port-alt": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", + "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", + "requires": { + "address": "^1.0.1", + "debug": "^2.6.0" + } + }, "dir-glob": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", @@ -9516,6 +9831,36 @@ "slash": "^1.0.0" } }, + "inquirer": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.4.tgz", + "integrity": "sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==", + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^2.4.2", + "cli-cursor": "^3.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", + "run-async": "^2.2.0", + "rxjs": "^6.5.3", + "string-width": "^4.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -9580,6 +9925,11 @@ "to-regex": "^3.0.2" } }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -9612,6 +9962,13 @@ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "requires": { "ansi-regex": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + } } }, "to-regex-range": { @@ -9781,14 +10138,6 @@ "minimatch": "3.0.4" } }, - "reduce": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/reduce/-/reduce-1.0.2.tgz", - "integrity": "sha512-xX7Fxke/oHO5IfZSk77lvPa/7bjMh9BuCk4OOoX5XTXrM7s0Z+MkPfSDfz0q7r91BhhGSs8gii/VEN/7zhCPpQ==", - "requires": { - "object-keys": "^1.1.0" - } - }, "regenerate": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz", @@ -9803,9 +10152,9 @@ } }, "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" }, "regenerator-transform": { "version": "0.14.5", @@ -9865,6 +10214,22 @@ "unicode-match-property-value-ecmascript": "^1.2.0" } }, + "registry-auth-token": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.0.tgz", + "integrity": "sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w==", + "requires": { + "rc": "^1.2.8" + } + }, + "registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "requires": { + "rc": "^1.2.8" + } + }, "regjsgen": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", @@ -9893,6 +10258,25 @@ "hast-util-from-parse5": "^5.0.0", "parse5": "^5.0.0", "xtend": "^4.0.0" + }, + "dependencies": { + "hast-util-from-parse5": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-5.0.3.tgz", + "integrity": "sha512-gOc8UB99F6eWVWFtM9jUikjN7QkWxB3nY0df5Z0Zq1/Nkwl5V4hAAsl0tmwlgWl/1shlTF8DnNYLO8X6wRV9pA==", + "requires": { + "ccount": "^1.0.3", + "hastscript": "^5.0.0", + "property-information": "^5.0.0", + "web-namespaces": "^1.1.2", + "xtend": "^4.0.1" + } + }, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" + } } }, "relateurl": { @@ -9945,64 +10329,51 @@ "integrity": "sha512-X9Ncj4cj3/CIvLI2Z9IobHtVi8FVdUrdJkCNaL9kdX8ohfsi18DXHsCVd/A7ssARBdccdDb5ODnt62WuEWaM/g==" }, "remark-mdx": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-1.6.6.tgz", - "integrity": "sha512-BkR7SjP+3OvrCsWGlYy1tWEsZ8aQ86x+i7XWbW79g73Ws/cCaeVsEn0ZxAzzoTRH+PJWVU7Mbe64GdejEyKr2g==", - "requires": { - "@babel/core": "7.9.6", - "@babel/helper-plugin-utils": "7.8.3", - "@babel/plugin-proposal-object-rest-spread": "7.9.6", - "@babel/plugin-syntax-jsx": "7.8.3", - "@mdx-js/util": "^1.6.6", + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-1.6.16.tgz", + "integrity": "sha512-xqZhBQ4TonFiSFpVt6SnTLRnxstu7M6pcaOibKZhqzk4zMRVacVenD7iECjfESK+72LkPm/NW+0r5ahJAg7zlQ==", + "requires": { + "@babel/core": "7.10.5", + "@babel/helper-plugin-utils": "7.10.4", + "@babel/plugin-proposal-object-rest-spread": "7.10.4", + "@babel/plugin-syntax-jsx": "7.10.4", + "@mdx-js/util": "1.6.16", "is-alphabetical": "1.0.4", - "remark-parse": "8.0.2", - "unified": "9.0.0" + "remark-parse": "8.0.3", + "unified": "9.1.0" }, "dependencies": { "@babel/core": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.6.tgz", - "integrity": "sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg==", - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.6", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helpers": "^7.9.6", - "@babel/parser": "^7.9.6", - "@babel/template": "^7.8.6", - "@babel/traverse": "^7.9.6", - "@babel/types": "^7.9.6", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.5.tgz", + "integrity": "sha512-O34LQooYVDXPl7QWCdW9p4NR+QlzOr7xShPPJz8GsuCU3/8ua/wqTr7gmnxXv+WBESiGU/G5s16i6tUvHkNb+w==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helpers": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.5", + "@babel/types": "^7.10.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", "json5": "^2.1.2", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" } }, - "@babel/helper-plugin-utils": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", - "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" - }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.6.tgz", - "integrity": "sha512-Ga6/fhGqA9Hj+y6whNpPv8psyaK5xzrQwSPsGPloVkvmH+PqW1ixdnfJ9uIO06OjQNYol3PMnfmJ8vfZtkzF+A==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.4.tgz", + "integrity": "sha512-6vh4SqRuLLarjgeOf4EaROJAHjvu9Gl+/346PbDH9yWbJyfnJ/ah3jmYKYtswEyCoWZiidvVHjHshd4WgjB9BA==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.9.5" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz", - "integrity": "sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/plugin-transform-parameters": "^7.10.4" } }, "semver": { @@ -10013,9 +10384,9 @@ } }, "remark-parse": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.2.tgz", - "integrity": "sha512-eMI6kMRjsAGpMXXBAywJwiwAse+KNpmt+BK55Oofy4KvBZEqUDj6mWbGLJZrujoPIPPxDXzn3T9baRlpsm2jnQ==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.3.tgz", + "integrity": "sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q==", "requires": { "ccount": "^1.0.0", "collapse-white-space": "^1.0.2", @@ -10058,6 +10429,21 @@ "htmlparser2": "^3.3.0", "strip-ansi": "^3.0.0", "utila": "^0.4.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } } }, "repeat-element": { @@ -10075,40 +10461,6 @@ "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=" }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - } - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -10167,6 +10519,14 @@ "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "requires": { + "lowercase-keys": "^1.0.0" + } + }, "restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -10242,9 +10602,9 @@ "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=" }, "rxjs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz", - "integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz", + "integrity": "sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==", "requires": { "tslib": "^1.9.0" } @@ -10324,6 +10684,14 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, + "semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "requires": { + "semver": "^6.3.0" + } + }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -10371,6 +10739,56 @@ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==" }, + "serve-handler": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.3.tgz", + "integrity": "sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==", + "requires": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "fast-url-parser": "1.1.3", + "mime-types": "2.1.18", + "minimatch": "3.0.4", + "path-is-inside": "1.0.2", + "path-to-regexp": "2.2.1", + "range-parser": "1.2.0" + }, + "dependencies": { + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "requires": { + "mime-db": "~1.33.0" + } + }, + "path-to-regexp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", + "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==" + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + } + } + }, "serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", @@ -10813,22 +11231,6 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, "ssri": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", @@ -10842,11 +11244,6 @@ "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" }, - "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==" - }, "state-toggle": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", @@ -10987,6 +11384,16 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -11048,11 +11455,11 @@ } }, "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "^4.1.0" } }, "strip-bom-string": { @@ -11070,6 +11477,11 @@ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, "style-to-object": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", @@ -11175,6 +11587,11 @@ "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" }, + "term-size": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz", + "integrity": "sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==" + }, "terser": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", @@ -11198,16 +11615,16 @@ } }, "terser-webpack-plugin": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.7.tgz", - "integrity": "sha512-xzYyaHUNhzgaAdBsXxk2Yvo/x1NJdslUaussK3fdpBbvttm1iIwU+c26dj9UxJcwk2c5UWt5F55MUTIA8BE7Dg==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.8.tgz", + "integrity": "sha512-/fKw3R+hWyHfYx7Bv6oPqmk4HGQcrWLtV3X6ggvPuwPNHSnzvVV51z6OaaCOus4YLjutYGOz3pEpbhe6Up2s1w==", "requires": { "cacache": "^13.0.1", "find-cache-dir": "^3.3.1", "jest-worker": "^25.4.0", "p-limit": "^2.3.0", "schema-utils": "^2.6.6", - "serialize-javascript": "^3.1.0", + "serialize-javascript": "^4.0.0", "source-map": "^0.6.1", "terser": "^4.6.12", "webpack-sources": "^1.4.3" @@ -11295,9 +11712,9 @@ } }, "serialize-javascript": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", - "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", "requires": { "randombytes": "^2.1.0" } @@ -11408,11 +11825,6 @@ "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=" }, - "to-factory": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-factory/-/to-factory-1.0.0.tgz", - "integrity": "sha1-hzivi9lxIK0dQEeXKtpVY7+UebE=" - }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -11436,6 +11848,11 @@ } } }, + "to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" + }, "to-regex": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", @@ -11479,22 +11896,6 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - } - } - }, "tr46": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", @@ -11550,23 +11951,10 @@ "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=" }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" }, "type-is": { "version": "1.6.18", @@ -11582,6 +11970,14 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "requires": { + "is-typedarray": "^1.0.0" + } + }, "unherit": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", @@ -11616,9 +12012,9 @@ "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==" }, "unified": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/unified/-/unified-9.0.0.tgz", - "integrity": "sha512-ssFo33gljU3PdlWLjNp15Inqb77d6JnJSfyplGJPT/a+fNRNyCBeveBAYJdO5khKdF6WVHa/yYCC7Xl6BDwZUQ==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.1.0.tgz", + "integrity": "sha512-VXOv7Ic6twsKGJDeZQ2wwPqXs2hM0KNu5Hkg9WgAZbSD1pxhZ7p8swqg583nw1Je2fhwHy6U8aEjiI79x1gvag==", "requires": { "bail": "^1.0.0", "extend": "^3.0.0", @@ -11677,6 +12073,14 @@ "imurmurhash": "^0.1.4" } }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "requires": { + "crypto-random-string": "^2.0.0" + } + }, "unist-builder": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz", @@ -11688,9 +12092,9 @@ "integrity": "sha512-1TC+NxQa4N9pNdayCYA1EGUOCAO0Le3fVp7Jzns6lnua/mYgwHo0tz5WUAfrdpNch1RZLHc61VZ1SDgrtNXLSw==" }, "unist-util-is": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz", - "integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz", + "integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==" }, "unist-util-position": { "version": "3.1.0", @@ -11703,13 +12107,6 @@ "integrity": "sha512-HwwWyNHKkeg/eXRnE11IpzY8JT55JNM1YCwwU9YNCnfzk6s8GhPXrVBBZWiwLeATJbI7euvoGSzcy9M29UeW3g==", "requires": { "unist-util-is": "^4.0.0" - }, - "dependencies": { - "unist-util-is": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz", - "integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==" - } } }, "unist-util-remove-position": { @@ -11736,13 +12133,6 @@ "@types/unist": "^2.0.0", "unist-util-is": "^4.0.0", "unist-util-visit-parents": "^3.0.0" - }, - "dependencies": { - "unist-util-is": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz", - "integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==" - } } }, "unist-util-visit-parents": { @@ -11752,13 +12142,6 @@ "requires": { "@types/unist": "^2.0.0", "unist-util-is": "^4.0.0" - }, - "dependencies": { - "unist-util-is": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz", - "integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==" - } } }, "universalify": { @@ -11817,6 +12200,26 @@ "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" }, + "update-notifier": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.0.tgz", + "integrity": "sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew==", + "requires": { + "boxen": "^4.2.0", + "chalk": "^3.0.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.3.1", + "is-npm": "^4.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.0.0", + "pupa": "^2.0.1", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + } + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -11846,6 +12249,16 @@ "querystring": "0.2.0" } }, + "url-loader": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.0.tgz", + "integrity": "sha512-IzgAAIC8wRrg6NYkFIJY09vtktQcsvU8V6HhtQj9PTefbYImzLB1hufqo4m+RyM5N3mLx5BqJKccgxJS+W3kqw==", + "requires": { + "loader-utils": "^2.0.0", + "mime-types": "^2.1.26", + "schema-utils": "^2.6.5" + } + }, "url-parse": { "version": "1.4.7", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", @@ -11855,6 +12268,21 @@ "requires-port": "^1.0.0" } }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "requires": { + "prepend-http": "^2.0.0" + }, + "dependencies": { + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" + } + } + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -11921,20 +12349,10 @@ "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==" }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "vfile": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.1.1.tgz", - "integrity": "sha512-lRjkpyDGjVlBA7cDQhQ+gNcvB1BGaTHYuSOcY3S7OhDmBtnzX95FhtZZDecSTDm6aajFymyve6S5DN4ZHGezdQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.0.tgz", + "integrity": "sha512-a/alcwCvtuc8OX92rqqo7PflxiCgXRFjdyoGVuYV+qbgCb0GgZJRvIgCD4+U/Kl1yhaRsaTwksF88xbPyGsgpw==", "requires": { "@types/unist": "^2.0.0", "is-buffer": "^2.0.0", @@ -11977,14 +12395,45 @@ "@hapi/joi": "^15.1.0", "fs-extra": "^8.1.0", "rx": "^4.1.0" + }, + "dependencies": { + "@hapi/address": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", + "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==" + }, + "@hapi/hoek": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", + "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==" + }, + "@hapi/joi": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.1.1.tgz", + "integrity": "sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ==", + "requires": { + "@hapi/address": "2.x.x", + "@hapi/bourne": "1.x.x", + "@hapi/hoek": "8.x.x", + "@hapi/topo": "3.x.x" + } + }, + "@hapi/topo": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", + "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==", + "requires": { + "@hapi/hoek": "^8.3.0" + } + } } }, "watchpack": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz", - "integrity": "sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz", + "integrity": "sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==", "requires": { - "chokidar": "^3.4.0", + "chokidar": "^3.4.1", "graceful-fs": "^4.1.2", "neo-async": "^2.5.0", "watchpack-chokidar2": "^2.0.0" @@ -12262,9 +12711,9 @@ "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" }, "webpack": { - "version": "4.43.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz", - "integrity": "sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.1.tgz", + "integrity": "sha512-4UOGAohv/VGUNQJstzEywwNxqX417FnjZgZJpJQegddzPmTvph37eBIRbRTfdySXzVtJXLJfbMN3mMYhM6GdmQ==", "requires": { "@webassemblyjs/ast": "1.9.0", "@webassemblyjs/helper-module-context": "1.9.0", @@ -12274,7 +12723,7 @@ "ajv": "^6.10.2", "ajv-keywords": "^3.4.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.1.0", + "enhanced-resolve": "^4.3.0", "eslint-scope": "^4.0.3", "json-parse-better-errors": "^1.0.2", "loader-runner": "^2.4.0", @@ -12287,7 +12736,7 @@ "schema-utils": "^1.0.0", "tapable": "^1.1.3", "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.6.1", + "watchpack": "^1.7.4", "webpack-sources": "^1.4.1" }, "dependencies": { @@ -12430,9 +12879,9 @@ } }, "serialize-javascript": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", - "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", "requires": { "randombytes": "^2.1.0" } @@ -12443,15 +12892,15 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "terser-webpack-plugin": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz", - "integrity": "sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", "requires": { "cacache": "^12.0.2", "find-cache-dir": "^2.1.0", "is-wsl": "^1.1.0", "schema-utils": "^1.0.0", - "serialize-javascript": "^3.1.0", + "serialize-javascript": "^4.0.0", "source-map": "^0.6.1", "terser": "^4.1.2", "webpack-sources": "^1.4.0", @@ -12490,9 +12939,9 @@ }, "dependencies": { "acorn": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", - "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==" + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", + "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==" }, "chalk": { "version": "2.4.2", @@ -12575,6 +13024,11 @@ "yargs": "^13.3.2" }, "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", @@ -12841,6 +13295,14 @@ "safe-buffer": "~5.1.0" } }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, "supports-color": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", @@ -13003,6 +13465,14 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, + "widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "requires": { + "string-width": "^4.0.0" + } + }, "worker-farm": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", @@ -13029,21 +13499,6 @@ "strip-ansi": "^5.0.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -13053,14 +13508,6 @@ "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^5.1.0" } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } } } }, @@ -13069,6 +13516,17 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, "ws": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", @@ -13077,6 +13535,11 @@ "async-limiter": "~1.0.0" } }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" + }, "xml-js": { "version": "1.6.11", "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", @@ -13127,21 +13590,6 @@ "yargs-parser": "^13.1.2" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -13151,14 +13599,6 @@ "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^5.1.0" } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } } } }, @@ -13178,11 +13618,6 @@ } } }, - "zepto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/zepto/-/zepto-1.2.0.tgz", - "integrity": "sha1-4Se9nmb9hGvl6rSME5SIL3wOT5g=" - }, "zwitch": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", diff --git a/website/package.json b/website/package.json index d024da876c..c2ae0c4742 100644 --- a/website/package.json +++ b/website/package.json @@ -5,13 +5,18 @@ "scripts": { "start": "docusaurus start", "start-no-watch": "docusaurus start --no-open --poll", - "build": "docusaurus build", + "build": "npm run versions && docusaurus build", "swizzle": "docusaurus swizzle", - "deploy": "./deploy.sh" + "deploy": "./deploy.sh", + "version": "docusaurus docs:version", + "versions": "./build-versions.sh", + "add-version": "./add-version.sh", + "docusaurus": "docusaurus", + "serve": "docusaurus serve" }, "dependencies": { - "@docusaurus/core": "^2.0.0-alpha.58", - "@docusaurus/preset-classic": "^2.0.0-alpha.58", + "@docusaurus/core": "^2.0.0-alpha.59", + "@docusaurus/preset-classic": "^2.0.0-alpha.59", "clsx": "^1.1.1", "react": "^16.8.4", "react-dom": "^16.8.4" diff --git a/website/sidebars.js b/website/sidebars.js index 03b790c3e4..5d164a979b 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -39,6 +39,12 @@ module.exports = { 'developer/backend', ] }, + { + Deploy: [ + 'developer/deploy', + 'developer/developers-guide-helm' + ] + }, 'developer/developers-guide-e2e-tests', 'developer/developers-guide-env-tech', ], diff --git a/website/src/css/custom.css b/website/src/css/custom.css index 1b5e6f88cd..314ad649bf 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -72,6 +72,12 @@ img.cf-logo { padding-top: 20px; } +/* Fix for dark mode, given the backgrounds are hardcoded below */ +.hero__title, +.hero__subtitle { + color: #fff; +} + .hero__subtitle { font-size: 36px; } @@ -94,7 +100,8 @@ img.cf-logo { font-size: 18px; } -.menu__list, .menu__link { +.menu__list, +.menu__link { font-size: 14px; } @@ -125,7 +132,7 @@ img.cf-logo { .hero__subtitle { font-size: 35px; } - + section .screenshot { margin: 0 0; } @@ -166,9 +173,9 @@ img.cf-logo { .screenshot img { width: 600px; - -webkit-box-shadow: 0px 0px 37px 15px rgba(20,20,20,0.75); - -moz-box-shadow: 0px 0px 37px 15px rgba(20,20,20,0.75); - box-shadow: 0px 0px 37px 15px rgba(20,20,20,0.75); + -webkit-box-shadow: 0px 0px 37px 15px rgba(20, 20, 20, 0.75); + -moz-box-shadow: 0px 0px 37px 15px rgba(20, 20, 20, 0.75); + box-shadow: 0px 0px 37px 15px rgba(20, 20, 20, 0.75); } .screenshot img.right { @@ -189,3 +196,14 @@ img.cf-logo { margin: 0; } } + + +/* Hide versions drop down and versions link on home page */ +.stratosIsNotDocsPage .navbar__item.dropdown--right { + display: none; +} + +/* Hide the `Versions: x` pill that appears above a doc page's title */ +article > div > .badge.badge--secondary { + display: none; +} diff --git a/website/src/pages/index.js b/website/src/pages/index.js index fc5979e229..941fe6a213 100644 --- a/website/src/pages/index.js +++ b/website/src/pages/index.js @@ -73,7 +73,7 @@ const features = [ ]; -function Feature({imageUrl, title, description, cls}) { +function Feature({ imageUrl, title, description, cls }) { const imgUrl = useBaseUrl(imageUrl); return ( <div className={clsx('col col--4', styles.feature)}> @@ -90,15 +90,15 @@ function Feature({imageUrl, title, description, cls}) { function Home() { const context = useDocusaurusContext(); - const {siteConfig = {}} = context; + const { siteConfig = {} } = context; return ( <Layout title={`Home`} description="Stratos - Web-based Management Interface for Cloud Foundry and Kubernetes"> <header className={clsx('hero hero--primary', styles.heroBanner)}> <div className="container home-intro"> - <h1 className="hero__title">{siteConfig.title}</h1> - <h2 className="hero__subtitle">Open-Source Multi-Cluster UI for <br/> Cloud Foundry and Kubernetes</h2> + <h1 className="hero__title">{siteConfig.title}</h1> + <h2 className="hero__subtitle">Open-Source Multi-Cluster UI for <br /> Cloud Foundry and Kubernetes</h2> <div className={clsx(styles.buttons, 'get-started')}> <Link className={clsx( @@ -110,7 +110,7 @@ function Home() { </Link> </div> </div> - <img class="home-logo" src="img/logo.png" /> + <img className="home-logo" src="img/logo.png" /> </header> <main> {features && features.length > 0 && ( @@ -126,8 +126,8 @@ function Home() { )} <section className={clsx(styles.features, 'screenshot-section', 'blue')}> <div className="container"> - <div class="screenshot"> - <img class="left" src="img/screens/cf-app.png" /> + <div className="screenshot"> + <img className="left" src="img/screens/cf-app.png" /> <div> <h2>Cloud Foundry</h2> <p>Deploy and manage applications in Cloud Foundry. Stream application logs, scale applications and ssh to application instances</p> @@ -139,9 +139,9 @@ function Home() { </div> </section> - <section className={clsx(styles.features, 'screenshot-section', 'white')}> + <section className={clsx(styles.features)}> <div className="container"> - <div class="screenshot"> + <div className="screenshot"> <div> <h2>Kubernetes</h2> <p>View cluster-level metadata</p> @@ -149,15 +149,15 @@ function Home() { <p>View Helm Releases and see relationships between Kubernetes Resources</p> <p>and lots more ...</p> </div> - <img class="right" src="img/screens/kube-graph.png" /> + <img className="right" src="img/screens/kube-graph.png" /> </div> </div> </section> <section className={clsx(styles.features, 'screenshot-section', 'blue')}> <div className="container"> - <div class="screenshot"> - <img class="left" src="img/screens/endpoints.png" /> + <div className="screenshot"> + <img className="left" src="img/screens/endpoints.png" /> <div> <h2>Multi-Cluster</h2> <p>Add and Connect multiple Cloud Foundry and/or Kubernetes clusters.</p> diff --git a/website/src/pages/versions.js b/website/src/pages/versions.js new file mode 100644 index 0000000000..d8fe416fcb --- /dev/null +++ b/website/src/pages/versions.js @@ -0,0 +1,95 @@ +import Link from '@docusaurus/Link'; +import useBaseUrl from '@docusaurus/useBaseUrl'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import Layout from '@theme/Layout'; +import React from 'react'; + +import versionsWithHash from '../../internal-versions.json'; +import versions from '../../versions.json'; + +function Version() { + const context = useDocusaurusContext(); + const { siteConfig = {} } = context; + const latestVersion = versions[0]; + const pastVersions = versionsWithHash + .map(versionWithHash => versionWithHash.split(':')) + .filter((versionWithHash) => versionWithHash[0] !== latestVersion); + + + const repoUrl = `https://github.com/${siteConfig.organizationName}/${siteConfig.projectName}`; + return ( + <Layout + title="Versions" + permalink="/versions"> + <main className="container margin-vert--lg"> + <h1>All Versions</h1> + <div className="margin-bottom--lg"> + <h3 id="latest">Latest version (Stable)</h3> + <p>Here you can find the latest documentation.</p> + <table> + <tbody> + <tr> + <th>{latestVersion}</th> + <td> + <Link to={useBaseUrl('/docs')}>Documentation</Link> + </td> + <td> + <a href={`${repoUrl}/releases/tag/${latestVersion}`}> + Release Notes + </a> + </td> + </tr> + </tbody> + </table> + </div> + <div className="margin-bottom--lg"> + <h3 id="next">Next version (Unreleased)</h3> + <p>Here you can find the documentation for unreleased version.</p> + <table> + <tbody> + <tr> + <th>master</th> + <td> + <Link to={useBaseUrl('/docs/next')}>Documentation</Link> + </td> + <td> + <a href={repoUrl}>Source Code</a> + </td> + </tr> + </tbody> + </table> + </div> + {pastVersions.length > 0 && ( + <div className="margin-bottom--lg"> + <h3 id="archive">Past Versions</h3> + <p> + Here you can find documentation for previous versions of + Docusaurus. + </p> + <table> + <tbody> + {pastVersions.map((version) => ( + <tr key={version[0]}> + <th>{version[0]}</th> + <td> + <Link to={useBaseUrl(`/docs/${version[0]}`)}> + Documentation + </Link> + </td> + <td> + <a href={`${repoUrl}/releases/tag/${version[0]}`}> + Release Notes + </a> + </td> + </tr> + ))} + </tbody> + </table> + </div> + )} + </main> + </Layout> + ); +} + +export default Version; \ No newline at end of file diff --git a/website/src/theme/DocVersionSuggestions/index.js b/website/src/theme/DocVersionSuggestions/index.js new file mode 100644 index 0000000000..3612410fc2 --- /dev/null +++ b/website/src/theme/DocVersionSuggestions/index.js @@ -0,0 +1,64 @@ +import Link from '@docusaurus/Link'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import { useActivePlugin, useActiveVersion, useDocVersionSuggestions } from '@theme/hooks/useDocs'; +import React from 'react'; + +import styles from './styles.module.css'; + +const useMandatoryActiveDocsPluginId = () => { + const activePlugin = useActivePlugin(); + + if (!activePlugin) { + throw new Error( + 'DocVersionCallout is only supposed to be used on docs-related routes', + ); + } + + return activePlugin.pluginId; +}; + +const getVersionMainDoc = (version) => + version.docs.find((doc) => doc.id === version.mainDocId); + +function DocVersionSuggestions() { + const { + siteConfig: { title: siteTitle }, + } = useDocusaurusContext(); + const pluginId = useMandatoryActiveDocsPluginId(); + const activeVersion = useActiveVersion(pluginId); + const { + latestDocSuggestion, + latestVersionSuggestion, + } = useDocVersionSuggestions(pluginId); // No suggestion to be made + + if (!latestVersionSuggestion) { + return <></>; + } + + const activeVersionName = activeVersion.name; // try to link to same doc in latest version (not always possible) + // fallback to main doc of latest version + + const suggestedDoc = + latestDocSuggestion ?? getVersionMainDoc(latestVersionSuggestion); + return ( + <div className="alert alert--secondary margin-bottom--md" role="alert" > + {activeVersionName === 'next' ? ( + <div className={styles.container}> + This is the development documentation. For the latest released documentation see{' '} + <strong> + <Link to={suggestedDoc.path}> {latestVersionSuggestion.name}</Link> + </strong>. + </div> + ) : ( + <div className={styles.container}> + This is documentation for <strong>{activeVersionName}</strong>. For the latest released documentation see {' '} + <strong> + <Link to={suggestedDoc.path}>{latestVersionSuggestion.name}</Link> + </strong>. + </div> + )} + </div> + ); +} + +export default DocVersionSuggestions; diff --git a/website/src/theme/DocVersionSuggestions/styles.module.css b/website/src/theme/DocVersionSuggestions/styles.module.css new file mode 100644 index 0000000000..5739a2b5e0 --- /dev/null +++ b/website/src/theme/DocVersionSuggestions/styles.module.css @@ -0,0 +1,3 @@ +:local(.container) { + font-size: 14px; +} diff --git a/website/src/theme/Navbar/index.js b/website/src/theme/Navbar/index.js new file mode 100644 index 0000000000..fe4a85c0f5 --- /dev/null +++ b/website/src/theme/Navbar/index.js @@ -0,0 +1,209 @@ +import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; +import Link from '@docusaurus/Link'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import useHideableNavbar from '@theme/hooks/useHideableNavbar'; +import useLockBodyScroll from '@theme/hooks/useLockBodyScroll'; +import useLogo from '@theme/hooks/useLogo'; +import useThemeContext from '@theme/hooks/useThemeContext'; +import useWindowSize, { windowSizes } from '@theme/hooks/useWindowSize'; +import NavbarItem from '@theme/NavbarItem'; +import SearchBar from '@theme/SearchBar'; +import Toggle from '@theme/Toggle'; +import clsx from 'clsx'; +import React, { useCallback, useEffect, useState } from 'react'; + +import styles from './styles.module.css'; + +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +const DefaultNavItemPosition = 'right'; // If split links by left/right +// if position is unspecified, fallback to right (as v1) + + +let stratosIsNotDocsPage = false; +function stratosUpdateIsNotDocsPage() { + const path = ExecutionEnvironment.canUseDOM ? window.location.pathname : null; + if (path) { + stratosIsNotDocsPage = !path.toLocaleLowerCase().startsWith('/docs'); + } +} + + +function splitNavItemsByPosition(items) { + const leftItems = items.filter( + (item) => (item.position ?? DefaultNavItemPosition) === 'left', + ); + const rightItems = items.filter( + (item) => (item.position ?? DefaultNavItemPosition) === 'right', + ); + return { + leftItems, + rightItems, + }; +} + +function Navbar() { + const { + siteConfig: { + themeConfig: { + navbar: { title = '', items = [], hideOnScroll = false } = {}, + colorMode: { disableSwitch: disableColorModeSwitch = false } = {}, + }, + }, + isClient, + } = useDocusaurusContext(); + const [sidebarShown, setSidebarShown] = useState(false); + const [isSearchBarExpanded, setIsSearchBarExpanded] = useState(false); + const { isDarkTheme, setLightTheme, setDarkTheme } = useThemeContext(); + const { navbarRef, isNavbarVisible } = useHideableNavbar(hideOnScroll); + const { logoLink, logoLinkProps, logoImageUrl, logoAlt } = useLogo(); + useLockBodyScroll(sidebarShown); + const showSidebar = useCallback(() => { + setSidebarShown(true); + }, [setSidebarShown]); + const hideSidebar = useCallback(() => { + setSidebarShown(false); + }, [setSidebarShown]); + const onToggleChange = useCallback( + (e) => (e.target.checked ? setDarkTheme() : setLightTheme()), + [setLightTheme, setDarkTheme], + ); + const windowSize = useWindowSize(); + useEffect(() => { + if (windowSize === windowSizes.desktop) { + setSidebarShown(false); + } + }, [windowSize]); + + useEffect(() => { + stratosUpdateIsNotDocsPage(); + }, []); + + const { leftItems, rightItems } = splitNavItemsByPosition(items); + return ( + <nav + ref={navbarRef} + className={clsx('navbar', 'navbar--light', 'navbar--fixed-top', { + 'navbar-sidebar--show': sidebarShown, + [styles.navbarHideable]: hideOnScroll, + [styles.navbarHidden]: !isNavbarVisible, + })}> + <div className={`navbar__inner ${stratosIsNotDocsPage ? 'stratosIsNotDocsPage' : ''}`}> + <div className="navbar__items"> + {items != null && items.length !== 0 && ( + <div + aria-label="Navigation bar toggle" + className="navbar__toggle" + role="button" + tabIndex={0} + onClick={showSidebar} + onKeyDown={showSidebar}> + <svg + xmlns="http://www.w3.org/2000/svg" + width="30" + height="30" + viewBox="0 0 30 30" + role="img" + focusable="false"> + <title>Menu + + + + )} + + {logoImageUrl != null && ( + {logoAlt} + )} + {title != null && ( + + {title} + + )} + + {leftItems.map((item, i) => ( + + ))} + +
+ {rightItems.map((item, i) => ( + + ))} + {!disableColorModeSwitch && ( + + )} + +
+ +
+
+
+ + {logoImageUrl != null && ( + {logoAlt} + )} + {title != null && ( + {title} + )} + + {!disableColorModeSwitch && sidebarShown && ( + + )} +
+
+
+
    + {items.map((item, i) => ( + + ))} +
+
+
+
+ + ); +} + +export default Navbar; diff --git a/website/src/theme/Navbar/styles.module.css b/website/src/theme/Navbar/styles.module.css new file mode 100644 index 0000000000..fb524ac417 --- /dev/null +++ b/website/src/theme/Navbar/styles.module.css @@ -0,0 +1,26 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +@media screen and (max-width: 997px) { + .displayOnlyInLargeViewport { + display: none !important; + } +} + +@media (max-width: 768px) { + .hideLogoText { + display: none; + } +} + +.navbarHideable { + transition: top 0.2s ease-in-out; +} + +.navbarHidden { + top: calc(var(--ifm-navbar-height) * -1) !important; +} diff --git a/website/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.js b/website/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.js new file mode 100644 index 0000000000..570d9cb773 --- /dev/null +++ b/website/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.js @@ -0,0 +1,81 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { useActiveDocContext, useLatestVersion, useVersions } from '@theme/hooks/useDocs'; +import React from 'react'; + +import versionsWithHash from '../../../internal-versions.json'; +import DefaultNavbarItem from '../../../node_modules/@docusaurus/theme-classic/lib/theme/NavbarItem/DefaultNavbarItem'; + +const versionLabel = (version, nextVersionLabel) => + version.name === 'next' ? nextVersionLabel : version.name; + +const getVersionMainDoc = (version) => + version.docs.find((doc) => doc.id === version.mainDocId); + +const createStratosVersions = (versions) => { + // Remove certain versions from version drop down + const newVersions = []; + const pastVersions = versionsWithHash.map(versionWithHash => versionWithHash.split(':')) + versions.forEach(version => { + const pastVersion = pastVersions.find(pastVersion => pastVersion[0] === version.name) + if (version.name === 'next' || (pastVersion && pastVersion[2] === 'true')) { + newVersions.push(version) + } + }) + return newVersions; +} + +export default function DocsVersionDropdownNavbarItem({ + mobile, + docsPluginId, + nextVersionLabel, + ...props +}) { + const activeDocContext = useActiveDocContext(docsPluginId); + const versions = useVersions(docsPluginId); + const stratosVersions = createStratosVersions(versions) + const latestVersion = useLatestVersion(docsPluginId); + const items = stratosVersions.map((version) => { + // We try to link to the same doc, in another version + // When not possible, fallback to the "main doc" of the version + const versionDoc = + activeDocContext?.alternateDocVersions[version.name] || + getVersionMainDoc(version); + return { + isNavLink: true, + label: versionLabel(version, nextVersionLabel), + to: versionDoc.path, + isActive: () => version === activeDocContext?.activeVersion, + }; + }); + + // Stratos - Add the 'All Versions' link to the versions drop down + items.push({ + isNavLink: true, + label: "All Versions", + to: "/versions", + isActive: () => false, + }) + + const dropdownVersion = activeDocContext.activeVersion ?? latestVersion; // Mobile is handled a bit differently + + const dropdownLabel = mobile + ? 'Versions' + : versionLabel(dropdownVersion, nextVersionLabel); + const dropdownTo = mobile + ? undefined + : getVersionMainDoc(dropdownVersion).path; + return ( + + ); +} From 5bfd091f8253bc173322c7f3627c2f2f728585a8 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 15 Sep 2020 16:19:34 +0100 Subject: [PATCH 619/648] Fix db lock issue (#479) * Fix db locking issue * Fix --- src/jetstream/plugins/monocular/main.go | 3 +-- .../plugins/monocular/store/chart_store_db.go | 22 +++++++++++++++---- .../plugins/monocular/sync_worker.go | 10 ++++++++- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/jetstream/plugins/monocular/main.go b/src/jetstream/plugins/monocular/main.go index c2746a13bf..b5c46f2e58 100644 --- a/src/jetstream/plugins/monocular/main.go +++ b/src/jetstream/plugins/monocular/main.go @@ -104,8 +104,7 @@ func (m *Monocular) syncOnStartup() { for _, ep := range endpoints { if ep.CNSIType == helmEndpointType { if ep.SubType == helmRepoEndpointType { - helmRepos[ep.Name] = true - log.Infof("Syncing helm repository to chart store: %s", ep.Name) + helmRepos[ep.GUID] = true m.Sync(interfaces.EndpointRegisterAction, ep) } else { metadata := "{}" diff --git a/src/jetstream/plugins/monocular/store/chart_store_db.go b/src/jetstream/plugins/monocular/store/chart_store_db.go index c21a7d5787..5fd0c92a2b 100644 --- a/src/jetstream/plugins/monocular/store/chart_store_db.go +++ b/src/jetstream/plugins/monocular/store/chart_store_db.go @@ -11,6 +11,7 @@ import ( var ( saveChartVersion = `INSERT INTO helm_charts (endpoint, name, repo_name, version, created, app_version, description, icon_url, chart_url, source_url, digest, is_latest, update_batch) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)` + updateChartVersion = `UPDATE helm_charts SET created=$1, app_version=$2, description=$3, icon_url=$4, chart_url=$5, source_url=$6, digest=$7, is_latest=$8, update_batch=$9 WHERE endpoint=$10 AND name=$11 AND repo_name=$12 AND version=$13` deleteChartVersion = `DELETE FROM helm_charts WHERE endpoint = $1 AND name = $2 and version = $3` deleteForEndpoint = `DELETE FROM helm_charts WHERE endpoint = $1` deleteForBatch = `DELETE FROM helm_charts WHERE endpoint = $1 AND name = $2 and update_batch != $3` @@ -20,12 +21,15 @@ var ( getChartVersion = `SELECT endpoint, name, repo_name, version, created, app_version, description, icon_url, chart_url, source_url, digest, is_latest FROM helm_charts WHERE repo_name = $1 AND name = $2 AND version = $3` getChartVersions = `SELECT endpoint, name, repo_name, version, created, app_version, description, icon_url, chart_url, source_url, digest, is_latest FROM helm_charts WHERE repo_name = $1 AND name = $2` getEndpointIDs = `SELECT DISTINCT endpoint FROM helm_charts` + updateChartDigest = `UPDATE helm_charts SET is_latest=$1, update_batch=$2 WHERE endpoint=$3 AND name=$4 AND repo_name=$5 AND version=$6` ) // InitRepositoryProvider - One time init for the given DB Provider func InitRepositoryProvider(databaseProvider string) { saveChartVersion = datastore.ModifySQLStatement(saveChartVersion, databaseProvider) - deleteChartVersion = datastore.ModifySQLStatement(deleteChartVersion, databaseProvider) + updateChartVersion = datastore.ModifySQLStatement(updateChartVersion, databaseProvider) + updateChartVersion = datastore.ModifySQLStatement(updateChartVersion, databaseProvider) + updateChartDigest = datastore.ModifySQLStatement(updateChartDigest, databaseProvider) deleteForEndpoint = datastore.ModifySQLStatement(deleteForEndpoint, databaseProvider) deleteForBatch = datastore.ModifySQLStatement(deleteForBatch, databaseProvider) renameEndpoint = datastore.ModifySQLStatement(renameEndpoint, databaseProvider) @@ -49,14 +53,24 @@ func NewHelmChartDBStore(dcp *sql.DB) (ChartStore, error) { // Save a Helm Chart to the database func (p *HelmChartDBStore) Save(chart ChartStoreRecord, batchID string) error { - // Delete the existing record - p.db.Exec(deleteChartVersion, chart.EndpointID, chart.Name, chart.Version) - sourceURL := "" if len(chart.Sources) > 0 { sourceURL = chart.Sources[0] } + // Get the existing record - if it has the same digest, then no need to store it + record, err := p.GetChart(chart.Repository, chart.Name, chart.Version) + if err == nil && record.Digest == chart.Digest { + _, err := p.db.Exec(updateChartDigest, chart.IsLatest, batchID, chart.EndpointID, chart.Name, chart.Repository, chart.Version) + return err + } + + if err == nil { + // The record already exists, so update it + _, err := p.db.Exec(updateChartVersion, chart.Created, chart.AppVersion, chart.Description, chart.IconURL, chart.ChartURL, sourceURL, chart.Digest, chart.IsLatest, batchID, chart.EndpointID, chart.Name, chart.Repository, chart.Version) + return err + } + if _, err := p.db.Exec(saveChartVersion, chart.EndpointID, chart.Name, chart.Repository, chart.Version, chart.Created, chart.AppVersion, chart.Description, chart.IconURL, chart.ChartURL, sourceURL, chart.Digest, chart.IsLatest, batchID); err != nil { return fmt.Errorf("Unable to save Helm Chart Version: %v", err) } diff --git a/src/jetstream/plugins/monocular/sync_worker.go b/src/jetstream/plugins/monocular/sync_worker.go index e168d2c611..962c899669 100644 --- a/src/jetstream/plugins/monocular/sync_worker.go +++ b/src/jetstream/plugins/monocular/sync_worker.go @@ -3,6 +3,7 @@ package monocular import ( "fmt" "strings" + "time" yaml "gopkg.in/yaml.v2" @@ -52,6 +53,9 @@ func (m *Monocular) syncHelmRepository(endpointID, repoName, url string) error { var latestCharts []store.ChartStoreRecord var allCharts []store.ChartStoreRecord + log.Infof("Helm Repository sync started for %s", repoName) + start := time.Now() + // Iterate over each chart in the index for name, chartVersions := range index.Entries { log.Debugf("Helm Repository Sync: Processing chart: %s", name) @@ -70,7 +74,8 @@ func (m *Monocular) syncHelmRepository(endpointID, repoName, url string) error { log.Errorf("%s", err) } - log.Infof("Sync completed for %s", repoName) + elapsed := time.Since(start).Round(time.Second) + log.Infof("Helm Repository sync completed for %s (%s)", repoName, elapsed) return nil } @@ -127,6 +132,9 @@ func (m *Monocular) procesChartVersions(endpoint, repoName, name string, chartVe if err := m.ChartStore.Save(record, batchID); err != nil { log.Warnf("Error saving Chart %s, Version %s to the database: %+v", record.Name, record.Version, err) } + + // Small delay mainly for SQLite so we don't hog the database connection + time.Sleep(2 * time.Millisecond) } } From ae76bbe06795ad89787cd38e378933c888183ded Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 15 Sep 2020 19:30:55 +0100 Subject: [PATCH 620/648] Merge upstream, also add 4.1.0 version & change log (#480) * Ensure filename/no filename text in connect by file dialog is aligned (#4474) - needs porting back * Add typed entity access and `custom-src` removal to change log (#4496) * Fixes #4335: Create Stratos static web site with better documentation (#4446) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * * Review comments fix * Fix broken link to an image in frontend extensions doc * Final tweaks Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Richard Cox Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Richard Cox * Fix and update customization docs (#4478) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * Apply new theming process to acme example * First pass at customization docs, most of themeing done * Final pass at customization docs * Merge fixes Co-authored-by: Veerapuram Varadhan Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update base README, point to website (#4488) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * Apply new theming process to acme example * First pass at customization docs, most of themeing done * Final pass at customization docs * Update base README, point to website Co-authored-by: Veerapuram Varadhan Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update developer docs (#4489) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * Apply new theming process to acme example * First pass at customization docs, most of themeing done * Final pass at customization docs * Update base README, point to website * Update developer docs Still need an overhall, but updated to remove incorrect data and apply website context Co-authored-by: Veerapuram Varadhan Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix docs (#4497) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * Apply new theming process to acme example * First pass at customization docs, most of themeing done * Final pass at customization docs * Update base README, point to website * Update developer docs Still need an overhall, but updated to remove incorrect data and apply website context * Fix PR template Co-authored-by: Veerapuram Varadhan Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Ensure images dependent on techPreview flag are included in imagelist.txt (#4500) * Improve clean-symlinks script (#4509) * Add support for including breaking changes in the changelog * Improve clean-symlinks script * Update changelog.sh * Update changelog.sh * Remove trailing line * Remove tailing line * Change nginx ciphers and make configurable via helm chart values (#4507) * Change nginx ciphers and make configurable via helm chart values * Add support for including breaking changes in the changelog * Improve clean-symlinks script * Update changelog.sh * Update changelog.sh * Remove trailing line * Remove tailing line * Fix bug with cert path patching * Fig bug where protocols not patched * Update version to 4.0.1, add change log (#4514) * Update version to 4.0.1, add change log * Update package-lock * Fix deploy from gitlab using a group's repo (#4479) - only supported user based repos - fixes #4153 * Add additional time ranges to base metrics range selector (#4480) - fixes https://github.com/SUSE/stratos/issues/415 * Add some time saving comments to cf permissions checker (#4508) * Move tab-nav and xsrf module source files to the src folder (#4518) * Move tab-nav files * Move xsrf module * Fix GitHub branch limit (#4510) * Ensure we fetch all repo's and branches when deploying github apps * Ensure we only fetch branches once - SUSE/stratos vs suse/stratos - ensure branches are treated as 'local' lists * Increate page size for github commits and gitlab requests * Remove use of nodejs util module (#4521) * Remove use of nodejs util module * Fix comment typo * Remove dependencies between store and core that have crept in (#4517) * Remove dependencies between store and core that have crept in * Fix issue with unit tests * Import fixes and unit test fix * Fix store testing package (#4520) * Fix store testing package * Update public-api.ts Co-authored-by: Richard Cox Co-authored-by: Richard Cox * Fix the position of the header guide array (#4524) - shown when there are no registered endpoints (and some incoming scenarios) - position is determined by location of associated button in header - position of button can change given visibility of other buttons (notification bell, endpoint backup, etc) - now check positiion after a delay, add fade in to mask delay * Metrics: Ensure trailing slashes are ignored when comparing URLs (#4527) * Remove imports of the form 'frontend/....' (#4519) * Move tab-nav files * Move xsrf module * Remove imports of the form 'frontend/...' * Move endpopints-health-check.ts (#4530) * Add UMD Ids to external modules in store package (#4522) * Add UMB Ids to external modules in store package * Add moment * Address PR feedback * Improvements & more checks to autoscaler scheduled date tests (#4535) - allow more time to skip between date and time values in date fields - add a check to - add checks to ensure we have the correct number of scheduled date rules * Remove api driven views (#4537) * Remove logger service and action history (#4538) * Remove logger service * Missed a few and removed action history as it was not being used * Remove action history * A few more * Fix for removed method * Add support for API keys (#4515) * Add backend support for API keys * Add last_used field to API keys * Use secure random value as key secret * Add tests for ListAPIKeys and AddAPIKey * Cover the rest of psql_apikeys.go with tests * Refactor the code a bit * Storing SQL queries in a struct should ensure that `datastore.ModifySQLStatement` gets called on all of them. * A wrapper func around `db.Exec` reduces copypasta. * Actually call `InitRepositoryProvider` for API keys package * Add route handler tests using gomock * Actually use skipper in xsrfMiddleware; minor clean-up * Update moment imports to remove warning when building library (#4534) * Update moment imports to remove warning when building library * Fix references to moment-timezone and markdown - contains some changes to e2e tests as well, might need to be reverted * Fix unit test * Fix `import *` in e2e tests Co-authored-by: Richard Cox * Fix Helm upgrade bug (#4544) * Fix Helm upgrade bug * Fix unit tests * Store api_keys.last_used in UTC (#4541) * Add UI for Stratos API Keys (#4523) * Add backend support for API keys * Add last_used field to API keys * Add base api keys page * Add basic api key entity framework (untested) * Add a basic api keys list (untested, need to wire in properties/columns + actions) * Fix entity type related issues * Add basic way to create api key * Wire in delete to list * Improve 'no api keys' ux * Final tidy up * Other fixes * Fix unit tests * Add 'Last Used' column to API keys list * Don't flash up 'no entries' when we haven't loaded api keys yet * Fix last used sorting - takes into account timezone - use cacheing to cater for often called sort * Fix unit test after changes in master * Fix after moment change * Remove now unrequired sorting of api key last used date via moment Co-authored-by: Ivan Kapelyukhin * Add basic e2e tests for API Keys (#4536) * Add backend support for API keys * Add last_used field to API keys * Add base api keys page * Add basic api key entity framework (untested) * Add a basic api keys list (untested, need to wire in properties/columns + actions) * Fix entity type related issues * Add basic way to create api key * Wire in delete to list * Improve 'no api keys' ux * Final tidy up * Other fixes * Fix unit tests * Add basic e2e tests for api keys * Add 'Last Used' column to API keys list * Don't flash up 'no entries' when we haven't loaded api keys yet * Fix last used sorting - takes into account timezone - use cacheing to cater for often called sort * Fix unit test after changes in master * Fix after moment change * Beef up test, fix for initial empty state * Remove now unrequired sorting of api key last used date via moment Co-authored-by: Ivan Kapelyukhin * Bump docusaurus version and add support for versioning (#4506) * Bump docusaurus version and versioning - Bump docusaurus to latest 2.0 version - Fix errors thrown up by new linting process - Add version support, setup 4.0.0 - Enable dark mode and fix in home screen * Move status_updates back into root project docs folder * Fix links - fix dead links - ensure all links are relative (so work when versioned) * Remove version 4.0.0 * WIP * WIP * Completed * Temporarily remove docsVersionDropdown until first version added to internal-versions * Update website docs * Build fix for temp situation where there's no versions * Add custom version of 'not latest released docs' message * Improve versioning and dark mode toggle - Add `All Versions page` - Conditionally include versions in drop down - Improve dark mode toggle icons * Temporarily remove dependent on versions - will be added back in when 4.0 sha is known * Update Versions Process * Add 4.0.0 * Fix in `build`/`serve` world - worked fine in `start` world... * Update 4.0.0 with temp version * Multiple improvements - Fix clean git repo before run - Swizzle docs version drop down nav bar item, move in custom code - Improve docs (latest version must appear in drop down) * Changes following review * Reduce font in version warning * API Keys: Make feature configurable for different user types (#4540) * Add a special type and parsing for the new config option * Add APIKeysEnabled to /info, check it in the middleware * Add test coverage for apiKeyMiddleware * Block API keys endpoints if disabled in the config; add tests * Fix failing test * Add API_KEYS_ENABLED to the Helm chart * Add JSON schema to the Helm chart * Add docs for UAA SSO user permissions management (#4554) * Bump docusaurus version and versioning - Bump docusaurus to latest 2.0 version - Fix errors thrown up by new linting process - Add version support, setup 4.0.0 - Enable dark mode and fix in home screen * Move status_updates back into root project docs folder * Fix links - fix dead links - ensure all links are relative (so work when versioned) * Remove version 4.0.0 * WIP * WIP * Completed * Temporarily remove docsVersionDropdown until first version added to internal-versions * Update website docs * Build fix for temp situation where there's no versions * Add custom version of 'not latest released docs' message * Improve versioning and dark mode toggle - Add `All Versions page` - Conditionally include versions in drop down - Improve dark mode toggle icons * Temporarily remove dependent on versions - will be added back in when 4.0 sha is known * Update Versions Process * Add 4.0.0 * Fix in `build`/`serve` world - worked fine in `start` world... * Update 4.0.0 with temp version * Add troubleshooting section for SSO * Update SSO auth troubleshooting section * Add basic developers guide for working with helm (#4511) * Bump docusaurus version and versioning - Bump docusaurus to latest 2.0 version - Fix errors thrown up by new linting process - Add version support, setup 4.0.0 - Enable dark mode and fix in home screen * Move status_updates back into root project docs folder * Fix links - fix dead links - ensure all links are relative (so work when versioned) * Add basic developers guide for working with helm * Fix post merge issue * Enable/disable API keys UI given API keys config setting (#4559) * Add a special type and parsing for the new config option * Add APIKeysEnabled to /info, check it in the middleware * Add test coverage for apiKeyMiddleware * Block API keys endpoints if disabled in the config; add tests * Fix failing test * Add API_KEYS_ENABLED to the Helm chart * Add JSON schema to the Helm chart * Enable/disable API keys UI given API keys config setting * Changes following review * Fix unit tests Co-authored-by: Ivan Kapelyukhin * Enable linting for all packages (#4561) * Enable linting for all packages - lots to solve! - we may wish to exclude some files * Fix linting * Bump angular json schema form (#4564) * Bump angular json schema form * Handle bug where mat-error maring is too big - We may want to consider moving this upstream Co-authored-by: Richard Cox * Docs: Update internal versions & Automate future updates (#4558) * Docs: Update where 4.0.0 is built from, add 4.0.1 - means we can delete the old `docs-versioning` branch - both versions point to the same commit (when branch merged to master), but reduces user confusion when they can't find the docs for the latest released version. Going forward this will point directly to the release label * Automatically update website's internal-versions.json on new releases - Add new github action - Action will trigger on a release being published - Action looks at github `release` object associated with event and extracts a version label and git label - Use extracted values adds new value to internal-versions.json - Creates a PR with the change * Fix checkout issue * Update documentation-versioning.yml * Fix nightly tage reference and names (#4574) * [Security] Bump node-fetch from 2.6.0 to 2.6.1 (#4572) Bumps [node-fetch](https://github.com/bitinn/node-fetch) from 2.6.0 to 2.6.1. **This update includes a security fix.** - [Release notes](https://github.com/bitinn/node-fetch/releases) - [Changelog](https://github.com/node-fetch/node-fetch/blob/master/docs/CHANGELOG.md) - [Commits](https://github.com/bitinn/node-fetch/compare/v2.6.0...v2.6.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Merge changes from downstream (#4567) - json schema form mat-error styling fix - info & border style colors - desktop style fixes - changes to breadcrumbs - sidenav dark mode fix - new functionality for card-number-metric component - filter endpoint by type - more info in stepper onNext fn - SessionService with isTechPreview - Add reset pagination of type action/reducer - Fix cf endpoint sort by type bug * Reduce sizes for stepper padding and dialog content on desktop (#4573) * Moer style tweaks * Update mat-desktop.scss Co-authored-by: Richard Cox * Optionally reduce size of tile selector cards, apply to create endpoints and deploy app (#4571) * Improve tile selector sizing - Reduce width of tile by tweaking tile grid breakpoints - Switch down to 1 column later on when reducing width - Add intermediat 3 column step to reduce times when 2 columns are too large - Reduce height of tiles by about a 1/4 * Shrink size of github, git url and docker images in create app stepper * Helm Chart: Add NOTES.txt template & update chart.yaml (#4557) * Helm Chart Updates - Add NOTES template - Add maintainers and keywords to chart.yaml * Update helm install instruction in build.sh script to helm 3 * Helm Chart Notes Fixes/Updates * Changes following review * Changes following review * Address PR comments, fix whitespacing * Update versions & Changelog * Use smaller tiles for register endpoint stepper * Update CHANGELOG.md Co-authored-by: Veerapuram Varadhan Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Veerapuram Varadhan Co-authored-by: Ivan Kapelyukhin --- .cfignore | 2 +- CHANGELOG.md | 35 + deploy/ci/console-nightly-releases.yml | 10 +- .../dev-releases/nightly-release-description | 2 +- .../tasks/dev-releases/nightly-release-name | 2 +- deploy/kubernetes/build.sh | 2 +- deploy/kubernetes/console/Chart.yaml | 13 +- deploy/kubernetes/console/README.md | 2 +- deploy/kubernetes/console/templates/NOTES.txt | 31 + package-lock.json | 1125 ++++++++++++++++- package.json | 2 +- .../deploy-application-steps.types.ts | 11 +- .../new-application-base-step.component.html | 3 +- .../packages/core/sass/mat-desktop.scss | 16 + .../create-endpoint-base-step.component.html | 3 +- .../tile-selector-tile.component.html | 27 +- .../tile-selector-tile.component.scss | 126 +- .../tile-selector-tile.component.ts | 9 +- .../tile-selector.component.html | 4 +- .../tile-selector.component.scss | 16 +- .../tile-selector/tile-selector.component.ts | 1 + .../components/tile/tile-selector.types.ts | 1 + website/docs/extensions/theming.md | 8 +- 23 files changed, 1314 insertions(+), 137 deletions(-) create mode 100644 deploy/kubernetes/console/templates/NOTES.txt diff --git a/.cfignore b/.cfignore index 4775f324c9..f58e7a9d67 100644 --- a/.cfignore +++ b/.cfignore @@ -22,4 +22,4 @@ docs/ build/dev_config.json e2e-reports/ website/ -.helm-cache/ \ No newline at end of file +.helm-cache/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 23026997c3..a2916e1e66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,40 @@ # Change Log +## 4.1.0 + +[Full Changelog](https://github.com/SUSE/stratos/compare/4.0.1...4.1.0) + +This release contains a number of fixes and improvements: + +**Improvements:** + +- Helm: Add support for JSON Schema when inputting values [\#447](https://github.com/SUSE/stratos/issues/447) +- Helm: Improve chart values YAML editor [\#481](https://github.com/SUSE/stratos/issues/481) +- Helm: Support helm upgrade of a workload [\#446](https://github.com/SUSE/stratos/issues/446) +- Helm: Allow users to view and use repositories from Helm Hub [\#445](https://github.com/SUSE/stratos/issues/445) +- Add support for API Keys [\#4504](https://github.com/cloudfoundry/stratos/issues/4504) +- Workload List: Show Chart Version instead of Release Version [\#473](https://github.com/SUSE/stratos/issues/473) +- Helm: Allow users to view revision history for a Helm Release [\#460](https://github.com/SUSE/stratos/issues/460) +- Kubernetes Terminal: It can take a long time to get to the prompt with large helm repositories registered [\#452](https://github.com/SUSE/stratos/issues/452) +- Show CaaSP Node version and whether updates are pending on the summary view [\#450](https://github.com/SUSE/stratos/issues/450) +- Update SUSE login screen to latest EOS design [\#448](https://github.com/SUSE/stratos/issues/448) +- Toggle live updates in workloads view [\#442](https://github.com/SUSE/stratos/issues/442) +- Container List in Pods List Improvements [\#360](https://github.com/SUSE/stratos/issues/360) +- Register Endpoint: Reduce size of cards [\#4568](https://github.com/cloudfoundry/stratos/issues/4568) +- Metrics view: Add "The last day" to time range selector [\#4516](https://github.com/cloudfoundry/stratos/issues/4516) + + +**Fixes:** + +- Helm Install: Install button becomes enabled before namespace supplied [\#471](https://github.com/SUSE/stratos/issues/471) +- Helm Chart: Charts view incorrectly shows development versions as the latest versions [\#455](https://github.com/SUSE/stratos/issues/455) +- Sort endpoint table by type results in empty sort drop down in cf endpoints list [\#4565](https://github.com/cloudfoundry/stratos/issues/4565) +- Endpoint unregister clears user sort & filter selection [\#4563](https://github.com/cloudfoundry/stratos/issues/4563) +- Metrics: Metrics detail page can show two endpoint cards if URLs have trailing slash [\#4528](https://github.com/cloudfoundry/stratos/issues/4528) +- Deploy from Gitlab: Repos in groups do not work [\#4153](https://github.com/cloudfoundry/stratos/issues/4153) +- Deploy app from Github repo only shows branches from a-e in dropdown [\#3966](https://github.com/cloudfoundry/stratos/issues/3966) + + ## 4.0.1 [Full Changelog](https://github.com/SUSE/stratos/compare/4.0.0...4.0.1) diff --git a/deploy/ci/console-nightly-releases.yml b/deploy/ci/console-nightly-releases.yml index 68751c0160..03d7a82c4e 100644 --- a/deploy/ci/console-nightly-releases.yml +++ b/deploy/ci/console-nightly-releases.yml @@ -106,25 +106,25 @@ jobs: dockerfile: stratos/deploy/Dockerfile.bk build: stratos/ target_name: prod-build - tag: stratos/deploy/ci/tasks/dev-releases/nightly-tag + tag: stratos/deploy/ci/tasks/build-images/nightly-tag build_args_file: image-tag/build-args - put: mariadb-image params: dockerfile: stratos/deploy/db/Dockerfile.mariadb build: stratos/deploy/db - tag: stratos/deploy/ci/tasks/dev-releases/nightly-tag + tag: stratos/deploy/ci/tasks/build-images/nightly-tag - do: - put: config-init-image params: dockerfile: stratos/deploy/Dockerfile.init build: stratos/ - tag: stratos/deploy/ci/tasks/dev-releases/nightly-tag + tag: stratos/deploy/ci/tasks/build-images/nightly-tag - put: ui-image params: dockerfile: stratos/deploy/Dockerfile.ui build: stratos/ target_name: prod-build - tag: stratos/deploy/ci/tasks/dev-releases/nightly-tag + tag: stratos/deploy/ci/tasks/build-images/nightly-tag prebuild_script: build/store-git-metadata.sh - name: create-chart plan: @@ -153,7 +153,7 @@ jobs: - put: nightly-gh-release params: name: stratos/deploy/ci/tasks/dev-releases/nightly-release-name - tag: stratos/deploy/ci/tasks/dev-releases/nightly-tag + tag: stratos/deploy/ci/tasks/build-images/nightly-tag body: stratos/deploy/ci/tasks/dev-releases/nightly-release-description globs: - helm-chart-tarball/*.tgz diff --git a/deploy/ci/tasks/dev-releases/nightly-release-description b/deploy/ci/tasks/dev-releases/nightly-release-description index 10f5c521b9..f7b1480f6d 100644 --- a/deploy/ci/tasks/dev-releases/nightly-release-description +++ b/deploy/ci/tasks/dev-releases/nightly-release-description @@ -1 +1 @@ -This is an alpha release of Stratos v2 \ No newline at end of file +Nightly Stratos build \ No newline at end of file diff --git a/deploy/ci/tasks/dev-releases/nightly-release-name b/deploy/ci/tasks/dev-releases/nightly-release-name index ae784554be..fc7a445a46 100644 --- a/deploy/ci/tasks/dev-releases/nightly-release-name +++ b/deploy/ci/tasks/dev-releases/nightly-release-name @@ -1 +1 @@ -Stratos 3.0.0 Nightly Release \ No newline at end of file +Stratos Nightly Release \ No newline at end of file diff --git a/deploy/kubernetes/build.sh b/deploy/kubernetes/build.sh index f838cc3549..4dc645a607 100755 --- a/deploy/kubernetes/build.sh +++ b/deploy/kubernetes/build.sh @@ -296,5 +296,5 @@ printf "${RESET}" echo echo "To deploy using Helm, execute the following:" echo -echo " helm install helm-chart --namespace console --name my-console" +echo " helm install my-console ./helm-chart --namespace console" echo diff --git a/deploy/kubernetes/console/Chart.yaml b/deploy/kubernetes/console/Chart.yaml index 22538c426e..aaea5f78e9 100644 --- a/deploy/kubernetes/console/Chart.yaml +++ b/deploy/kubernetes/console/Chart.yaml @@ -1,8 +1,17 @@ apiVersion: v1 -description: A Helm chart for deploying Stratos UI Console +description: A Helm chart for deploying Stratos name: console version: 0.1.0 appVersion: 0.1.0 sources: - https://github.com/cloudfoundry/stratos -icon: https://raw.githubusercontent.com/cloudfoundry/stratos/master/deploy/kubernetes/console/icon.png \ No newline at end of file +icon: https://raw.githubusercontent.com/cloudfoundry/stratos/master/deploy/kubernetes/console/icon.png +home: https://stratos.app +maintainers: + - name: Stratos Maintainers + email: stratos-maintainers@suse.de +keywords: + - Stratos + - "Cloud Foundry" + - Kubernetes + - Helm \ No newline at end of file diff --git a/deploy/kubernetes/console/README.md b/deploy/kubernetes/console/README.md index ee238aa880..cadd5c7d49 100644 --- a/deploy/kubernetes/console/README.md +++ b/deploy/kubernetes/console/README.md @@ -22,7 +22,7 @@ Check the repository was successfully added by searching for the `console`, for ``` helm search repo console NAME CHART VERSION APP VERSION DESCRIPTION -stratos/console 4.0.1 4.0.1 A Helm chart for deploying Stratos UI Console +stratos/console 4.1.0 4.1.0 A Helm chart for deploying Stratos UI Console ``` > Note: Version numbers will depend on the version of Stratos available from the Helm repository diff --git a/deploy/kubernetes/console/templates/NOTES.txt b/deploy/kubernetes/console/templates/NOTES.txt new file mode 100644 index 0000000000..0d005a46fd --- /dev/null +++ b/deploy/kubernetes/console/templates/NOTES.txt @@ -0,0 +1,31 @@ +{{- if .Values.console.techPreview }} +Tech Preview is enabled, extra features will be shown. +{{- end}} + +To access Stratos: +{{- $ingress := .Values.console.ingress | default dict }} +{{- if $ingress.enabled }} +From outside the cluster, the server URL is: http://{{ .Values.console.ingress.host }} +{{- else }} +Get the URL by running these commands in the same shell: +{{- if contains "NodePort" .Values.console.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services console-ui-ext) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo https://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.console.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get svc --namespace {{ .Release.Namespace }} -w console-ui-ext' + + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} console-ui-ext -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + echo http://$SERVICE_IP:{{ .Values.console.service.servicePort }} +{{- else if contains "ClusterIP" .Values.console.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app=stratos-0,component=ui" -o jsonpath="{.items[0].metadata.name}") + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 443 +{{- end }} +{{- end }} + +To learn more about the release, try: + $ helm status {{ .Release.Name }} -n {{ .Release.Namespace }} + $ helm get values {{ .Release.Name }} -n {{ .Release.Namespace }} + $ kubectl get services -n {{ .Release.Namespace }} + $ kubectl get pods -n {{ .Release.Namespace }} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6ae8d5b767..e9babc88f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3331,6 +3331,916 @@ "@babel/types": "^7.11.0" } }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", + "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz", + "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz", + "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz", + "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz", + "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz", + "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==", + "dev": true, + "requires": { + "regenerator-transform": "^0.14.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", + "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz", + "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz", + "integrity": "sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", + "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-regex": "^7.10.4" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz", + "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz", + "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz", + "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/preset-env": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.0.tgz", + "integrity": "sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.9.0", + "@babel/helper-compilation-targets": "^7.8.7", + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-proposal-async-generator-functions": "^7.8.3", + "@babel/plugin-proposal-dynamic-import": "^7.8.3", + "@babel/plugin-proposal-json-strings": "^7.8.3", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-proposal-numeric-separator": "^7.8.3", + "@babel/plugin-proposal-object-rest-spread": "^7.9.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", + "@babel/plugin-proposal-optional-chaining": "^7.9.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", + "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", + "@babel/plugin-syntax-numeric-separator": "^7.8.0", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0", + "@babel/plugin-syntax-top-level-await": "^7.8.3", + "@babel/plugin-transform-arrow-functions": "^7.8.3", + "@babel/plugin-transform-async-to-generator": "^7.8.3", + "@babel/plugin-transform-block-scoped-functions": "^7.8.3", + "@babel/plugin-transform-block-scoping": "^7.8.3", + "@babel/plugin-transform-classes": "^7.9.0", + "@babel/plugin-transform-computed-properties": "^7.8.3", + "@babel/plugin-transform-destructuring": "^7.8.3", + "@babel/plugin-transform-dotall-regex": "^7.8.3", + "@babel/plugin-transform-duplicate-keys": "^7.8.3", + "@babel/plugin-transform-exponentiation-operator": "^7.8.3", + "@babel/plugin-transform-for-of": "^7.9.0", + "@babel/plugin-transform-function-name": "^7.8.3", + "@babel/plugin-transform-literals": "^7.8.3", + "@babel/plugin-transform-member-expression-literals": "^7.8.3", + "@babel/plugin-transform-modules-amd": "^7.9.0", + "@babel/plugin-transform-modules-commonjs": "^7.9.0", + "@babel/plugin-transform-modules-systemjs": "^7.9.0", + "@babel/plugin-transform-modules-umd": "^7.9.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", + "@babel/plugin-transform-new-target": "^7.8.3", + "@babel/plugin-transform-object-super": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.8.7", + "@babel/plugin-transform-property-literals": "^7.8.3", + "@babel/plugin-transform-regenerator": "^7.8.7", + "@babel/plugin-transform-reserved-words": "^7.8.3", + "@babel/plugin-transform-shorthand-properties": "^7.8.3", + "@babel/plugin-transform-spread": "^7.8.3", + "@babel/plugin-transform-sticky-regex": "^7.8.3", + "@babel/plugin-transform-template-literals": "^7.8.3", + "@babel/plugin-transform-typeof-symbol": "^7.8.4", + "@babel/plugin-transform-unicode-regex": "^7.8.3", + "@babel/preset-modules": "^0.1.3", + "@babel/types": "^7.9.0", + "browserslist": "^4.9.1", + "core-js-compat": "^3.6.2", + "invariant": "^2.2.2", + "levenary": "^1.1.1", + "semver": "^5.5.0" + } + }, + "@babel/preset-modules": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", + "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/runtime": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz", + "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "@cfstratos/ajsf-core": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@cfstratos/ajsf-core/-/ajsf-core-0.1.6.tgz", + "integrity": "sha512-G47ZAvn7ynuUkB2XzeewxhDB9yB5EYcBWfmBYB6FeEkFzX9PpcaCm8QK72IO6Kis+HjAvO5kwqiud9TXUPhMNw==", + "requires": { + "ajv": "^6.10.0", + "lodash-es": "^4.17.15" + } + }, + "@cfstratos/ajsf-material": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@cfstratos/ajsf-material/-/ajsf-material-0.1.6.tgz", + "integrity": "sha512-dUV2aqwQoRCaedNh31ZVZUgDhWqdiJnikag/Nws5h4fFs6sdmL2sS8A18e6onLA8t9t4n0y8PEib6qgxqr3ezw==", + "requires": { + "@cfstratos/ajsf-core": "~0.1.5", + "lodash-es": "^4.17.15" + } + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, "@babel/highlight": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", @@ -3375,22 +4285,76 @@ "globals": "^11.1.0", "lodash": "^4.17.19" } + } + } + }, + "@jsdevtools/ono": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.2.tgz", + "integrity": "sha512-qS/a24RA5FEoiJS9wiv6Pwg2c/kiUo3IVUQcfeM9JvsR6pM8Yx+yl/6xWYLckZCT5jpLNhslgjiA8p/XcGyMRQ==", + "dev": true + }, + "@ngrx/effects": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-9.2.0.tgz", + "integrity": "sha512-8V09zDIPehGpzgfcgyczelovsVYJvDQhN9wHt37K5A+YCG0CI8nj8FmKokHATwv/S62YqFrOVnr/TZacxpDhBw==" + }, + "@ngrx/router-store": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@ngrx/router-store/-/router-store-9.2.0.tgz", + "integrity": "sha512-thu6aU9YWM64oNEk4Srx/mNSeQ2SPJKlTji8MSzfr06qgCMyPSXZBYlfs8HqY+af3eB7XBEhb/4ew4JJ6xC9zw==" + }, + "@ngrx/store": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-9.2.0.tgz", + "integrity": "sha512-V8AI3mxbMztVpbZpALkLZYlGkofKcu9GaOCY5e+sZ1VcJ90oxhFjBpnmd6MuVdmhep1XAHALb1B8ZbBFn+xsgQ==" + }, + "@ngrx/store-devtools": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-9.2.0.tgz", + "integrity": "sha512-/FvgcpjO4IvwNFnRVoHGikAvckr6fxKf4NgYoTQ9giI8xavolLvuQUHxzH20legi5dgZz34ii2m2g1Q7OxEV8w==" + }, + "@ngtools/webpack": { + "version": "9.1.12", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-9.1.12.tgz", + "integrity": "sha512-lypMXIq5oxBMsoDu/VOa1yUmmXthhxkCJa8LG0ZohfnbwhmZvz3SAW7omBGuVrb5cVIfLCkaRCSnQ1MNc6ULXw==", + "dev": true, + "requires": { + "@angular-devkit/core": "9.1.12", + "enhanced-resolve": "4.1.1", + "rxjs": "6.5.4", + "webpack-sources": "1.4.3" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "9.1.12", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.1.12.tgz", + "integrity": "sha512-D/GnBeSlmdgGn7EhuE32HuPuRAjvUuxi7Q6WywBI8PSsXKAGnrypghBwMATNnOA24//CgbW2533Y9VWHaeXdeA==", + "dev": true, + "requires": { + "ajv": "6.12.3", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.5.4", + "source-map": "0.7.3" + } }, - "@babel/types": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", - "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "ajv": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "rxjs": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", + "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", "dev": true, "requires": { "ms": "^2.1.1" @@ -3404,10 +4368,53 @@ } } }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", - "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", + "@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "^1.6.0" + } + }, + "@npmcli/move-file": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", + "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } + } + }, + "@rollup/plugin-commonjs": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz", + "integrity": "sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA==", "dev": true, "requires": { "@babel/helper-create-regexp-features-plugin": "^7.10.4" @@ -3634,10 +4641,44 @@ "regenerator-transform": "^0.14.2" } }, - "@babel/plugin-transform-reserved-words": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", - "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true + }, + "@tweenjs/tween.js": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-17.4.0.tgz", + "integrity": "sha512-J3fzl1F6wvh8KXVVcIuHN12xi1ZDcPA/0Vix+ZcJYwZWVHUwfIqfvzYXXEw7ybeev6477KCTt9fKydU+ajUqcg==" + }, + "@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", + "dev": true + }, + "@types/circular-json": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@types/circular-json/-/circular-json-0.4.0.tgz", + "integrity": "sha512-7+kYB7x5a7nFWW1YPBh3KxhwKfiaI4PbZ1RvzBU91LZy7lWJO822CI+pqzSre/DZ7KsCuMKdHnLHHFu8AyXbQg==" + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4" @@ -3662,10 +4703,28 @@ "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0" } }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", - "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", + "@types/node": { + "version": "13.13.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.11.tgz", + "integrity": "sha512-FX7mIFKfnGCfq10DGWNhfCNxhACEeqH5uulT6wRRA1KEt7zgLe0HdrAd9/QQkObDqp2Z0KEV3OAmNgs0lTx5tQ==", + "dev": true + }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, + "@types/q": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", + "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", + "dev": true + }, + "@types/request": { + "version": "2.48.4", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.4.tgz", + "integrity": "sha512-W1t1MTKYR8PxICH+A4HgEIPuAC3sbljoEVfyZbeFJJDbr30guDspJri2XOaM2E+Un7ZjrihaDi7cf6fPa2tbgw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4", @@ -9726,14 +10785,6 @@ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, - "graphlib": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", - "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", - "requires": { - "lodash": "^4.17.15" - } - }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -11433,9 +12484,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -13464,9 +14515,9 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "node-fetch": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", "dev": true }, "node-fetch-npm": { diff --git a/package.json b/package.json index 0806aac4f6..91cc3b68b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stratos", - "version": "4.0.1", + "version": "4.1.0", "description": "Stratos Console", "main": "index.js", "scripts": { diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-steps.types.ts b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-steps.types.ts index 5acb4f80d2..b20ecdb697 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-steps.types.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-steps.types.ts @@ -34,7 +34,8 @@ export class ApplicationDeploySourceTypes { helpText: 'Please select the public GitHub project and branch you would like to deploy from.', graphic: { // TODO: Move cf assets to CF package (#3769) - location: '/core/assets/endpoint-icons/github-logo.png' + location: '/core/assets/endpoint-icons/github-logo.png', + transform: 'scale(0.7)' } }, { @@ -51,7 +52,8 @@ export class ApplicationDeploySourceTypes { id: DEPLOY_TYPES_IDS.GIT_URL, helpText: 'Please enter the public git url and branch or tag you would like to deploy from.', graphic: { - location: '/core/assets/endpoint-icons/Git-logo.png' + location: '/core/assets/endpoint-icons/Git-logo.png', + transform: 'scale(0.7)' } }, { @@ -59,12 +61,13 @@ export class ApplicationDeploySourceTypes { id: DEPLOY_TYPES_IDS.DOCKER_IMG, helpText: 'Please specify an application name and the Docker image to be used (registry/org/image-name).', graphic: { - location: '/core/assets/endpoint-icons/docker.png' + location: '/core/assets/endpoint-icons/docker.png', + transform: 'scale(0.8)' }, disabledText: 'The selected Cloud Foundry cannot deploy Docker images. Please check that the Diego Docker feature flag is enabled' }, { - name: 'Application Archive File', + name: 'Application Archive', id: DEPLOY_TYPES_IDS.FILE, helpText: 'Please select the archive file that contains the application you would like to deploy.', graphic: { matIcon: 'unarchive' } diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/new-application-base-step/new-application-base-step.component.html b/src/frontend/packages/cloud-foundry/src/features/applications/new-application-base-step/new-application-base-step.component.html index 0b8f2f5066..9bd7ae404a 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/new-application-base-step/new-application-base-step.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/applications/new-application-base-step/new-application-base-step.component.html @@ -7,7 +7,8 @@

New Application

Select application source

To create an application you can either deploy from a specific source or create an application shell. An application shell is an empty application with no package associated with it.

- + +
\ No newline at end of file diff --git a/src/frontend/packages/core/sass/mat-desktop.scss b/src/frontend/packages/core/sass/mat-desktop.scss index 7e11af970e..dd073dffa5 100644 --- a/src/frontend/packages/core/sass/mat-desktop.scss +++ b/src/frontend/packages/core/sass/mat-desktop.scss @@ -103,6 +103,7 @@ $desktop-toggle-button-item-height: $desktop-menu-item-height - 2px; line-height: normal; } } + } // Smaller page header @@ -127,4 +128,19 @@ $desktop-toggle-button-item-height: $desktop-menu-item-height - 2px; app-profile-info .user-profile { top: $desktop-page-header-height; } + + // Smaller dialog titles & content + .mat-dialog-title { + font-size: 18px; + } + .mat-dialog-content { + font-size: 16px; + } + + // Reduced padding on steppers - the stepper already had padding applied because they are included in the dashboard, which + // has padding via the '.dashboard__content` class - so we end up with double padding - this removes the extra level of padding + .steppers__inner { + padding: 0; + } + } diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.html b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.html index 5a3116dce9..c37555a655 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.html +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-base-step/create-endpoint-base-step.component.html @@ -12,7 +12,8 @@

Select the type of the endpoint to register

the 'Connecting' process. To do this return to the Endpoints page.

--> - + diff --git a/src/frontend/packages/core/src/shared/components/tile-selector-tile/tile-selector-tile.component.html b/src/frontend/packages/core/src/shared/components/tile-selector-tile/tile-selector-tile.component.html index 155b4b7885..eefa5a8c67 100644 --- a/src/frontend/packages/core/src/shared/components/tile-selector-tile/tile-selector-tile.component.html +++ b/src/frontend/packages/core/src/shared/components/tile-selector-tile/tile-selector-tile.component.html @@ -1,13 +1,16 @@ -
-
- {{ tile.graphic.matIcon }} - - - +
+
+
+ {{ tile.graphic.matIcon }} + + + +
+
+

{{ tile.label }}

+
+
{{ tile.description }}
-
-

{{ tile.label }}

-
-
{{ tile.description }}
-
+
\ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/tile-selector-tile/tile-selector-tile.component.scss b/src/frontend/packages/core/src/shared/components/tile-selector-tile/tile-selector-tile.component.scss index 68db7e8382..2a6328ad84 100644 --- a/src/frontend/packages/core/src/shared/components/tile-selector-tile/tile-selector-tile.component.scss +++ b/src/frontend/packages/core/src/shared/components/tile-selector-tile/tile-selector-tile.component.scss @@ -1,74 +1,82 @@ -.tile-selector { - $tile-height: 200px; - border-radius: 3px; - cursor: pointer; - display: flex; - flex-direction: column; - height: $tile-height; - opacity: .5; - transition: transform .2s ease; - user-select: none; - width: 100%; - &__taller { - height: 260px; - } - &:hover { - opacity: 1; - transform: scale(1.02); - } - &__more { - align-items: center; +@mixin create($tile-height: 200px, $icon-size: 80px) { + $img-size: $tile-height / 2; + + .tile-selector { + border-radius: 3px; cursor: pointer; display: flex; - font-size: 16px; + flex-direction: column; height: $tile-height; - justify-content: center; - } + opacity: .5; + transition: transform .2s ease; + user-select: none; + width: 100%; - &:active { - transform: scale(.98); - } + &:hover { + opacity: 1; + transform: scale(1.02); + } + &__more { + align-items: center; + cursor: pointer; + display: flex; + font-size: 16px; + height: $tile-height; + justify-content: center; + } - &__active { - opacity: 1; - } + &:active { + transform: scale(.98); + } - &__header { - align-items: center; - display: flex; - flex: 2; - justify-content: center; - width: 100%; + &__active { + opacity: 1; + } + + &__header { + align-items: center; + display: flex; + flex: 2; + justify-content: center; + width: 100%; - img { - height: 100px; + img { + height: $img-size; + } } - } - &__description { - flex: 0; - padding: 10px; - text-align: center; - } + &__description { + flex: 0; + padding: 10px; + text-align: center; + } - &__content { - display: flex; - flex: 0; - flex-direction: column; - justify-content: center; - opacity: .8; - padding: 0 1em; - position: relative; - text-align: center; - word-wrap: break-word; + &__content { + display: flex; + flex: 0; + flex-direction: column; + justify-content: center; + opacity: .8; + padding: 0 1em; + position: relative; + text-align: center; + word-wrap: break-word; + } + + &__icon { + font-size: $icon-size; + height: $icon-size; + opacity: .6; + width: $icon-size; + } } +} + +.tile-selector-parent { + @include create; - $icon-size: 80px; - &__icon { - font-size: $icon-size; - height: $icon-size; - opacity: .6; - width: $icon-size; + &.smaller { + @include create(160px, 60px); } } diff --git a/src/frontend/packages/core/src/shared/components/tile-selector-tile/tile-selector-tile.component.ts b/src/frontend/packages/core/src/shared/components/tile-selector-tile/tile-selector-tile.component.ts index f4f6a69ec7..5b11d6ac48 100644 --- a/src/frontend/packages/core/src/shared/components/tile-selector-tile/tile-selector-tile.component.ts +++ b/src/frontend/packages/core/src/shared/components/tile-selector-tile/tile-selector-tile.component.ts @@ -1,17 +1,20 @@ -import { Component, Input, EventEmitter, Output } from '@angular/core'; -import { ITileConfig, ITileIconConfig, ITileImgConfig, ITileData } from '../tile/tile-selector.types'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +import { ITileConfig, ITileData, ITileGraphic } from '../tile/tile-selector.types'; @Component({ selector: 'app-tile-selector-tile', templateUrl: './tile-selector-tile.component.html', styleUrls: ['./tile-selector-tile.component.scss'] }) -export class TileSelectorTileComponent { +export class TileSelectorTileComponent { @Input() tile: ITileConfig; @Input() active: boolean; + @Input() smaller = false; + @Output() tileSelect = new EventEmitter(); public onClick(tile: ITileConfig) { diff --git a/src/frontend/packages/core/src/shared/components/tile-selector/tile-selector.component.html b/src/frontend/packages/core/src/shared/components/tile-selector/tile-selector.component.html index b3b0112dae..7f05aa5e7d 100644 --- a/src/frontend/packages/core/src/shared/components/tile-selector/tile-selector.component.html +++ b/src/frontend/packages/core/src/shared/components/tile-selector/tile-selector.component.html @@ -11,7 +11,7 @@
- + \ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/tile-selector/tile-selector.component.scss b/src/frontend/packages/core/src/shared/components/tile-selector/tile-selector.component.scss index c5a5ce6951..cccdb39707 100644 --- a/src/frontend/packages/core/src/shared/components/tile-selector/tile-selector.component.scss +++ b/src/frontend/packages/core/src/shared/components/tile-selector/tile-selector.component.scss @@ -9,17 +9,31 @@ min-height: 0; min-width: 0; - @include breakpoint(tablet) { + + @include breakpoint(mobileonly) { + // < 600 + grid-template-columns: repeat(1, 1fr); + } + + @include breakpoint(phablet) { + // > 600 grid-column-gap: $bottom-space; grid-row-gap: $bottom-space; grid-template-columns: repeat(2, 1fr); } + @include breakpoint(tablet) { + // > 900 + grid-template-columns: repeat(3, 1fr); + } + @include breakpoint(laptop) { + // > 1200 grid-template-columns: repeat(4, 1fr); } @include breakpoint(desktop) { + // > 1800 grid-template-columns: repeat(5, 1fr); } } diff --git a/src/frontend/packages/core/src/shared/components/tile-selector/tile-selector.component.ts b/src/frontend/packages/core/src/shared/components/tile-selector/tile-selector.component.ts index 18c8c144de..cd7d92f4b2 100644 --- a/src/frontend/packages/core/src/shared/components/tile-selector/tile-selector.component.ts +++ b/src/frontend/packages/core/src/shared/components/tile-selector/tile-selector.component.ts @@ -12,6 +12,7 @@ export class TileSelectorComponent { public pOptions: ITileConfig[] = []; public hiddenOptions: ITileConfig[] = []; public showingMore = false; + @Input() smallerTiles = false; @Input() set options(options: ITileConfig[]) { if (!options) { return; diff --git a/src/frontend/packages/core/src/shared/components/tile/tile-selector.types.ts b/src/frontend/packages/core/src/shared/components/tile/tile-selector.types.ts index d443c03fcb..92f314d6b5 100644 --- a/src/frontend/packages/core/src/shared/components/tile/tile-selector.types.ts +++ b/src/frontend/packages/core/src/shared/components/tile/tile-selector.types.ts @@ -5,6 +5,7 @@ export interface ITileIconConfig { export interface ITileImgConfig { location: string; + transform?: string; } export type ITileGraphic = ITileIconConfig | ITileImgConfig; diff --git a/website/docs/extensions/theming.md b/website/docs/extensions/theming.md index 50d26d74da..cd55d91cfc 100644 --- a/website/docs/extensions/theming.md +++ b/website/docs/extensions/theming.md @@ -13,7 +13,7 @@ Stratos provides a mechanism for customizing the theme, including: Theme's are best encapsulated in a new npm package. It should contain the usual `package.json` file with a `stratos` section which will contain some of the theme customizations. -An example of the type of package that can be created can be found in the [ACME example](https://github.com/cloudfoundry/stratos/tree/master/src/frontend/packages/example-theme). To run Stratos with these customizations see [here](/docs/extensions/introduction#acme). +An example of the type of package that can be created can be found in the [ACME example](https://github.com/cloudfoundry/stratos/tree/master/src/frontend/packages/example-theme). To run Stratos with these customizations see [here](./introduction#acme). ## Colors Stratos uses Material Design and the [angular-material](https://material.angular.io/) library and uses the same approach to theming. @@ -53,10 +53,10 @@ Additional Stratos colors can be customized by supplying more colors to the `the |---|---| |app-background-color| Base color to show in the background of the application | |app-background-text-color| Color of text when placed on the basic background | -|side-nav| See [below](/docs/extensions/theming#side-nav-colors) | -|status| See [below](/docs/extensions/theming#status-colors)| +|side-nav| See [below](./theming#side-nav-colors) | +|status| See [below](./theming#status-colors)| |subdued-color| Lighter color meant to be a subdued version of the primary color | -|ansi-colors| See [below](/docs/extensions/theming#ansi-colors)| +|ansi-colors| See [below](./theming#ansi-colors)| |header-background-color| Background color for the main stratos header| |header-foreground-color| Foreground color for the main stratos | |stratos-title-show-text| Boolean - Show `Stratos` or provided title with the large logo in the about page, default log in page, etc | From 9bd34032c6f90017056e95ecb58d4c9612e6c167 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 16 Sep 2020 12:41:46 +0100 Subject: [PATCH 621/648] Helm Values Editor: Bug fixes (#482) * Bug fixes * Better fix using merge * Ensure values are strings before submitting - only change in chart editor is getValues return type, otherwise all whitespace/semi colon Co-authored-by: Richard Cox --- .../chart-values-editor.component.ts | 33 ++++++++++--------- .../chart-values-editor/diffvalues.ts | 32 +++++++++++++++--- .../workloads/chart-values-editor/merge.ts | 26 +++++++++++++++ .../create-release.component.ts | 6 ++-- .../upgrade-release.component.ts | 8 ++--- 5 files changed, 79 insertions(+), 26 deletions(-) create mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/merge.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts index 9b5f28c925..ff0ace0e14 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts @@ -10,6 +10,7 @@ import { ConfirmationDialogService } from '../../../../../../core/src/shared/com import { ThemeService } from './../../../../../../store/src/theme.service'; import { diffObjects } from './diffvalues'; import { generateJsonSchemaFromObject } from './json-schema-generator'; +import { mergeObjects } from './merge'; export interface ChartValuesConfig { @@ -21,7 +22,7 @@ export interface ChartValuesConfig { valuesUrl: string; // Values for the current release (optional) - releaseValues? : string + releaseValues?: string; } // Height of the toolbar that sits above the editor conmponent @@ -107,7 +108,7 @@ export class ChartValuesEditorComponent implements OnInit, OnDestroy, AfterViewI private codeOnEnter: string; // Reference to the editor, so we can adjust its size to fit - @ViewChild('monacoEditor', {read: ElementRef}) monacoEditor: ElementRef; + @ViewChild('monacoEditor', { read: ElementRef }) monacoEditor: ElementRef; @ViewChild('schemaForm') schemaForm: JsonSchemaFormComponent; @@ -145,11 +146,11 @@ export class ChartValuesEditorComponent implements OnInit, OnDestroy, AfterViewI private httpClient: HttpClient, private themeService: ThemeService, private confirmDialog: ConfirmationDialogService, - ) {} + ) { } ngOnInit(): void { // Listen for window resize and resize the editor when this happens - this.resizeSub = fromEvent(window, 'resize').pipe(debounceTime(150)).subscribe(event => this.resize()); + this.resizeSub = fromEvent(window, 'resize').pipe(debounceTime(150)).subscribe(event => this.resize()); } private init() { @@ -231,7 +232,9 @@ export class ChartValuesEditorComponent implements OnInit, OnDestroy, AfterViewI // Form -> Editor // Only copy if there is not an error - otherwise keep the invalid yaml from the editor that needs fixing if (!this.yamlError) { - this.code = this.getDiff(this.formData); + const codeYaml = yaml.safeLoad(this.code || '{}'); + const data = mergeObjects(codeYaml, this.formData); + this.code = this.getDiff(data); this.codeOnEnter = this.code; } @@ -242,20 +245,18 @@ export class ChartValuesEditorComponent implements OnInit, OnDestroy, AfterViewI // Try and parse the YAML - if we can't this is an error, so we can't edit this back in the form try { if (this.codeOnEnter === this.code) { - return + // Code did not change + return; } // Parse as json const json = yaml.safeLoad(this.code || '{}'); // Must be an object, otherwise it was not valid - if (typeof(json) !== 'object') { + if (typeof (json) !== 'object') { throw new Error('Invalid YAML'); } this.yamlError = false; - const data = { - ...this.formData, - ...json - }; + const data = mergeObjects(this.formData, json); this.initialFormData = data; this.formData = data; } catch (e) { @@ -296,7 +297,7 @@ export class ChartValuesEditorComponent implements OnInit, OnDestroy, AfterViewI this.model = { language: 'yaml', uri: this.getSchemaUri() - }; + }; } // Delayed resize of editor to fit @@ -357,7 +358,7 @@ export class ChartValuesEditorComponent implements OnInit, OnDestroy, AfterViewI }); } - public getValues(): any { + public getValues(): object { return (this.mode === EditorMode.JSonSchemaForm) ? this.formData : yaml.safeLoad(this.code); } @@ -402,10 +403,12 @@ export class ChartValuesEditorComponent implements OnInit, OnDestroy, AfterViewI } } - // Reset the form values + // Reset the form values and the code clearFormValues() { this.confirmDialog.open(this.clearValuesConfirmation, () => { this.initialFormData = {}; + this.code = ''; + this.codeOnEnter = ''; }); } @@ -422,6 +425,6 @@ export class ChartValuesEditorComponent implements OnInit, OnDestroy, AfterViewI if (code.trim() === '{}') { code = ''; } - return code + return code; } } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/diffvalues.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/diffvalues.ts index 4fc2d15946..d4a27c6e8d 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/diffvalues.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/diffvalues.ts @@ -5,11 +5,14 @@ function arraysAreEqual(a1: any[], a2: any[]): boolean { return false } + // Compare each item in the array for (let i=0; i { + if (typeof(src[key]) !== typeof(dest[key])) { + return false; + } else if(src[key] === null && dest[key] === null) { + return true; + } else if (Array.isArray(src[key]) && !arraysAreEqual(src[key], dest[key])) { + return false; + } else if (typeof(src[key]) === 'object' && !objectsAreEqual(src[key], dest[key])) { + return false; + } + }); + + return true; +} + // NOTE: This is a one-way diff only // diffObjects is main export - diffs two objects and returns only the diffrence export function diffObjects(src: any, dest: any): any { if (!src) { return {}; } + Object.keys(src).forEach(key => { if (typeof(src[key]) === typeof(dest[key])) { if(src[key] === null && dest[key] === null) { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/merge.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/merge.ts new file mode 100644 index 0000000000..39ef4cf042 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/merge.ts @@ -0,0 +1,26 @@ + +export function mergeObjects(src: any, ...dest: any): any { + // Copy src + const data = JSON.parse(JSON.stringify(src)); + // Merge in all of the dest objects + for (const obj of dest) { + doMergeObjects(data, obj); + } + + return data; +} + +// merge from dest into src +function doMergeObjects(src: any, dest: any) { + // Go through the keys of dest an update them in src + Object.keys(dest).forEach(key => { + if (typeof(dest[key]) === 'object' && !Array.isArray(dest)) { + if (!src[key]) { + src[key] = {}; + } + doMergeObjects(src[key], dest[key]); + } else { + src[key] = dest[key] + } + }); +} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.ts index fcc3ccff84..f044e91101 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.ts @@ -65,7 +65,7 @@ export class CreateReleaseComponent implements OnInit, OnDestroy { this.config = { valuesUrl: `/pp/v1/chartsvc/v1/assets/${chart.repo}/${chart.name}/versions/${chart.version}/values.yaml`, schemaUrl: `/pp/v1/chartsvc/v1/assets/${chart.repo}/${chart.name}/versions/${chart.version}/values.schema.json`, - } + }; this.setupDetailsStep(); } @@ -222,7 +222,7 @@ export class CreateReleaseComponent implements OnInit, OnDestroy { // Build the request body const values: HelmInstallValues = { ...this.details.value, - values: this.editor.getValues(), + values: JSON.stringify(this.editor.getValues()), chart: { name: this.route.snapshot.params.name, repo: this.route.snapshot.params.repo, @@ -257,7 +257,7 @@ export class CreateReleaseComponent implements OnInit, OnDestroy { }, message: !result.error ? '' : result.message })) - ) + ); }) ); } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts index 2502205f7e..43d7af5f25 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts @@ -85,7 +85,7 @@ export class UpgradeReleaseComponent { // Ensure the editor is resized when the overrides step becomes visible onEnterOverrides = () => { this.editor.resizeEditor(); - } + }; // Update the editor with the chosen version when the user moves to the next step onNext = (): Observable => { @@ -103,10 +103,10 @@ export class UpgradeReleaseComponent { }; }), map(() => { - return { success: true } + return { success: true }; }) ); - } + }; // Hide/show the advanced options step toggleAdvancedOptions() { @@ -121,7 +121,7 @@ export class UpgradeReleaseComponent { // Add the chart url into the values const values: HelmUpgradeValues = { - values: this.editor.getValues(), + values: JSON.stringify(this.editor.getValues()), restartPods: false, chart: { name: this.version.relationships.chart.data.name, From bc9cd1a1d0862e258bc3a56b6b5042426e0ff828 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 16 Sep 2020 13:31:35 +0100 Subject: [PATCH 622/648] Update CI pipelines (#483) --- deploy/ci/console-dev-releases.yml | 4 ++-- deploy/ci/suse-console-dev-releases.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deploy/ci/console-dev-releases.yml b/deploy/ci/console-dev-releases.yml index 4696f6a39b..e52789e24f 100644 --- a/deploy/ci/console-dev-releases.yml +++ b/deploy/ci/console-dev-releases.yml @@ -13,8 +13,8 @@ resources: uri: git@github.com:((stratos-repository-organization))/((stratos-repository)) branch: ((stratos-repository-branch)) private_key: ((github-private-key)) - # Match any Version 3 release (alpha, beta, rc) other than the actual release - tag_filter: "3*-*" + # Match any Version 4 release (alpha, beta, rc) other than the actual release + tag_filter: "4*-*" - name: helm-repo type: git source: diff --git a/deploy/ci/suse-console-dev-releases.yml b/deploy/ci/suse-console-dev-releases.yml index eb40b5b8c0..e05f578318 100644 --- a/deploy/ci/suse-console-dev-releases.yml +++ b/deploy/ci/suse-console-dev-releases.yml @@ -14,8 +14,8 @@ resources: uri: git@github.com:((stratos-repository-organization))/((stratos-repository)) branch: ((stratos-repository-branch)) private_key: ((github-private-key)) - # Match any Version 3 release (alpha, beta, rc) other than the actual release - tag_filter: "3*-*" + # Match any Version 4 release (alpha, beta, rc) other than the actual release + tag_filter: "4*-*" - name: helm-repo type: git source: From 73ec30e15ae318c555a02a8d1976e615133f91fd Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Wed, 16 Sep 2020 20:56:06 +0100 Subject: [PATCH 623/648] Update package-lock (#485) --- package-lock.json | 1221 +++++---------------------------------------- 1 file changed, 125 insertions(+), 1096 deletions(-) diff --git a/package-lock.json b/package-lock.json index e9babc88f2..a7b9794ca8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "stratos", - "version": "4.0.1", + "version": "4.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -3331,916 +3331,6 @@ "@babel/types": "^7.11.0" } }, - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", - "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", - "dev": true - }, - "@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/traverse": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", - "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.5", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.11.5", - "@babel/types": "^7.11.5", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "@babel/types": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", - "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", - "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz", - "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz", - "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/generator": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", - "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", - "dev": true, - "requires": { - "@babel/types": "^7.11.5", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", - "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", - "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", - "dev": true, - "requires": { - "@babel/types": "^7.11.0" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", - "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-replace-supers": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", - "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", - "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", - "dev": true, - "requires": { - "@babel/types": "^7.11.0" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", - "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", - "dev": true - }, - "@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/traverse": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", - "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.5", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.11.5", - "@babel/types": "^7.11.5", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "@babel/types": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", - "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz", - "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/generator": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", - "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", - "dev": true, - "requires": { - "@babel/types": "^7.11.5", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", - "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", - "dev": true - }, - "@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/traverse": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", - "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.5", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.11.5", - "@babel/types": "^7.11.5", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "@babel/types": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", - "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz", - "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz", - "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==", - "dev": true, - "requires": { - "regenerator-transform": "^0.14.2" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", - "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz", - "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz", - "integrity": "sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", - "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-regex": "^7.10.4" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz", - "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz", - "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz", - "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/preset-env": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.0.tgz", - "integrity": "sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.9.0", - "@babel/helper-compilation-targets": "^7.8.7", - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-proposal-async-generator-functions": "^7.8.3", - "@babel/plugin-proposal-dynamic-import": "^7.8.3", - "@babel/plugin-proposal-json-strings": "^7.8.3", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-proposal-numeric-separator": "^7.8.3", - "@babel/plugin-proposal-object-rest-spread": "^7.9.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", - "@babel/plugin-proposal-optional-chaining": "^7.9.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", - "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-json-strings": "^7.8.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-numeric-separator": "^7.8.0", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.8.3", - "@babel/plugin-transform-arrow-functions": "^7.8.3", - "@babel/plugin-transform-async-to-generator": "^7.8.3", - "@babel/plugin-transform-block-scoped-functions": "^7.8.3", - "@babel/plugin-transform-block-scoping": "^7.8.3", - "@babel/plugin-transform-classes": "^7.9.0", - "@babel/plugin-transform-computed-properties": "^7.8.3", - "@babel/plugin-transform-destructuring": "^7.8.3", - "@babel/plugin-transform-dotall-regex": "^7.8.3", - "@babel/plugin-transform-duplicate-keys": "^7.8.3", - "@babel/plugin-transform-exponentiation-operator": "^7.8.3", - "@babel/plugin-transform-for-of": "^7.9.0", - "@babel/plugin-transform-function-name": "^7.8.3", - "@babel/plugin-transform-literals": "^7.8.3", - "@babel/plugin-transform-member-expression-literals": "^7.8.3", - "@babel/plugin-transform-modules-amd": "^7.9.0", - "@babel/plugin-transform-modules-commonjs": "^7.9.0", - "@babel/plugin-transform-modules-systemjs": "^7.9.0", - "@babel/plugin-transform-modules-umd": "^7.9.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", - "@babel/plugin-transform-new-target": "^7.8.3", - "@babel/plugin-transform-object-super": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.8.7", - "@babel/plugin-transform-property-literals": "^7.8.3", - "@babel/plugin-transform-regenerator": "^7.8.7", - "@babel/plugin-transform-reserved-words": "^7.8.3", - "@babel/plugin-transform-shorthand-properties": "^7.8.3", - "@babel/plugin-transform-spread": "^7.8.3", - "@babel/plugin-transform-sticky-regex": "^7.8.3", - "@babel/plugin-transform-template-literals": "^7.8.3", - "@babel/plugin-transform-typeof-symbol": "^7.8.4", - "@babel/plugin-transform-unicode-regex": "^7.8.3", - "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.9.0", - "browserslist": "^4.9.1", - "core-js-compat": "^3.6.2", - "invariant": "^2.2.2", - "levenary": "^1.1.1", - "semver": "^5.5.0" - } - }, - "@babel/preset-modules": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", - "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/template": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", - "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/generator": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", - "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", - "dev": true, - "requires": { - "@babel/types": "^7.11.5", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", - "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", - "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", - "dev": true, - "requires": { - "@babel/types": "^7.11.0" - } - }, - "@babel/helper-module-imports": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", - "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-module-transforms": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", - "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-simple-access": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/template": "^7.10.4", - "@babel/types": "^7.11.0", - "lodash": "^4.17.19" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", - "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-replace-supers": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", - "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-simple-access": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", - "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", - "dev": true, - "requires": { - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", - "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", - "dev": true, - "requires": { - "@babel/types": "^7.11.0" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", - "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", - "dev": true - }, - "@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/traverse": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", - "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.5", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.11.5", - "@babel/types": "^7.11.5", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "@babel/types": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", - "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz", - "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.9.0", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "@cfstratos/ajsf-core": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@cfstratos/ajsf-core/-/ajsf-core-0.1.6.tgz", - "integrity": "sha512-G47ZAvn7ynuUkB2XzeewxhDB9yB5EYcBWfmBYB6FeEkFzX9PpcaCm8QK72IO6Kis+HjAvO5kwqiud9TXUPhMNw==", - "requires": { - "ajv": "^6.10.0", - "lodash-es": "^4.17.15" - } - }, - "@cfstratos/ajsf-material": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@cfstratos/ajsf-material/-/ajsf-material-0.1.6.tgz", - "integrity": "sha512-dUV2aqwQoRCaedNh31ZVZUgDhWqdiJnikag/Nws5h4fFs6sdmL2sS8A18e6onLA8t9t4n0y8PEib6qgxqr3ezw==", - "requires": { - "@cfstratos/ajsf-core": "~0.1.5", - "lodash-es": "^4.17.15" - } - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/generator": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", - "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", - "dev": true, - "requires": { - "@babel/types": "^7.11.5", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", - "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", - "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", - "dev": true, - "requires": { - "@babel/types": "^7.11.0" - } - }, - "@babel/helper-module-imports": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", - "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-module-transforms": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", - "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-simple-access": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/template": "^7.10.4", - "@babel/types": "^7.11.0", - "lodash": "^4.17.19" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", - "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", - "dev": true, - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-replace-supers": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", - "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-simple-access": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", - "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", - "dev": true, - "requires": { - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", - "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", - "dev": true, - "requires": { - "@babel/types": "^7.11.0" - } - }, "@babel/highlight": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", @@ -4285,76 +3375,22 @@ "globals": "^11.1.0", "lodash": "^4.17.19" } - } - } - }, - "@jsdevtools/ono": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.2.tgz", - "integrity": "sha512-qS/a24RA5FEoiJS9wiv6Pwg2c/kiUo3IVUQcfeM9JvsR6pM8Yx+yl/6xWYLckZCT5jpLNhslgjiA8p/XcGyMRQ==", - "dev": true - }, - "@ngrx/effects": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-9.2.0.tgz", - "integrity": "sha512-8V09zDIPehGpzgfcgyczelovsVYJvDQhN9wHt37K5A+YCG0CI8nj8FmKokHATwv/S62YqFrOVnr/TZacxpDhBw==" - }, - "@ngrx/router-store": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@ngrx/router-store/-/router-store-9.2.0.tgz", - "integrity": "sha512-thu6aU9YWM64oNEk4Srx/mNSeQ2SPJKlTji8MSzfr06qgCMyPSXZBYlfs8HqY+af3eB7XBEhb/4ew4JJ6xC9zw==" - }, - "@ngrx/store": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-9.2.0.tgz", - "integrity": "sha512-V8AI3mxbMztVpbZpALkLZYlGkofKcu9GaOCY5e+sZ1VcJ90oxhFjBpnmd6MuVdmhep1XAHALb1B8ZbBFn+xsgQ==" - }, - "@ngrx/store-devtools": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-9.2.0.tgz", - "integrity": "sha512-/FvgcpjO4IvwNFnRVoHGikAvckr6fxKf4NgYoTQ9giI8xavolLvuQUHxzH20legi5dgZz34ii2m2g1Q7OxEV8w==" - }, - "@ngtools/webpack": { - "version": "9.1.12", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-9.1.12.tgz", - "integrity": "sha512-lypMXIq5oxBMsoDu/VOa1yUmmXthhxkCJa8LG0ZohfnbwhmZvz3SAW7omBGuVrb5cVIfLCkaRCSnQ1MNc6ULXw==", - "dev": true, - "requires": { - "@angular-devkit/core": "9.1.12", - "enhanced-resolve": "4.1.1", - "rxjs": "6.5.4", - "webpack-sources": "1.4.3" - }, - "dependencies": { - "@angular-devkit/core": { - "version": "9.1.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.1.12.tgz", - "integrity": "sha512-D/GnBeSlmdgGn7EhuE32HuPuRAjvUuxi7Q6WywBI8PSsXKAGnrypghBwMATNnOA24//CgbW2533Y9VWHaeXdeA==", - "dev": true, - "requires": { - "ajv": "6.12.3", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.5.4", - "source-map": "0.7.3" - } }, - "ajv": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", - "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", "dev": true, "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" } }, - "rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { "ms": "^2.1.1" @@ -4368,53 +3404,10 @@ } } }, - "@nodelib/fs.scandir": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.3", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.3", - "fastq": "^1.6.0" - } - }, - "@npmcli/move-file": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", - "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", - "dev": true, - "requires": { - "mkdirp": "^1.0.4" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - } - } - }, - "@rollup/plugin-commonjs": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz", - "integrity": "sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA==", + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", + "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", "dev": true, "requires": { "@babel/helper-create-regexp-features-plugin": "^7.10.4" @@ -4641,44 +3634,10 @@ "regenerator-transform": "^0.14.2" } }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true - }, - "@tweenjs/tween.js": { - "version": "17.4.0", - "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-17.4.0.tgz", - "integrity": "sha512-J3fzl1F6wvh8KXVVcIuHN12xi1ZDcPA/0Vix+ZcJYwZWVHUwfIqfvzYXXEw7ybeev6477KCTt9fKydU+ajUqcg==" - }, - "@types/caseless": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", - "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", - "dev": true - }, - "@types/circular-json": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@types/circular-json/-/circular-json-0.4.0.tgz", - "integrity": "sha512-7+kYB7x5a7nFWW1YPBh3KxhwKfiaI4PbZ1RvzBU91LZy7lWJO822CI+pqzSre/DZ7KsCuMKdHnLHHFu8AyXbQg==" - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, - "@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true - }, - "@types/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "@babel/plugin-transform-reserved-words": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", + "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4" @@ -4703,28 +3662,10 @@ "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0" } }, - "@types/node": { - "version": "13.13.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.11.tgz", - "integrity": "sha512-FX7mIFKfnGCfq10DGWNhfCNxhACEeqH5uulT6wRRA1KEt7zgLe0HdrAd9/QQkObDqp2Z0KEV3OAmNgs0lTx5tQ==", - "dev": true - }, - "@types/normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", - "dev": true - }, - "@types/q": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", - "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", - "dev": true - }, - "@types/request": { - "version": "2.48.4", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.4.tgz", - "integrity": "sha512-W1t1MTKYR8PxICH+A4HgEIPuAC3sbljoEVfyZbeFJJDbr30guDspJri2XOaM2E+Un7ZjrihaDi7cf6fPa2tbgw==", + "@babel/plugin-transform-sticky-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", + "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4", @@ -4980,6 +3921,26 @@ "lodash-es": "^4.17.15" } }, + "@cfstratos/monaco-yaml": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cfstratos/monaco-yaml/-/monaco-yaml-2.5.0.tgz", + "integrity": "sha512-4ojHL0lnXZHHW8k2J8ipaXsl+aQj3bOXVdJEIwyhLtsJcYpq3npEiQkQs/U7ySi4Ehm/3qTdccXlilAyuWLxyw==", + "requires": { + "js-yaml": "^3.12.0", + "prettier": "^1.19.1", + "vscode-json-languageservice": "^3.8.3", + "vscode-languageserver": "^6.1.1", + "vscode-uri": "^2.1.2" + }, + "dependencies": { + "prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "optional": true + } + } + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -6215,7 +5176,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -9697,8 +8657,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esrecurse": { "version": "4.2.1", @@ -10785,6 +9744,14 @@ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, + "graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "requires": { + "lodash": "^4.17.15" + } + }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -12554,7 +11521,6 @@ "version": "3.13.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -12662,6 +11628,11 @@ "minimist": "^1.2.5" } }, + "jsonc-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", + "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==" + }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -12841,13 +11812,6 @@ "path-exists": "^4.0.0" } }, - "fsevents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", - "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", - "dev": true, - "optional": true - }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -14509,6 +13473,11 @@ "tslib": "^1.9.0" } }, + "ngx-monaco-editor": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ngx-monaco-editor/-/ngx-monaco-editor-9.0.0.tgz", + "integrity": "sha512-fPXT3M8W920Vs0KPMX6iA7GJYjBRnl9naug9A7D2inPPzxQGtgPZrSEatIPf37a6ir9Ts+8fwt1bTkFzfTgIpQ==" + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -19293,8 +18262,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { "version": "1.16.1", @@ -21235,6 +20203,67 @@ "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", "dev": true }, + "vscode-json-languageservice": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-3.8.4.tgz", + "integrity": "sha512-njDG0+YJvYNKXH+6plQGZMxgbifATFrRpC6Qnm/SAn4IW8bMHxsYunsxrjtpqK42CVSz6Lr7bpbTEZbVuOmFLw==", + "requires": { + "jsonc-parser": "^2.3.1", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "3.16.0-next.2", + "vscode-nls": "^5.0.0", + "vscode-uri": "^2.1.2" + } + }, + "vscode-jsonrpc": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz", + "integrity": "sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A==" + }, + "vscode-languageserver": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-6.1.1.tgz", + "integrity": "sha512-DueEpkUAkD5XTR4MLYNr6bQIp/UFR0/IPApgXU3YfCBCB08u2sm9hRCs6DxYZELkk++STPjpcjksR2H8qI3cDQ==", + "requires": { + "vscode-languageserver-protocol": "^3.15.3" + } + }, + "vscode-languageserver-protocol": { + "version": "3.15.3", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz", + "integrity": "sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw==", + "requires": { + "vscode-jsonrpc": "^5.0.1", + "vscode-languageserver-types": "3.15.1" + }, + "dependencies": { + "vscode-languageserver-types": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz", + "integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==" + } + } + }, + "vscode-languageserver-textdocument": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1.tgz", + "integrity": "sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA==" + }, + "vscode-languageserver-types": { + "version": "3.16.0-next.2", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0-next.2.tgz", + "integrity": "sha512-QjXB7CKIfFzKbiCJC4OWC8xUncLsxo19FzGVp/ADFvvi87PlmBSCAtZI5xwGjF5qE0xkLf0jjKUn3DzmpDP52Q==" + }, + "vscode-nls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-5.0.0.tgz", + "integrity": "sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA==" + }, + "vscode-uri": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.2.tgz", + "integrity": "sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==" + }, "watchpack": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz", From 99ff9e9efa52ad7043f7dbd49eedc5dc1d5dc577 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Wed, 16 Sep 2020 20:56:36 +0100 Subject: [PATCH 624/648] Ensure wide helm chart logos are not cropped in chart list (#486) --- .../custom/helm/monocular/chart-item/chart-item.component.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.scss b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.scss index e42c43a10e..813c0b946e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.scss @@ -9,6 +9,7 @@ $chart-item-logo-width: 80px; &-logo { max-height: 80%; + max-width: 80%; } &-info { From 3279924a840f80d4357d444c1ea4a6252c74c4fd Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 17 Sep 2020 07:28:42 +0100 Subject: [PATCH 625/648] Truncate fields to fit db width (#487) --- src/jetstream/plugins/monocular/chart_svc.go | 2 ++ src/jetstream/plugins/monocular/store/chart_store_db.go | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/jetstream/plugins/monocular/chart_svc.go b/src/jetstream/plugins/monocular/chart_svc.go index 770d0d69fc..2913536a52 100644 --- a/src/jetstream/plugins/monocular/chart_svc.go +++ b/src/jetstream/plugins/monocular/chart_svc.go @@ -227,6 +227,8 @@ func (m *Monocular) translateToChart(record *store.ChartStoreRecord, chartYaml * if chartYaml != nil { chart.Keywords = chartYaml.Keywords + // Prefer the Chart Yaml description if we have it (db one might be truncated) + chart.Description = chartYaml.Description chart.Maintainers = make([]ChartMaintainer, len(chartYaml.Maintainers)) for index, maintainer := range chartYaml.Maintainers { chart.Maintainers[index] = *maintainer diff --git a/src/jetstream/plugins/monocular/store/chart_store_db.go b/src/jetstream/plugins/monocular/store/chart_store_db.go index 5fd0c92a2b..3ad7e0bd79 100644 --- a/src/jetstream/plugins/monocular/store/chart_store_db.go +++ b/src/jetstream/plugins/monocular/store/chart_store_db.go @@ -50,6 +50,10 @@ func NewHelmChartDBStore(dcp *sql.DB) (ChartStore, error) { return &HelmChartDBStore{db: dcp}, nil } +func truncate(in string) string { + return fmt.Sprintf("%.255s", in) +} + // Save a Helm Chart to the database func (p *HelmChartDBStore) Save(chart ChartStoreRecord, batchID string) error { @@ -67,11 +71,11 @@ func (p *HelmChartDBStore) Save(chart ChartStoreRecord, batchID string) error { if err == nil { // The record already exists, so update it - _, err := p.db.Exec(updateChartVersion, chart.Created, chart.AppVersion, chart.Description, chart.IconURL, chart.ChartURL, sourceURL, chart.Digest, chart.IsLatest, batchID, chart.EndpointID, chart.Name, chart.Repository, chart.Version) + _, err := p.db.Exec(updateChartVersion, chart.Created, chart.AppVersion, truncate(chart.Description), truncate(chart.IconURL), truncate(chart.ChartURL), truncate(sourceURL), chart.Digest, chart.IsLatest, batchID, chart.EndpointID, chart.Name, chart.Repository, chart.Version) return err } - if _, err := p.db.Exec(saveChartVersion, chart.EndpointID, chart.Name, chart.Repository, chart.Version, chart.Created, chart.AppVersion, chart.Description, chart.IconURL, chart.ChartURL, sourceURL, chart.Digest, chart.IsLatest, batchID); err != nil { + if _, err := p.db.Exec(saveChartVersion, chart.EndpointID, chart.Name, chart.Repository, chart.Version, chart.Created, chart.AppVersion, truncate(chart.Description), truncate(chart.IconURL), truncate(chart.ChartURL), truncate(sourceURL), chart.Digest, chart.IsLatest, batchID); err != nil { return fmt.Errorf("Unable to save Helm Chart Version: %v", err) } return nil From 9a0727227c68b1d256059a3216f48f5ed2e98a83 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Thu, 17 Sep 2020 09:06:55 +0100 Subject: [PATCH 626/648] Ensure service name is correct in helm chart NOTES.txt (#489) --- deploy/kubernetes/console/templates/NOTES.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/kubernetes/console/templates/NOTES.txt b/deploy/kubernetes/console/templates/NOTES.txt index 0d005a46fd..a423cd3600 100644 --- a/deploy/kubernetes/console/templates/NOTES.txt +++ b/deploy/kubernetes/console/templates/NOTES.txt @@ -9,14 +9,14 @@ From outside the cluster, the server URL is: http://{{ .Values.console.ingress.h {{- else }} Get the URL by running these commands in the same shell: {{- if contains "NodePort" .Values.console.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services console-ui-ext) + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ .Release.Name }}-ui-ext) export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") echo https://$NODE_IP:$NODE_PORT {{- else if contains "LoadBalancer" .Values.console.service.type }} NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get svc --namespace {{ .Release.Namespace }} -w console-ui-ext' + You can watch the status of by running 'kubectl get svc --namespace {{ .Release.Namespace }} -w {{ .Release.Name }}-ui-ext' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} console-ui-ext -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ .Release.Name }}-ui-ext -o jsonpath='{.status.loadBalancer.ingress[0].ip}') echo http://$SERVICE_IP:{{ .Values.console.service.servicePort }} {{- else if contains "ClusterIP" .Values.console.service.type }} export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app=stratos-0,component=ui" -o jsonpath="{.items[0].metadata.name}") From 4967c8d7b715612d89372bddbf9958878618179d Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Thu, 17 Sep 2020 10:51:13 +0100 Subject: [PATCH 627/648] Ensure the api key secret db field is long enough (#488) --- src/jetstream/datastore/20200814140918_ApiKeys.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jetstream/datastore/20200814140918_ApiKeys.go b/src/jetstream/datastore/20200814140918_ApiKeys.go index 2a00b44365..43bc76b181 100644 --- a/src/jetstream/datastore/20200814140918_ApiKeys.go +++ b/src/jetstream/datastore/20200814140918_ApiKeys.go @@ -10,7 +10,7 @@ func init() { RegisterMigration(20200814140918, "ApiKeys", func(txn *sql.Tx, conf *goose.DBConf) error { apiTokenTable := "CREATE TABLE IF NOT EXISTS api_keys (" apiTokenTable += "guid VARCHAR(36) NOT NULL UNIQUE," - apiTokenTable += "secret VARCHAR(36) NOT NULL UNIQUE," + apiTokenTable += "secret VARCHAR(64) NOT NULL UNIQUE," apiTokenTable += "user_guid VARCHAR(36) NOT NULL," apiTokenTable += "comment VARCHAR(255) NOT NULL," apiTokenTable += "last_used TIMESTAMP," From c0499097dd1c85d0a5c85173ec06cc8d5dde07d4 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 17 Sep 2020 15:16:29 +0100 Subject: [PATCH 628/648] Fix created date issue on helm chart re-sync (#491) * Fix created date issue * Typo fix, change log level --- src/jetstream/plugins/monocular/store/chart_store_db.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/jetstream/plugins/monocular/store/chart_store_db.go b/src/jetstream/plugins/monocular/store/chart_store_db.go index 3ad7e0bd79..5a7561898f 100644 --- a/src/jetstream/plugins/monocular/store/chart_store_db.go +++ b/src/jetstream/plugins/monocular/store/chart_store_db.go @@ -7,6 +7,7 @@ import ( "sort" "github.com/cloudfoundry-incubator/stratos/src/jetstream/datastore" + log "github.com/sirupsen/logrus" ) var ( @@ -21,7 +22,7 @@ var ( getChartVersion = `SELECT endpoint, name, repo_name, version, created, app_version, description, icon_url, chart_url, source_url, digest, is_latest FROM helm_charts WHERE repo_name = $1 AND name = $2 AND version = $3` getChartVersions = `SELECT endpoint, name, repo_name, version, created, app_version, description, icon_url, chart_url, source_url, digest, is_latest FROM helm_charts WHERE repo_name = $1 AND name = $2` getEndpointIDs = `SELECT DISTINCT endpoint FROM helm_charts` - updateChartDigest = `UPDATE helm_charts SET is_latest=$1, update_batch=$2 WHERE endpoint=$3 AND name=$4 AND repo_name=$5 AND version=$6` + updateChartDigest = `UPDATE helm_charts SET created=$1, is_latest=$2, update_batch=$3 WHERE endpoint=$4 AND name=$5 AND repo_name=$6 AND version=$7` ) // InitRepositoryProvider - One time init for the given DB Provider @@ -65,11 +66,13 @@ func (p *HelmChartDBStore) Save(chart ChartStoreRecord, batchID string) error { // Get the existing record - if it has the same digest, then no need to store it record, err := p.GetChart(chart.Repository, chart.Name, chart.Version) if err == nil && record.Digest == chart.Digest { - _, err := p.db.Exec(updateChartDigest, chart.IsLatest, batchID, chart.EndpointID, chart.Name, chart.Repository, chart.Version) + log.Debugf("Chart already exists %s/%s-%s with digest %s", chart.Repository, chart.Name, chart.Version, chart.Digest) + _, err := p.db.Exec(updateChartDigest, chart.Created, chart.IsLatest, batchID, chart.EndpointID, chart.Name, chart.Repository, chart.Version) return err } if err == nil { + log.Debugf("Chart already exists %s/%s-%s with different digest %s", chart.Repository, chart.Name, chart.Version, chart.Digest) // The record already exists, so update it _, err := p.db.Exec(updateChartVersion, chart.Created, chart.AppVersion, truncate(chart.Description), truncate(chart.IconURL), truncate(chart.ChartURL), truncate(sourceURL), chart.Digest, chart.IsLatest, batchID, chart.EndpointID, chart.Name, chart.Repository, chart.Version) return err From 29ee2f91203fa5f156c829482fc3ab2aa4bbcc7e Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Thu, 17 Sep 2020 16:37:47 +0100 Subject: [PATCH 629/648] Add chart link in charts table, refresh endpoints status on sync (#490) * Add link to chart in chart list table view - * Refresh endpoints after sync request is sent * Fix unit tests --- .../custom/helm/chart-view/monocular.component.ts | 9 ++++++--- .../src/custom/helm/helm-entity-generator.ts | 14 ++++++++++++-- .../monocular-charts-list-config.service.ts | 11 ++++++++++- .../chart-details-versions.component.spec.ts | 6 ++++-- .../chart-details-versions.component.ts | 9 ++++++--- .../monocular/chart-item/chart-item.component.ts | 7 +------ .../monocular/shared/services/charts.service.ts | 4 ++++ .../tabs/catalog-tab/catalog-tab.component.spec.ts | 5 ++++- .../create-release/create-release.component.ts | 2 +- 9 files changed, 48 insertions(+), 19 deletions(-) diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/chart-view/monocular.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/chart-view/monocular.component.ts index f8cb93cdff..fd788562d4 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/chart-view/monocular.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/chart-view/monocular.component.ts @@ -1,8 +1,8 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; +import { ChartsService } from '../monocular/shared/services/charts.service'; import { createMonocularProviders } from '../monocular/stratos-monocular-providers.helpers'; -import { getMonocularEndpoint } from '../monocular/stratos-monocular.helper'; @Component({ @@ -19,7 +19,10 @@ export class MonocularChartViewComponent implements OnInit { public title = ''; - constructor(private route: ActivatedRoute) { } + constructor( + private route: ActivatedRoute, + private chartService: ChartsService + ) { } public ngOnInit() { @@ -34,7 +37,7 @@ export class MonocularChartViewComponent implements OnInit { if (!!parts.version) { breadcrumbs.push( - { value: this.title, routerLink: `/monocular/charts/${getMonocularEndpoint(this.route)}/${parts.repo}/${parts.chartName}` } + { value: this.title, routerLink: this.chartService.getChartSummaryRoute(parts.repo, parts.chartName, null, this.route) } ); this.title = parts.version; } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-generator.ts b/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-generator.ts index 8ac9945a41..23722669e3 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-generator.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-generator.ts @@ -1,6 +1,6 @@ import { Store } from '@ngrx/store'; import { of } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { catchError, first, map } from 'rxjs/operators'; import { IListAction } from '../../../../core/src/shared/components/list/list.component.types'; import { AppState } from '../../../../store/src/app-state'; @@ -10,6 +10,7 @@ import { StratosCatalogEntity, } from '../../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; import { StratosEndpointExtensionDefinition } from '../../../../store/src/entity-catalog/entity-catalog.types'; +import { stratosEntityCatalog } from '../../../../store/src/stratos-entity-catalog'; import { EndpointModel } from '../../../../store/src/types/endpoint.types'; import { IFavoriteMetadata } from '../../../../store/src/types/user-favorites.types'; import { helmEntityCatalog } from './helm-entity-catalog'; @@ -57,7 +58,16 @@ export function generateHelmEntities(): StratosBaseCatalogEntity[] { authTypes: [], endpointListActions: (store: Store): IListAction[] => { return [{ - action: (item: EndpointModel) => helmEntityCatalog.chart.api.synchronise(item), + action: (item: EndpointModel) => { + helmEntityCatalog.chart.api.synchronise(item).pipe( + catchError(() => null), // Be super safe to ensure we pass the first filter + first() + ).subscribe(res => { + if (res != null) { + stratosEntityCatalog.endpoint.api.getAll(); + } + }); + }, label: 'Synchronize', description: '', createVisible: row => row.pipe( diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-charts-list-config.service.ts b/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-charts-list-config.service.ts index cd41a26b0c..427a61b9ae 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-charts-list-config.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-charts-list-config.service.ts @@ -15,6 +15,7 @@ import { ListView } from '../../../../../store/src/actions/list.actions'; import { AppState } from '../../../../../store/src/app-state'; import { defaultHelmKubeListPageSize } from '../../kubernetes/list-types/kube-helm-list-types'; import { HELM_ENDPOINT_TYPE } from '../helm-entity-factory'; +import { ChartsService } from '../monocular/shared/services/charts.service'; import { MonocularChart } from '../store/helm.types'; import { MonocularChartCardComponent } from './monocular-chart-card/monocular-chart-card.component'; import { MonocularChartsDataSource } from './monocular-charts-data-source'; @@ -29,7 +30,14 @@ export class MonocularChartsListConfig implements IListConfig { { columnId: 'name', headerCell: () => 'Name', cellDefinition: { - getValue: (row) => row.name, + getValue: row => row.name, + getLink: row => this.chartsService.getChartSummaryRoute( + row.attributes.repo.name, + row.name, + null, + null, + row + ), }, sort: { type: 'sort', @@ -81,6 +89,7 @@ export class MonocularChartsListConfig implements IListConfig { store: Store, private endpointsService: EndpointsService, private route: ActivatedRoute, + private chartsService: ChartsService ) { this.initialised = endpointsService.endpoints$.pipe( diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.spec.ts index 00a67df285..450d53069d 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.spec.ts @@ -2,9 +2,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { PanelComponent } from '../../panel/panel.component'; +import { MockChartService } from '../../shared/services/chart.service.mock'; +import { ChartsService } from '../../shared/services/charts.service'; import { ChartDetailsVersionsComponent } from './chart-details-versions.component'; -/* tslint:disable:no-unused-variable */ describe('ChartDetailsVersionsComponent', () => { let component: ChartDetailsVersionsComponent; let fixture: ComponentFixture; @@ -12,7 +13,8 @@ describe('ChartDetailsVersionsComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ChartDetailsVersionsComponent, PanelComponent], - imports: [RouterTestingModule] + imports: [RouterTestingModule], + providers: [{ provide: ChartsService, useValue: new MockChartService() },] }) .compileComponents(); })); diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.ts index 553b028fc9..b69c28e3be 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.ts @@ -3,7 +3,7 @@ import { ActivatedRoute } from '@angular/router'; import { ChartAttributes } from '../../shared/models/chart'; import { ChartVersion } from '../../shared/models/chart-version'; -import { getMonocularEndpoint } from '../../stratos-monocular.helper'; +import { ChartsService } from '../../shared/services/charts.service'; @Component({ selector: 'app-chart-details-versions', @@ -15,11 +15,14 @@ export class ChartDetailsVersionsComponent { @Input() currentVersion: ChartVersion; showAllVersions: boolean; - constructor(private route: ActivatedRoute) { } + constructor( + private route: ActivatedRoute, + private chartService: ChartsService + ) { } goToVersionUrl(version: ChartVersion): string { const chart: ChartAttributes = version.relationships.chart.data; - return `/monocular/charts/${getMonocularEndpoint(this.route)}/${chart.repo.name}/${chart.name}/${version.attributes.version}`; + return this.chartService.getChartSummaryRoute(chart.repo.name, chart.name, version.attributes.version, this.route); } isSelected(version: ChartVersion): boolean { diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.ts index e17d058c7d..777f0e8d48 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.ts @@ -2,7 +2,6 @@ import { Component, OnInit } from '@angular/core'; import { Chart } from '../shared/models/chart'; import { ChartsService } from '../shared/services/charts.service'; -import { getMonocularEndpoint } from '../stratos-monocular.helper'; @Component({ selector: 'app-chart-item', @@ -27,11 +26,7 @@ export class ChartItemComponent implements OnInit { } goToDetailUrl(): string { - return `/monocular/charts/${getMonocularEndpoint(null, this.chart)}/${this.chart.attributes.repo.name}/${this.chart.attributes - .name}`; + return this.chartsService.getChartSummaryRoute(this.chart.attributes.repo.name, this.chart.attributes.name, null, null, this.chart); } - goToRepoUrl(): string { - return `/monocular/charts/${getMonocularEndpoint(null, this.chart)}/${this.chart.attributes.repo.name}`; - } } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts index 5d48d733b0..196f3f96eb 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts @@ -183,6 +183,10 @@ export class ChartsService { } } + getChartSummaryRoute(repoName: string, chartName: string, version?: string, route?: ActivatedRoute, chart?: Chart): string { + return `/monocular/charts/${getMonocularEndpoint(route, chart)}/${repoName}/${chartName}${version ? `/${version}` : ''}`; + } + /** * Store the charts in the cache * diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/tabs/catalog-tab/catalog-tab.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/helm/tabs/catalog-tab/catalog-tab.component.spec.ts index ae3ab61f3f..a772479936 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/tabs/catalog-tab/catalog-tab.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/tabs/catalog-tab/catalog-tab.component.spec.ts @@ -1,6 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { HelmBaseTestModules } from '../../helm-testing.module'; +import { MockChartService } from '../../monocular/shared/services/chart.service.mock'; +import { ChartsService } from '../../monocular/shared/services/charts.service'; import { CatalogTabComponent } from './catalog-tab.component'; describe('CatalogTabComponent', () => { @@ -12,7 +14,8 @@ describe('CatalogTabComponent', () => { imports: [ ...HelmBaseTestModules ], - declarations: [CatalogTabComponent] + declarations: [CatalogTabComponent], + providers: [{ provide: ChartsService, useValue: new MockChartService() },] }) .compileComponents(); })); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.ts index f044e91101..59d4a9d1d7 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.ts @@ -59,7 +59,7 @@ export class CreateReleaseComponent implements OnInit, OnDestroy { private chartsService: ChartsService, ) { const chart = this.route.snapshot.params as HelmChartReference; - this.cancelUrl = `/monocular/charts/${getMonocularEndpoint(this.route)}/${chart.repo}/${chart.name}/${chart.version}`; + this.cancelUrl = this.chartsService.getChartSummaryRoute(chart.repo, chart.name, chart.version, this.route); this.chart = chart; this.config = { From def5429db5e43c84e7e9ffc8f4f7788a2c645f6c Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 18 Sep 2020 09:32:34 +0100 Subject: [PATCH 630/648] Update mysel helper script (#492) --- build/tools/mysqldb-dev.sh | 40 +++++------------------------------- src/jetstream/config.dev | 9 ++++++++ src/jetstream/config.example | 1 + 3 files changed, 15 insertions(+), 35 deletions(-) diff --git a/build/tools/mysqldb-dev.sh b/build/tools/mysqldb-dev.sh index 94aefd952f..157410950b 100755 --- a/build/tools/mysqldb-dev.sh +++ b/build/tools/mysqldb-dev.sh @@ -8,39 +8,9 @@ echo $STRATOS_PATH docker stop stratos-db docker rm stratos-db -ID=$(docker run --name stratos-db -d -e MYSQL_ROOT_PASSWORD=dbroot -p 3306:3306 splatform/stratos-mariadb) -echo $ID +IMAGE=mariadb:10.2.33 -rm -f dbsetup.sql init.sh -cat < dbsetup.sql -CREATE DATABASE stratosdb; -CREATE USER stratos IDENTIFIED BY 'strat0s'; -GRANT ALL PRIVILEGES ON stratosdb.* to 'stratos'@'%'; -EOF - -cat < init.sh -#!/usr/bin/env bash -mysql -uroot -pdbroot < /dbsetup.sql -EOF - -chmod +x init.sh -docker cp ./dbsetup.sql ${ID}:/dbsetup.sql -docker cp ./init.sh ${ID}:/init.sh -rm dbsetup.sql init.sh - -#Fetch dockerize tool -wget https://github.com/jwilder/dockerize/releases/download/v0.6.1/dockerize-linux-amd64-v0.6.1.tar.gz -tar -xzvf dockerize-linux-amd64-v0.6.1.tar.gz -rm dockerize-linux-amd64-v0.6.1.tar.gz - -chmod +x ./dockerize -docker cp ./dockerize ${ID}:/dockerize -rm dockerize - -#We us wait for the internal socket to come up before running init script -echo "Just waiting a few seconds for the DB to come online ..." -docker exec -t ${ID} /dockerize -wait file:///var/run/mysql/mysql.sock -timeout 1m - -docker exec -t ${ID} /init.sh - -echo "Database ready" +# The container can set up users and a new database via env vars +ID=$(docker run --name stratos-db -d -e MYSQL_DATABASE=stratosdb -e MYSQL_ROOT_PASSWORD=dbroot -e MYSQL_PASSWORD=strat0s -p 3306:3306 ${IMAGE}) +echo "Launched container: $ID" +echo "Database started ... it may take a few seconds to complete initialization ..." diff --git a/src/jetstream/config.dev b/src/jetstream/config.dev index 27ec166bf3..d22fff32f8 100644 --- a/src/jetstream/config.dev +++ b/src/jetstream/config.dev @@ -59,3 +59,12 @@ LOCAL_USER_SCOPE=stratos.admin # Cache folder for Helm Charts HELM_CACHE_FOLDER=./.helm-cache + +# MariaDB database for local dev +# DATABASE_PROVIDER=mysql +# DB_HOST=127.0.0.1 +# DB_PORT=3306 +# DB_USER=stratos +# DB_PASSWORD=strat0s +# DB_DATABASE_NAME=stratosdb +# DB_SSL_MODE=disable \ No newline at end of file diff --git a/src/jetstream/config.example b/src/jetstream/config.example index 3723583c27..701192d521 100644 --- a/src/jetstream/config.example +++ b/src/jetstream/config.example @@ -58,6 +58,7 @@ INVITE_USER_CLIENT_SECRET= # DB_USER=stratos # DB_PASSWORD=strat0s # DB_DATABASE_NAME=stratosdb +# DB_SSL_MODE=disable # Postgresql database for local dev # DATABASE_PROVIDER=pgsql From a95b49b4c84362a4246ce4e0be39936e07306647 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Mon, 21 Sep 2020 08:43:50 +0100 Subject: [PATCH 631/648] Fix issue where charts list errors if only helm hub registered (#493) * Fix issue where charts list errored if both helm hub and helm repo's are registered - this was a regression - we shouldn't make the call if there's no registered endpoints anyway * Fix styling on install chart step 1 - apply flex to ensure form fields are correct width --- .../src/custom/helm/store/helm.effects.ts | 38 ++++++++++++------- .../create-release.component.html | 2 +- .../create-release.component.scss | 17 +-------- src/jetstream/go.sum | 1 + 4 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts index 351e05ffc1..931d26902c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts @@ -31,7 +31,13 @@ import { WrapperRequestActionSuccess, } from '../../../../../store/src/types/request.types'; import { helmEntityCatalog } from '../helm-entity-catalog'; -import { getHelmVersionId, getMonocularChartId, HELM_ENDPOINT_TYPE, HELM_HUB_ENDPOINT_TYPE } from '../helm-entity-factory'; +import { + getHelmVersionId, + getMonocularChartId, + HELM_ENDPOINT_TYPE, + HELM_HUB_ENDPOINT_TYPE, + HELM_REPO_ENDPOINT_TYPE, +} from '../helm-entity-factory'; import { Chart } from '../monocular/shared/models/chart'; import { stratosMonocularEndpointGuid } from '../monocular/stratos-monocular.helper'; import { @@ -144,13 +150,10 @@ export class HelmEffects { const helmHubEndpoint = helmEndpoints.find(endpoint => endpoint.sub_type === HELM_HUB_ENDPOINT_TYPE); // See https://github.com/SUSE/stratos/issues/466. It would be better to use the standard proxy for this request and go out to all - // valid helm sub types - const stratosMonocular = this.httpClient.get(`/pp/${this.proxyAPIVersion}/chartsvc/v1/charts`); - const helmHubMonocular = helmHubEndpoint ? this.createHelmHubRequest(helmHubEndpoint.guid) : of({ data: [] }); - + // valid helm sub types instead of making two requests here return combineLatest([ - stratosMonocular, - helmHubMonocular + this.createHelmRepoRequest(helmEndpoints), + this.createHelmHubRequest(helmHubEndpoint) ]).pipe( map(res => mergeMonocularChartResponses(entityKey, res)), mergeMap((response: NormalizedResponse) => [new WrapperRequestActionSuccess(response, action)]), @@ -352,12 +355,21 @@ export class HelmEffects { }; } - private createHelmHubRequest(endpointId: string): Observable { - return this.httpClient.get(`/pp/${this.proxyAPIVersion}/chartsvc/v1/charts`, { - headers: { - 'x-cap-cnsi-list': endpointId - } - }).pipe(map(res => addMonocularId(endpointId, res))); + private createHelmHubRequest(helmHubEndpoint: EndpointModel): Observable { + return helmHubEndpoint ? + this.httpClient.get(`/pp/${this.proxyAPIVersion}/chartsvc/v1/charts`, { + headers: { + 'x-cap-cnsi-list': helmHubEndpoint.guid + } + }).pipe(map(res => addMonocularId(helmHubEndpoint.guid, res))) : + of({ data: [] }); + } + + private createHelmRepoRequest(helmEndpoints: EndpointModel[]): Observable { + const helmRepoEndpoints = helmEndpoints.find(endpoint => endpoint.sub_type === HELM_REPO_ENDPOINT_TYPE); + return helmRepoEndpoints ? + this.httpClient.get(`/pp/${this.proxyAPIVersion}/chartsvc/v1/charts`) : + of({ data: [] }); } private makeRequest( diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.html index 000f286a25..8be2ac3295 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.html @@ -3,7 +3,7 @@ -
+ Select the Kubernetes cluster to install to diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.scss index cf807defaa..7b6b2917ab 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.scss @@ -1,18 +1,3 @@ -:host { - flex: 1; -} - .helm-create-release { - &__heading { - align-items: center; - display: flex; - } - - &__title { - flex: 1; - font-size: 14px; - } - &__button { - height: 36px; - } + flex: 1; } diff --git a/src/jetstream/go.sum b/src/jetstream/go.sum index 32e2f9c513..9488234375 100644 --- a/src/jetstream/go.sum +++ b/src/jetstream/go.sum @@ -256,6 +256,7 @@ github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= From 8173e371ed6861a6ce7b04cddb0e3d63ed424144 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 22 Sep 2020 11:22:11 +0100 Subject: [PATCH 632/648] Fix issue where invalid yaml throws an error (#500) --- .../chart-values-editor/chart-values-editor.component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts index ff0ace0e14..e3f2484b45 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts @@ -167,7 +167,7 @@ export class ChartValuesEditorComponent implements OnInit, OnDestroy, AfterViewI this.schema = schema; if (values !== null) { this.chartValuesYaml = values as string; - this.chartValues = yaml.safeLoad(values); + this.chartValues = yaml.safeLoad(values, { json: true }); // Set the form to the chart values initially, so if the user does nothing, they get the defaults this.initialFormData = this.chartValues; } @@ -232,7 +232,7 @@ export class ChartValuesEditorComponent implements OnInit, OnDestroy, AfterViewI // Form -> Editor // Only copy if there is not an error - otherwise keep the invalid yaml from the editor that needs fixing if (!this.yamlError) { - const codeYaml = yaml.safeLoad(this.code || '{}'); + const codeYaml = yaml.safeLoad(this.code || '{}', { json: true }); const data = mergeObjects(codeYaml, this.formData); this.code = this.getDiff(data); this.codeOnEnter = this.code; @@ -250,7 +250,7 @@ export class ChartValuesEditorComponent implements OnInit, OnDestroy, AfterViewI } // Parse as json - const json = yaml.safeLoad(this.code || '{}'); + const json = yaml.safeLoad(this.code || '{}', { json: true }); // Must be an object, otherwise it was not valid if (typeof (json) !== 'object') { throw new Error('Invalid YAML'); @@ -415,7 +415,7 @@ export class ChartValuesEditorComponent implements OnInit, OnDestroy, AfterViewI // Update the code editor to only show the YAML that contains the differences with the values.yaml diff() { this.confirmDialog.open(this.overwriteDiffValuesConfirmation, () => { - const userValues = yaml.safeLoad(this.code); + const userValues = yaml.safeLoad(this.code, { json: true }); this.code = this.getDiff(userValues); }); } From ec8bb95b72ae03bb9191e2b9a01fea98e2d45459 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 22 Sep 2020 12:06:13 +0100 Subject: [PATCH 633/648] Fix duplicated repo name ids (#495) * Fix filter by helm chart source - To reproduce add bitnami helm repo endpoint named `bitnami`, add helm hub , try to filter by helm repo endpoint and nothing changes - the helm repo bitnami charts were being overwritten by the helm hub bitnami charts as they were keyed the same in the store - Fix is to ensure charts are keyed with their source * Display helm hub label in chart summary view --- .../src/custom/helm/helm-entity-factory.ts | 15 +++++---- .../chart-details.component.html | 3 +- .../chart-details/chart-details.component.ts | 7 ++++- .../src/custom/helm/store/helm.effects.ts | 31 ++++++++++--------- 4 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-factory.ts b/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-factory.ts index 6cdf2402a9..3449e60e33 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-factory.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-factory.ts @@ -1,15 +1,13 @@ import { Schema, schema } from 'normalizr'; import { EntitySchema } from '../../../../store/src/helpers/entity-schema'; +import { stratosMonocularEndpointGuid } from './monocular/stratos-monocular.helper'; import { HelmVersion, MonocularChart } from './store/helm.types'; export const helmVersionsEntityType = 'helmVersions'; export const monocularChartsEntityType = 'monocularCharts'; export const monocularChartVersionsEntityType = 'monocularChartVersions'; -export const getMonocularChartId = (entity: MonocularChart) => entity.id; -export const getHelmVersionId = (entity: HelmVersion) => entity.endpointId; - export const HELM_ENDPOINT_TYPE = 'helm'; export const HELM_REPO_ENDPOINT_TYPE = 'repo'; export const HELM_HUB_ENDPOINT_TYPE = 'hub'; @@ -39,19 +37,24 @@ export class HelmEntitySchema extends EntitySchema { entityCache[monocularChartsEntityType] = new HelmEntitySchema( monocularChartsEntityType, {}, - { idAttribute: getMonocularChartId } + { + idAttribute: (entity: MonocularChart) => { + const monocularPrefix = entity.monocularEndpointId || stratosMonocularEndpointGuid; + return monocularPrefix + '/' + entity.id; + } + } ); entityCache[helmVersionsEntityType] = new HelmEntitySchema( helmVersionsEntityType, {}, - { idAttribute: getHelmVersionId } + { idAttribute: (entity: HelmVersion) => entity.endpointId } ); entityCache[monocularChartVersionsEntityType] = new HelmEntitySchema( monocularChartVersionsEntityType, {}, - { idAttribute: getMonocularChartId } + { idAttribute: (entity: MonocularChart) => entity.id } ); export function helmEntityFactory(key: string): EntitySchema { diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.html b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.html index 31c68dc996..f89ef9ce0a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.html @@ -5,8 +5,7 @@

Sorry, we couldn't find the chart

+ [subTitle]="chartSubTitle" [subText]="chart.attributes.description" [imagePath]="iconUrl">
diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.ts index 5d63a62325..49f0f11661 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.ts @@ -6,7 +6,7 @@ import { Chart } from '../shared/models/chart'; import { ChartVersion } from '../shared/models/chart-version'; import { ChartsService } from '../shared/services/charts.service'; import { ConfigService } from '../shared/services/config.service'; -import { getMonocularEndpoint } from '../stratos-monocular.helper'; +import { getMonocularEndpoint, stratosMonocularEndpointGuid } from '../stratos-monocular.helper'; @Component({ selector: 'app-chart-details', @@ -21,6 +21,7 @@ export class ChartDetailsComponent implements OnInit { currentVersion: ChartVersion; iconUrl: string; titleVersion: string; + chartSubTitle: string; loadingDelay: any; @@ -43,6 +44,10 @@ export class ChartDetailsComponent implements OnInit { this.loading = false; this.initing = false; this.chart = chart; + this.chartSubTitle = chart.attributes.repo.name; + if (getMonocularEndpoint(this.route, chart) !== stratosMonocularEndpointGuid) { + this.chartSubTitle = 'Helm Hub - ' + this.chartSubTitle; + } const version = params.version || this.chart.relationships.latestChartVersion.data.version; this.chartsService.getVersion(repo, chartName, version).pipe(first()) .subscribe(chartVersion => { diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts index 931d26902c..7a05a8c357 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts @@ -18,6 +18,7 @@ import { import { ClearPaginationOfType, ResetPaginationOfType } from '../../../../../store/src/actions/pagination.actions'; import { AppState } from '../../../../../store/src/app-state'; import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; +import { EntitySchema } from '../../../../../store/src/helpers/entity-schema'; import { isJetstreamError } from '../../../../../store/src/jetstream'; import { ApiRequestTypes } from '../../../../../store/src/reducers/api-request-reducer/request-helpers'; import { endpointOfTypeSelector } from '../../../../../store/src/selectors/endpoint.selectors'; @@ -31,13 +32,7 @@ import { WrapperRequestActionSuccess, } from '../../../../../store/src/types/request.types'; import { helmEntityCatalog } from '../helm-entity-catalog'; -import { - getHelmVersionId, - getMonocularChartId, - HELM_ENDPOINT_TYPE, - HELM_HUB_ENDPOINT_TYPE, - HELM_REPO_ENDPOINT_TYPE, -} from '../helm-entity-factory'; +import { HELM_ENDPOINT_TYPE, HELM_HUB_ENDPOINT_TYPE, HELM_REPO_ENDPOINT_TYPE } from '../helm-entity-factory'; import { Chart } from '../monocular/shared/models/chart'; import { stratosMonocularEndpointGuid } from '../monocular/stratos-monocular.helper'; import { @@ -58,7 +53,11 @@ type MonocularChartsResponse = { data: Chart[]; }; -const mapMonocularChartResponse = (entityKey: string, response: MonocularChartsResponse): NormalizedResponse => { +const mapMonocularChartResponse = ( + entityKey: string, + response: MonocularChartsResponse, + schema: EntitySchema +): NormalizedResponse => { const base: NormalizedResponse = { entities: { [entityKey]: {} }, result: [] @@ -66,7 +65,7 @@ const mapMonocularChartResponse = (entityKey: string, response: MonocularChartsR const items = response.data as Array; const processedData: NormalizedResponse = items.reduce((res, data) => { - const id = getMonocularChartId(data); + const id = schema.getId(data); res.entities[entityKey][id] = data; // Promote the name to the top-level object for simplicity data.name = data.attributes.name; @@ -76,12 +75,16 @@ const mapMonocularChartResponse = (entityKey: string, response: MonocularChartsR return processedData; }; -const mergeMonocularChartResponses = (entityKey: string, responses: MonocularChartsResponse[]): NormalizedResponse => { +const mergeMonocularChartResponses = ( + entityKey: string, + responses: MonocularChartsResponse[], + schema: EntitySchema +): NormalizedResponse => { const combined = responses.reduce((res, response) => { res.data = res.data.concat(response.data); return res; }, { data: [] }); - return mapMonocularChartResponse(entityKey, combined); + return mapMonocularChartResponse(entityKey, combined, schema); }; const addMonocularId = (endpointId: string, response: MonocularChartsResponse): MonocularChartsResponse => { @@ -155,7 +158,7 @@ export class HelmEffects { this.createHelmRepoRequest(helmEndpoints), this.createHelmHubRequest(helmHubEndpoint) ]).pipe( - map(res => mergeMonocularChartResponses(entityKey, res)), + map(res => mergeMonocularChartResponses(entityKey, res, action.entity[0])), mergeMap((response: NormalizedResponse) => [new WrapperRequestActionSuccess(response, action)]), catchError(error => { const { status, message } = HelmEffects.createHelmError(error); @@ -199,7 +202,7 @@ export class HelmEffects { endpointId: endpoint, ...endpointData }; - processedData.entities[entityKey][getHelmVersionId(version)] = version; + processedData.entities[entityKey][action.entity[0].getId(version)] = version; processedData.result.push(endpoint); }); return processedData; @@ -221,7 +224,7 @@ export class HelmEffects { const items = response.data as Array; const processedData = items.reduce((res, data) => { - const id = getMonocularChartId(data); + const id = action.entity[0].getId(data); res.entities[entityKey][id] = data; // Promote the name to the top-level object for simplicity data.name = data.attributes.name; From ac2e8b717ebeafb51326a22712445ec699f9ec15 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 22 Sep 2020 13:37:03 +0100 Subject: [PATCH 634/648] Fix issue where kube terminal adds helm hub as a repository (#501) --- .../plugins/kubernetes/terminal/helpers.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/jetstream/plugins/kubernetes/terminal/helpers.go b/src/jetstream/plugins/kubernetes/terminal/helpers.go index b34caf9a9a..634cfff1ab 100644 --- a/src/jetstream/plugins/kubernetes/terminal/helpers.go +++ b/src/jetstream/plugins/kubernetes/terminal/helpers.go @@ -22,6 +22,11 @@ import ( corev1 "k8s.io/client-go/kubernetes/typed/core/v1" ) +const ( + helmEndpointType = "helm" + helmRepoEndpointType = "repo" +) + // PodCreationData stores the clients and names used to create pod and secret type PodCreationData struct { Namespace string @@ -123,7 +128,7 @@ func (k *KubeTerminal) createPod(c echo.Context, kubeConfig, kubeVersion string, podSpec.Spec.EnableServiceLinks = &off podSpec.Spec.RestartPolicy = "Never" podSpec.Spec.DNSPolicy = "Default" - + volumeMountsSpec := make([]v1.VolumeMount, 1) volumeMountsSpec[0].Name = "kubeconfig" volumeMountsSpec[0].MountPath = "/home/stratos/.stratos" @@ -139,7 +144,7 @@ func (k *KubeTerminal) createPod(c echo.Context, kubeConfig, kubeVersion string, containerSpec[0].Env = make([]v1.EnvVar, 1) containerSpec[0].Env[0].Name = "K8S_VERSION" containerSpec[0].Env[0].Value = kubeVersion - + podSpec.Spec.Containers = containerSpec volumesSpec := make([]v1.Volume, 1) @@ -168,7 +173,7 @@ func (k *KubeTerminal) createPod(c echo.Context, kubeConfig, kubeVersion string, for { status, err := podClient.Get(pod.Name, statusOptions) if err == nil && status.Status.Phase == "Running" { - break; + break } timeout = timeout - 1 @@ -219,8 +224,10 @@ func getHelmRepoSetupScript(portalProxy interfaces.PortalProxy) string { } for _, ep := range endpoints { - if ep.CNSIType == "helm" { - str += fmt.Sprintf("helm repo add %s %s > /dev/null\n", ep.Name, ep.APIEndpoint) + if ep.CNSIType == helmEndpointType && ep.SubType == helmRepoEndpointType { + // Remove spaces from the name + name := strings.ReplaceAll(ep.Name, " ", "_") + str += fmt.Sprintf("helm repo add %s %s > /dev/null\n", name, ep.APIEndpoint) } } @@ -252,7 +259,7 @@ func (k *KubeTerminal) getKubeVersion(endpointID, userID string) (string, error) // Get the version number - remove any 'v' perfix or '+' suffix version := nodes.Items[0].Status.NodeInfo.KubeletVersion reg, err := regexp.Compile("[^0-9\\.]+") - if err == nil { + if err == nil { version = reg.ReplaceAllString(version, "") } parts := strings.Split(version, ".") From 4d36e00e57b6d4225c0aa4a80fb0e02a33234f71 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 22 Sep 2020 16:39:18 +0100 Subject: [PATCH 635/648] Fix issue with Schemas on Helm Hub (#503) * Fix issue where chart download link was not absolute - Expected url, got chart file name - We now check for this, if url is not absoulte assume filename and append to repo url * Fix issue with Schemas on Helm Hub * Update following changes in #503 * Fix issue with missing schema on upgrade for local chart * Fix failing unit test Co-authored-by: Richard Cox --- .../chart-details-info.component.ts | 6 +- .../shared/services/charts.service.ts | 60 +++++++++++++++- .../src/custom/helm/store/helm.types.ts | 1 + .../chart-values-editor.component.html | 2 +- .../chart-values-editor.component.ts | 4 ++ .../create-release.component.spec.ts | 1 + .../create-release.component.ts | 14 ++-- .../tabs/helm-release-helper.service.ts | 10 +-- .../upgrade-release.component.spec.ts | 5 ++ .../upgrade-release.component.ts | 26 ++++--- .../kubernetes/workloads/workload.utils.ts | 9 --- .../kubernetes/workloads/workloads.module.ts | 6 +- src/jetstream/plugins/monocular/chart.go | 2 +- src/jetstream/plugins/monocular/chart_svc.go | 68 +++++++++++++++++++ src/jetstream/plugins/monocular/main.go | 15 ++-- 15 files changed, 183 insertions(+), 46 deletions(-) delete mode 100644 src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workload.utils.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.ts index fad154c9d7..7a273df5e6 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.ts @@ -26,7 +26,7 @@ export class ChartDetailsInfoComponent implements OnInit { @Input() set currentVersion (version: ChartVersion) { this._currentVersion = version; if (version) { - this.getSchema(this._currentVersion); + this.getSchema(this._currentVersion, this.chart); } } @@ -69,8 +69,8 @@ export class ChartDetailsInfoComponent implements OnInit { ); } - private getSchema(currentVersion: ChartVersion) { - this.chartsService.getChartSchema(currentVersion).pipe( + private getSchema(currentVersion: ChartVersion, chart: Chart) { + this.chartsService.getChartSchema(currentVersion, chart).pipe( first(), catchError(() => of(null)) ).subscribe(schema => { diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts index 196f3f96eb..80590f39ba 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts @@ -7,6 +7,7 @@ import { catchError, map, tap } from 'rxjs/operators'; import { getMonocularEndpoint, stratosMonocularEndpointGuid } from '../../stratos-monocular.helper'; import { Chart } from '../models/chart'; import { ChartVersion } from '../models/chart-version'; +import { RepoAttributes } from '../models/repo'; import { ConfigService } from './config.service'; @@ -22,7 +23,6 @@ export class ChartsService { config: ConfigService, private route: ActivatedRoute, ) { - this.hostname = `${config.backendHostname}/chartsvc`; this.cacheCharts = {}; this.hostname = '/pp/v1/chartsvc'; } @@ -136,8 +136,53 @@ export class ChartsService { * @param version Chart version * @return An observable that will be the json schema */ - getChartSchema(chartVersion: ChartVersion): Observable { - return chartVersion.attributes.schema ? this.http.get(`${this.hostname}${chartVersion.attributes.schema}`) : of(null); + getChartSchema(chartVersion: ChartVersion, chart: Chart): Observable { + const url = this.getChartSchemaURL(chartVersion, chart.attributes.name, chart.attributes.repo); + return url ? this.http.get(url) : of(null); + } + + // Get the URL for obtaining a Chart's schema + getChartSchemaURL(chartVersion: ChartVersion, name: string, repo: RepoAttributes): string { + // Helm Hub does not give us the schema information so we have to use an additional backend API to fetch the chart and check + if (chartVersion.attributes.schema === undefined) { + let url = this.getChartURL(chartVersion, repo); + url = btoa(url); + return `/pp/v1/monocular/schema/${name}/${url}`; + } + + // We have the schema URL, so we can fetch that directly + return chartVersion.attributes.schema ? `${this.hostname}${chartVersion.attributes.schema}` : null; + } + + getChartURL(chartVersion: ChartVersion, repo?: RepoAttributes): string { + const firstUrl = this.getFirstChartUrl(chartVersion); + if (firstUrl.length > 0) { + // Check if url is absolute, if not assume it's a filename + if (!firstUrl.startsWith('http://') && !firstUrl.startsWith('https://')) { + const repoUrl = repo ? repo.url : ''; + return repoUrl || this.getChartRepoUrl(chartVersion) + '/' + firstUrl; + } + } + return firstUrl; + } + + private getFirstChartUrl(chart: ChartVersion): string { + if (chart && chart.attributes && chart.attributes.urls && chart.attributes.urls.length > 0) { + return chart.attributes.urls[0]; + } + return ''; + } + + private getChartRepoUrl(chart: ChartVersion): string { + if (chart && + chart.relationships && + chart.relationships.chart && + chart.relationships.chart.data && + chart.relationships.chart.data.repo + ) { + return chart.relationships.chart.data.repo.url; + } + return ''; } /** @@ -168,6 +213,15 @@ export class ChartsService { ); } + getVersionFromEndpoint(endpoint: string, repo: string, chartName: string, version: string): Observable { + const requestArgs = { headers: { 'x-cap-cnsi-list': endpoint !== stratosMonocularEndpointGuid ? endpoint :'' } }; + return this.http.get( + `${this.hostname}/v1/charts/${repo}/${chartName}/versions/${version}`, requestArgs).pipe( + map(this.extractData), + catchError(this.handleError) + ); + } + /** * Get the URL for retrieving the chart's icon * diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.types.ts b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.types.ts index fc79d7039c..0bee35fc0e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.types.ts +++ b/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.types.ts @@ -46,6 +46,7 @@ export enum HelmStatus { } export interface HelmChartReference { + endpoint?: string; name: string; repo: string; version: string; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.html index 2baa79aaf0..2b5bf1b3fd 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.html @@ -1,5 +1,5 @@ -
+
Loading ...
diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts index e3f2484b45..d7621a5e9b 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts @@ -98,6 +98,8 @@ export class ChartValuesEditorComponent implements OnInit, OnDestroy, AfterViewI // Observable - are we still loading resources? public loading$: Observable; + public initing = true; + // Observable for tracking if the Monaco editor has loaded private monacoLoaded$ = new BehaviorSubject(false); @@ -191,6 +193,8 @@ export class ChartValuesEditorComponent implements OnInit, OnDestroy, AfterViewI map(([schema, values, loaded]) => !loaded), startWith(true) ); + + this.initing = false; } ngAfterViewInit(): void { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.spec.ts index 00adc0ba34..9de5331552 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.spec.ts @@ -47,6 +47,7 @@ describe('CreateReleaseComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(CreateReleaseComponent); + httpMock.expectOne('/pp/v1/chartsvc/v1/charts/undefined/undefined/versions/undefined'); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.ts index 59d4a9d1d7..438c58c404 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.ts @@ -19,7 +19,6 @@ import { HelmChartReference, HelmInstallValues } from '../../../helm/store/helm. import { kubeEntityCatalog } from '../../kubernetes-entity-catalog'; import { KUBERNETES_ENDPOINT_TYPE } from '../../kubernetes-entity-factory'; import { KubernetesNamespace } from '../../store/kube.types'; -import { getFirstChartUrl } from '../workload.utils'; import { ChartValuesConfig, ChartValuesEditorComponent } from './../chart-values-editor/chart-values-editor.component'; @Component({ @@ -62,10 +61,13 @@ export class CreateReleaseComponent implements OnInit, OnDestroy { this.cancelUrl = this.chartsService.getChartSummaryRoute(chart.repo, chart.name, chart.version, this.route); this.chart = chart; - this.config = { - valuesUrl: `/pp/v1/chartsvc/v1/assets/${chart.repo}/${chart.name}/versions/${chart.version}/values.yaml`, - schemaUrl: `/pp/v1/chartsvc/v1/assets/${chart.repo}/${chart.name}/versions/${chart.version}/values.schema.json`, - }; + // Fetch the Chart Version metadata so we can get the correct URL for the Chart's JSON Schema + this.chartsService.getVersion(this.chart.repo, this.chart.name, this.chart.version).pipe(first()).subscribe(ch => { + this.config = { + valuesUrl: `/pp/v1/monocular/values/${this.chart.endpoint}/${this.chart.repo}/${chart.name}/${this.chart.version}`, + schemaUrl: this.chartsService.getChartSchemaURL(ch, ch.relationships.chart.data.name, ch.relationships.chart.data.repo) + }; + }); this.setupDetailsStep(); } @@ -238,7 +240,7 @@ export class CreateReleaseComponent implements OnInit, OnDestroy { throw new Error('Could not get Chart URL'); } // Add the chart url into the values - values.chartUrl = getFirstChartUrl(chartInfo); + values.chartUrl = this.chartsService.getChartURL(chartInfo); if (values.chartUrl.length === 0) { throw new Error('Could not get Chart URL'); } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts index d3af46d384..93dffcc4e4 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts @@ -257,21 +257,17 @@ export class HelmReleaseHelperService { // We don't know which Helm repository it came from, so use the name and sources to match private isProbablySameChart(a: ChartMetadata, b: ChartMetadata): boolean { // Basic properties must be the same - if ((a.name !== b.name) || (a.sources.length !== b.sources.length)) { + if (a.name !== b.name) { return false; } - // Sources must match + // Must have at least one source in common let count = 0; a.sources.forEach(source => { count += b.sources.findIndex((s) => s === source) === -1 ? 0 : 1; }); - if (count !== a.sources.length) { - return false; - } - - return true; + return count > 0; } } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.spec.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.spec.ts index f77dc54235..c3151ef19b 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.spec.ts @@ -1,5 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { MockChartService } from '../../../helm/monocular/shared/services/chart.service.mock'; +import { ChartsService } from '../../../helm/monocular/shared/services/charts.service'; +import { ConfigService } from '../../../helm/monocular/shared/services/config.service'; import { HelmReleaseProviders, KubeBaseGuidMock } from '../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; import { WorkloadsBaseTestingModule } from '../workloads.testing.module'; @@ -20,6 +23,8 @@ describe('UpgradeReleaseComponent', () => { KubernetesEndpointService, KubeBaseGuidMock, ...HelmReleaseProviders, + { provide: ChartsService, useValue: new MockChartService() }, + { provide: ConfigService, useValue: { appName: 'appName' } }, ] }) .compileComponents(); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts index 43d7af5f25..8d4b789437 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts @@ -1,7 +1,7 @@ import { Component, ViewChild } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; -import { Observable, of } from 'rxjs'; +import { combineLatest, Observable, of } from 'rxjs'; import { filter, first, map, pairwise, tap } from 'rxjs/operators'; import { @@ -10,12 +10,13 @@ import { StepOnNextResult, } from '../../../../../../core/src/shared/components/stepper/step/step.component'; import { ActionState } from '../../../../../../store/src/reducers/api-request-reducer/types'; +import { ChartsService } from '../../../helm/monocular/shared/services/charts.service'; +import { createMonocularProviders } from '../../../helm/monocular/stratos-monocular-providers.helpers'; import { stratosMonocularEndpointGuid } from '../../../helm/monocular/stratos-monocular.helper'; import { HelmUpgradeValues, MonocularVersion } from '../../../helm/store/helm.types'; import { ChartValuesConfig, ChartValuesEditorComponent } from '../chart-values-editor/chart-values-editor.component'; import { HelmReleaseHelperService } from '../release/tabs/helm-release-helper.service'; import { HelmReleaseGuid } from '../workload.types'; -import { getFirstChartUrl } from '../workload.utils'; import { workloadsEntityCatalog } from './../workloads-entity-catalog'; import { ReleaseUpgradeVersionsListConfig } from './release-version-list-config'; @@ -33,7 +34,8 @@ import { ReleaseUpgradeVersionsListConfig } from './release-version-list-config' deps: [ ActivatedRoute ] - } + }, + ...createMonocularProviders() ] }) export class UpgradeReleaseComponent { @@ -54,7 +56,8 @@ export class UpgradeReleaseComponent { constructor( store: Store, - public helper: HelmReleaseHelperService + public helper: HelmReleaseHelperService, + private chartsService: ChartsService, ) { this.cancelUrl = `/workloads/${this.helper.guid}`; @@ -91,14 +94,19 @@ export class UpgradeReleaseComponent { onNext = (): Observable => { const chart = this.version.relationships.chart.data; const version = this.version.attributes.version; + const endpointID = this.monocularEndpointId || stratosMonocularEndpointGuid; + // Fetch the release metadata so that we have the values used to install the current release - return this.helper.release$.pipe( + return combineLatest( + [this.helper.release$, this.chartsService.getVersionFromEndpoint(endpointID, chart.repo.name, chart.name, version)] + ).pipe( first(), - tap(release => { + tap(([release, chartVersionDetail]) => { + const schemaUrl = this.chartsService.getChartSchemaURL(chartVersionDetail, chart.name, chart.repo); this.config = { - schemaUrl: `/pp/v1/chartsvc/v1/assets/${chart.repo.name}/${chart.name}/versions/${version}/values.schema.json`, - valuesUrl: `/pp/v1/chartsvc/v1/assets/${chart.repo.name}/${chart.name}/versions/${version}/values.yaml`, + schemaUrl, + valuesUrl: `/pp/v1/monocular/values/${endpointID}/${chart.repo.name}/${chart.name}/${version}`, releaseValues: release.config }; }), @@ -129,7 +137,7 @@ export class UpgradeReleaseComponent { version: this.version.attributes.version, }, monocularEndpoint: this.monocularEndpointId === stratosMonocularEndpointGuid ? null : this.monocularEndpointId, - chartUrl: getFirstChartUrl(this.version) + chartUrl: this.chartsService.getChartURL(this.version) }; // Make the request diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workload.utils.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workload.utils.ts deleted file mode 100644 index 7c8fb71baa..0000000000 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workload.utils.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ChartVersion } from '../../helm/monocular/shared/models/chart-version'; - -// Get first URL for a Chart or return empty string if none -export function getFirstChartUrl(chart: ChartVersion): string { - if (chart && chart.attributes && chart.attributes.urls && chart.attributes.urls.length > 0) { - return chart.attributes.urls[0]; - } - return ''; -} diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts index 7359657da2..341c83d740 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts @@ -14,6 +14,7 @@ import { HelmReleaseTabBaseComponent } from './release/helm-release-tab-base/hel import { HelmReleaseAnalysisTabComponent, } from './release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component'; +import { HelmReleaseHistoryTabComponent } from './release/tabs/helm-release-history-tab/helm-release-history-tab.component'; import { HelmReleaseNotesTabComponent } from './release/tabs/helm-release-notes-tab/helm-release-notes-tab.component'; import { HelmReleasePodsTabComponent } from './release/tabs/helm-release-pods/helm-release-pods-tab.component'; import { @@ -22,12 +23,11 @@ import { import { HelmReleaseServicesTabComponent } from './release/tabs/helm-release-services/helm-release-services-tab.component'; import { HelmReleaseSummaryTabComponent } from './release/tabs/helm-release-summary-tab/helm-release-summary-tab.component'; import { HelmReleaseValuesTabComponent } from './release/tabs/helm-release-values-tab/helm-release-values-tab.component'; +import { WorkloadLiveReloadComponent } from './release/workload-live-reload/workload-live-reload.component'; import { HelmReleasesTabComponent } from './releases-tab/releases-tab.component'; import { WorkloadsStoreModule } from './store/workloads.store.module'; import { UpgradeReleaseComponent } from './upgrade-release/upgrade-release.component'; import { WorkloadsRouting } from './workloads.routing'; -import { HelmReleaseHistoryTabComponent } from './release/tabs/helm-release-history-tab/helm-release-history-tab.component'; -import { WorkloadLiveReloadComponent } from './release/workload-live-reload/workload-live-reload.component'; // Default config for the Monaco edfior const monacoConfig: NgxMonacoEditorConfig = { @@ -68,7 +68,7 @@ const monacoConfig: NgxMonacoEditorConfig = { HelmReleaseCardComponent ], providers: [ - DatePipe + DatePipe, ] }) export class WorkloadsModule { } diff --git a/src/jetstream/plugins/monocular/chart.go b/src/jetstream/plugins/monocular/chart.go index 70e9c4a763..3b486aff12 100644 --- a/src/jetstream/plugins/monocular/chart.go +++ b/src/jetstream/plugins/monocular/chart.go @@ -51,7 +51,7 @@ type ChartVersion struct { URLs []string `json:"urls"` Readme string `json:"readme,omitempty"` Values string `json:"values,omitempty"` - Schema string `json:"schema,omitempty"` + Schema string `json:"schema"` } //RepoCheck describes the state of a repository in terms its current checksum and last update time. diff --git a/src/jetstream/plugins/monocular/chart_svc.go b/src/jetstream/plugins/monocular/chart_svc.go index 2913536a52..79a192bebf 100644 --- a/src/jetstream/plugins/monocular/chart_svc.go +++ b/src/jetstream/plugins/monocular/chart_svc.go @@ -1,6 +1,7 @@ package monocular import ( + "encoding/base64" "errors" "fmt" "net/http" @@ -197,6 +198,73 @@ func (m *Monocular) getChartAndVersionFile(c echo.Context) error { return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Can not find file %s for the specified chart", filename)) } +func (m *Monocular) getChartValues(c echo.Context) error { + endpointID := c.Param("endpoint") + repo := c.Param("repo") + chartName := c.Param("name") + version := c.Param("version") + + // Built in Monocular + if endpointID == "default" { + filename := "values.yaml" + log.Debugf("Get chart file: %s", filename) + chart, err := m.ChartStore.GetChart(repo, chartName, version) + if err != nil { + return err + } + if m.cacheChart(*chart) == nil { + return c.File(path.Join(m.getChartCacheFolder(*chart), filename)) + } + return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Can not find file %s for the specified chart", filename)) + } + + // Helm Hub + // Change the URL and then forward on + p := fmt.Sprintf("/chartsvc/v1/assets/%s/%s/versions/%s/values.yaml", repo, chartName, version) + monocularEndpoint, err := m.validateExternalMonocularEndpoint(endpointID) + if monocularEndpoint == nil || err != nil { + return echo.NewHTTPError(http.StatusBadRequest, errors.New("No monocular endpoint")) + } + + return m.proxyToMonocularInstance(c, monocularEndpoint, p) +} + +// Check to see if the given chart URL has a schema +func (m *Monocular) checkForJsonSchema(c echo.Context) error { + log.Debug("checkForJsonSchema called") + + chartName := c.Param("name") + encodedChartURL := c.Param("encodedChartURL") + url, err := base64.StdEncoding.DecodeString(encodedChartURL) + if err != nil { + return err + } + + chartURL := string(url) + + chartCachePath := path.Join(m.CacheFolder, "schemas", encodedChartURL) + if err := m.ensureFolder(chartCachePath); err != nil { + log.Warnf("checkForJsonSchema: Could not create folder for chart downloads: %+v", err) + return err + } + + // We can delete the Chart archive - don't need it anymore + defer os.RemoveAll(chartCachePath) + + archiveFile := path.Join(chartCachePath, "chart.tgz") + if err := m.downloadFile(archiveFile, chartURL); err != nil { + return fmt.Errorf("Could not download chart from: %s - %+v", chartURL, err) + } + + // Now extract the files we need + filenames := []string{"values.schema.json"} + if err := extractArchiveFiles(archiveFile, chartName, chartCachePath, filenames); err != nil { + return fmt.Errorf("Could not extract files from chart archive: %s - %+v", archiveFile, err) + } + + return c.File(path.Join(chartCachePath, "values.schema.json")) +} + // This is the simpler version that returns just enough data needed for the Charts list view // This is a slight cheat - the response is not as complete as the Monocular API, but includes // enough for the UI and saves us having to pull out all of the Chart.yaml files diff --git a/src/jetstream/plugins/monocular/main.go b/src/jetstream/plugins/monocular/main.go index b5c46f2e58..9315381a2b 100644 --- a/src/jetstream/plugins/monocular/main.go +++ b/src/jetstream/plugins/monocular/main.go @@ -171,6 +171,9 @@ func (m *Monocular) AddSessionGroupRoutes(echoGroup *echo.Group) { // however cannot be done for things like img src echoGroup.Any("/monocular/:guid/chartsvc/*", m.handleMonocularInstance) + echoGroup.Any("/monocular/schema/:name/:encodedChartURL", m.checkForJsonSchema) + echoGroup.Any("/monocular/values/:endpoint/:repo/:name/:version", m.getChartValues) + echoGroup.POST("/chartrepos/:guid", m.syncRepo) echoGroup.POST("/chartrepos/status", m.getRepoStatuses) @@ -290,7 +293,6 @@ func (m *Monocular) baseHandleMonocularInstance(c echo.Context, monocularEndpoin // by the 'authHandler' associated with the endpoint OR defaults to an OAuth request. For this case there's no auth at all so falls over. // Tracked in https://github.com/SUSE/stratos/issues/466 - url := monocularEndpoint.APIEndpoint path := c.Request().URL.Path log.Debug("URL to monocular requested: %v", path) if strings.Index(path, stratosPrefix) == 0 { @@ -308,13 +310,18 @@ func (m *Monocular) baseHandleMonocularInstance(c echo.Context, monocularEndpoin parts = parts[2:] } - // Bring all back together - url.Path += "/" + strings.Join(parts, "/") + path = "/" + strings.Join(parts, "/") } + + return m.proxyToMonocularInstance(c, monocularEndpoint, path) +} + +func (m *Monocular) proxyToMonocularInstance(c echo.Context, monocularEndpoint *interfaces.CNSIRecord, path string) error { + url := monocularEndpoint.APIEndpoint log.Debugf("URL to monocular: %v", url.String()) + url.Path += path req, err := http.NewRequest(c.Request().Method, url.String(), nil) - removeBreakingHeaders(c.Request(), req) client := &http.Client{Timeout: 30 * time.Second} From 9742e5d665be01f4a55be3fd69b8f18b8b157973 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 22 Sep 2020 16:51:15 +0100 Subject: [PATCH 636/648] Merge upstream (#504) * Ensure filename/no filename text in connect by file dialog is aligned (#4474) - needs porting back * Add typed entity access and `custom-src` removal to change log (#4496) * Fixes #4335: Create Stratos static web site with better documentation (#4446) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * * Review comments fix * Fix broken link to an image in frontend extensions doc * Final tweaks Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Richard Cox Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Richard Cox * Fix and update customization docs (#4478) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * Apply new theming process to acme example * First pass at customization docs, most of themeing done * Final pass at customization docs * Merge fixes Co-authored-by: Veerapuram Varadhan Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update base README, point to website (#4488) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * Apply new theming process to acme example * First pass at customization docs, most of themeing done * Final pass at customization docs * Update base README, point to website Co-authored-by: Veerapuram Varadhan Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update developer docs (#4489) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * Apply new theming process to acme example * First pass at customization docs, most of themeing done * Final pass at customization docs * Update base README, point to website * Update developer docs Still need an overhall, but updated to remove incorrect data and apply website context Co-authored-by: Veerapuram Varadhan Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix docs (#4497) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * Apply new theming process to acme example * First pass at customization docs, most of themeing done * Final pass at customization docs * Update base README, point to website * Update developer docs Still need an overhall, but updated to remove incorrect data and apply website context * Fix PR template Co-authored-by: Veerapuram Varadhan Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Ensure images dependent on techPreview flag are included in imagelist.txt (#4500) * Improve clean-symlinks script (#4509) * Add support for including breaking changes in the changelog * Improve clean-symlinks script * Update changelog.sh * Update changelog.sh * Remove trailing line * Remove tailing line * Change nginx ciphers and make configurable via helm chart values (#4507) * Change nginx ciphers and make configurable via helm chart values * Add support for including breaking changes in the changelog * Improve clean-symlinks script * Update changelog.sh * Update changelog.sh * Remove trailing line * Remove tailing line * Fix bug with cert path patching * Fig bug where protocols not patched * Update version to 4.0.1, add change log (#4514) * Update version to 4.0.1, add change log * Update package-lock * Fix deploy from gitlab using a group's repo (#4479) - only supported user based repos - fixes #4153 * Add additional time ranges to base metrics range selector (#4480) - fixes https://github.com/SUSE/stratos/issues/415 * Add some time saving comments to cf permissions checker (#4508) * Move tab-nav and xsrf module source files to the src folder (#4518) * Move tab-nav files * Move xsrf module * Fix GitHub branch limit (#4510) * Ensure we fetch all repo's and branches when deploying github apps * Ensure we only fetch branches once - SUSE/stratos vs suse/stratos - ensure branches are treated as 'local' lists * Increate page size for github commits and gitlab requests * Remove use of nodejs util module (#4521) * Remove use of nodejs util module * Fix comment typo * Remove dependencies between store and core that have crept in (#4517) * Remove dependencies between store and core that have crept in * Fix issue with unit tests * Import fixes and unit test fix * Fix store testing package (#4520) * Fix store testing package * Update public-api.ts Co-authored-by: Richard Cox Co-authored-by: Richard Cox * Fix the position of the header guide array (#4524) - shown when there are no registered endpoints (and some incoming scenarios) - position is determined by location of associated button in header - position of button can change given visibility of other buttons (notification bell, endpoint backup, etc) - now check positiion after a delay, add fade in to mask delay * Metrics: Ensure trailing slashes are ignored when comparing URLs (#4527) * Remove imports of the form 'frontend/....' (#4519) * Move tab-nav files * Move xsrf module * Remove imports of the form 'frontend/...' * Move endpopints-health-check.ts (#4530) * Add UMD Ids to external modules in store package (#4522) * Add UMB Ids to external modules in store package * Add moment * Address PR feedback * Improvements & more checks to autoscaler scheduled date tests (#4535) - allow more time to skip between date and time values in date fields - add a check to - add checks to ensure we have the correct number of scheduled date rules * Remove api driven views (#4537) * Remove logger service and action history (#4538) * Remove logger service * Missed a few and removed action history as it was not being used * Remove action history * A few more * Fix for removed method * Add support for API keys (#4515) * Add backend support for API keys * Add last_used field to API keys * Use secure random value as key secret * Add tests for ListAPIKeys and AddAPIKey * Cover the rest of psql_apikeys.go with tests * Refactor the code a bit * Storing SQL queries in a struct should ensure that `datastore.ModifySQLStatement` gets called on all of them. * A wrapper func around `db.Exec` reduces copypasta. * Actually call `InitRepositoryProvider` for API keys package * Add route handler tests using gomock * Actually use skipper in xsrfMiddleware; minor clean-up * Update moment imports to remove warning when building library (#4534) * Update moment imports to remove warning when building library * Fix references to moment-timezone and markdown - contains some changes to e2e tests as well, might need to be reverted * Fix unit test * Fix `import *` in e2e tests Co-authored-by: Richard Cox * Fix Helm upgrade bug (#4544) * Fix Helm upgrade bug * Fix unit tests * Store api_keys.last_used in UTC (#4541) * Add UI for Stratos API Keys (#4523) * Add backend support for API keys * Add last_used field to API keys * Add base api keys page * Add basic api key entity framework (untested) * Add a basic api keys list (untested, need to wire in properties/columns + actions) * Fix entity type related issues * Add basic way to create api key * Wire in delete to list * Improve 'no api keys' ux * Final tidy up * Other fixes * Fix unit tests * Add 'Last Used' column to API keys list * Don't flash up 'no entries' when we haven't loaded api keys yet * Fix last used sorting - takes into account timezone - use cacheing to cater for often called sort * Fix unit test after changes in master * Fix after moment change * Remove now unrequired sorting of api key last used date via moment Co-authored-by: Ivan Kapelyukhin * Add basic e2e tests for API Keys (#4536) * Add backend support for API keys * Add last_used field to API keys * Add base api keys page * Add basic api key entity framework (untested) * Add a basic api keys list (untested, need to wire in properties/columns + actions) * Fix entity type related issues * Add basic way to create api key * Wire in delete to list * Improve 'no api keys' ux * Final tidy up * Other fixes * Fix unit tests * Add basic e2e tests for api keys * Add 'Last Used' column to API keys list * Don't flash up 'no entries' when we haven't loaded api keys yet * Fix last used sorting - takes into account timezone - use cacheing to cater for often called sort * Fix unit test after changes in master * Fix after moment change * Beef up test, fix for initial empty state * Remove now unrequired sorting of api key last used date via moment Co-authored-by: Ivan Kapelyukhin * Bump docusaurus version and add support for versioning (#4506) * Bump docusaurus version and versioning - Bump docusaurus to latest 2.0 version - Fix errors thrown up by new linting process - Add version support, setup 4.0.0 - Enable dark mode and fix in home screen * Move status_updates back into root project docs folder * Fix links - fix dead links - ensure all links are relative (so work when versioned) * Remove version 4.0.0 * WIP * WIP * Completed * Temporarily remove docsVersionDropdown until first version added to internal-versions * Update website docs * Build fix for temp situation where there's no versions * Add custom version of 'not latest released docs' message * Improve versioning and dark mode toggle - Add `All Versions page` - Conditionally include versions in drop down - Improve dark mode toggle icons * Temporarily remove dependent on versions - will be added back in when 4.0 sha is known * Update Versions Process * Add 4.0.0 * Fix in `build`/`serve` world - worked fine in `start` world... * Update 4.0.0 with temp version * Multiple improvements - Fix clean git repo before run - Swizzle docs version drop down nav bar item, move in custom code - Improve docs (latest version must appear in drop down) * Changes following review * Reduce font in version warning * API Keys: Make feature configurable for different user types (#4540) * Add a special type and parsing for the new config option * Add APIKeysEnabled to /info, check it in the middleware * Add test coverage for apiKeyMiddleware * Block API keys endpoints if disabled in the config; add tests * Fix failing test * Add API_KEYS_ENABLED to the Helm chart * Add JSON schema to the Helm chart * Add docs for UAA SSO user permissions management (#4554) * Bump docusaurus version and versioning - Bump docusaurus to latest 2.0 version - Fix errors thrown up by new linting process - Add version support, setup 4.0.0 - Enable dark mode and fix in home screen * Move status_updates back into root project docs folder * Fix links - fix dead links - ensure all links are relative (so work when versioned) * Remove version 4.0.0 * WIP * WIP * Completed * Temporarily remove docsVersionDropdown until first version added to internal-versions * Update website docs * Build fix for temp situation where there's no versions * Add custom version of 'not latest released docs' message * Improve versioning and dark mode toggle - Add `All Versions page` - Conditionally include versions in drop down - Improve dark mode toggle icons * Temporarily remove dependent on versions - will be added back in when 4.0 sha is known * Update Versions Process * Add 4.0.0 * Fix in `build`/`serve` world - worked fine in `start` world... * Update 4.0.0 with temp version * Add troubleshooting section for SSO * Update SSO auth troubleshooting section * Add basic developers guide for working with helm (#4511) * Bump docusaurus version and versioning - Bump docusaurus to latest 2.0 version - Fix errors thrown up by new linting process - Add version support, setup 4.0.0 - Enable dark mode and fix in home screen * Move status_updates back into root project docs folder * Fix links - fix dead links - ensure all links are relative (so work when versioned) * Add basic developers guide for working with helm * Fix post merge issue * Enable/disable API keys UI given API keys config setting (#4559) * Add a special type and parsing for the new config option * Add APIKeysEnabled to /info, check it in the middleware * Add test coverage for apiKeyMiddleware * Block API keys endpoints if disabled in the config; add tests * Fix failing test * Add API_KEYS_ENABLED to the Helm chart * Add JSON schema to the Helm chart * Enable/disable API keys UI given API keys config setting * Changes following review * Fix unit tests Co-authored-by: Ivan Kapelyukhin * Enable linting for all packages (#4561) * Enable linting for all packages - lots to solve! - we may wish to exclude some files * Fix linting * Bump angular json schema form (#4564) * Bump angular json schema form * Handle bug where mat-error maring is too big - We may want to consider moving this upstream Co-authored-by: Richard Cox * Docs: Update internal versions & Automate future updates (#4558) * Docs: Update where 4.0.0 is built from, add 4.0.1 - means we can delete the old `docs-versioning` branch - both versions point to the same commit (when branch merged to master), but reduces user confusion when they can't find the docs for the latest released version. Going forward this will point directly to the release label * Automatically update website's internal-versions.json on new releases - Add new github action - Action will trigger on a release being published - Action looks at github `release` object associated with event and extracts a version label and git label - Use extracted values adds new value to internal-versions.json - Creates a PR with the change * Fix checkout issue * Update documentation-versioning.yml * Fix nightly tage reference and names (#4574) * [Security] Bump node-fetch from 2.6.0 to 2.6.1 (#4572) Bumps [node-fetch](https://github.com/bitinn/node-fetch) from 2.6.0 to 2.6.1. **This update includes a security fix.** - [Release notes](https://github.com/bitinn/node-fetch/releases) - [Changelog](https://github.com/node-fetch/node-fetch/blob/master/docs/CHANGELOG.md) - [Commits](https://github.com/bitinn/node-fetch/compare/v2.6.0...v2.6.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Merge changes from downstream (#4567) - json schema form mat-error styling fix - info & border style colors - desktop style fixes - changes to breadcrumbs - sidenav dark mode fix - new functionality for card-number-metric component - filter endpoint by type - more info in stepper onNext fn - SessionService with isTechPreview - Add reset pagination of type action/reducer - Fix cf endpoint sort by type bug * Reduce sizes for stepper padding and dialog content on desktop (#4573) * Moer style tweaks * Update mat-desktop.scss Co-authored-by: Richard Cox * Optionally reduce size of tile selector cards, apply to create endpoints and deploy app (#4571) * Improve tile selector sizing - Reduce width of tile by tweaking tile grid breakpoints - Switch down to 1 column later on when reducing width - Add intermediat 3 column step to reduce times when 2 columns are too large - Reduce height of tiles by about a 1/4 * Shrink size of github, git url and docker images in create app stepper * Helm Chart: Add NOTES.txt template & update chart.yaml (#4557) * Helm Chart Updates - Add NOTES template - Add maintainers and keywords to chart.yaml * Update helm install instruction in build.sh script to helm 3 * Helm Chart Notes Fixes/Updates * Changes following review * Changes following review * Address PR comments, fix whitespacing * Bump version and notes (#4576) * Bump version * Add release notes * Update CHANGELOG.md * Change version number to 4.1.0 * Revert "Change version number to 4.1.0" This reverts commit 78d8a920952a7786ef2df7dc188c448858b31b44. * Update tag to match v4 (#4582) * Update package-lock.json version to 4.1.0 (#4581) * Ensure service name is correct in helm chart NOTES.txt (#4585) * Ensure the api key secret db field is long enough (#4586) * Fix release tag issue (#4594) * Add more logging of all files to be archived (#4595) * Release Pipelines: Always read vars from git checkout (#4597) * Always read vars from git checkout * FIx create chart Co-authored-by: Veerapuram Varadhan Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Veerapuram Varadhan Co-authored-by: Ivan Kapelyukhin --- .../tasks/dev-releases/check-gh-release.yml | 9 ++--- deploy/ci/tasks/dev-releases/create-chart.yml | 10 +++-- .../tasks/dev-releases/generate-tag-files.yml | 39 +++++-------------- .../tasks/dev-releases/update-gh-release.yml | 8 ++-- deploy/ci/tasks/dev-releases/vars-helper.sh | 32 +++++++++++++++ .../console/templates/deployment.yaml | 2 +- src/jetstream/config.dev | 2 +- 7 files changed, 59 insertions(+), 43 deletions(-) create mode 100755 deploy/ci/tasks/dev-releases/vars-helper.sh diff --git a/deploy/ci/tasks/dev-releases/check-gh-release.yml b/deploy/ci/tasks/dev-releases/check-gh-release.yml index b9a23fc69f..64c34f6ddd 100644 --- a/deploy/ci/tasks/dev-releases/check-gh-release.yml +++ b/deploy/ci/tasks/dev-releases/check-gh-release.yml @@ -16,14 +16,13 @@ run: - | # Check that the Github release DOES NOT exist ROOT_DIR=${PWD} - VERSION=$(cat image-tag/v2-version) - FULL_VERSION=$(cat image-tag/v2-alpha-tag) - GIT_TAG=$(cat image-tag/v2-tag) STRATOS=${ROOT_DIR}/stratos + source "${STRATOS}/deploy/ci/tasks/dev-releases/vars-helper.sh" + source ${STRATOS}/deploy/ci/tasks/dev-releases/github-helper.sh # Check tagged release version is consistent with package.json version - TAG_RELEASE_VERSION=$(echo ${FULL_VERSION} | cut -d"-" -f1) + TAG_RELEASE_VERSION=$(echo ${LATEST_TAG} | cut -d"-" -f1) if [ "${TAG_RELEASE_VERSION}" != "${VERSION}" ]; then echo "Package.json version is not consistent with tag release version! ${TAG_RELEASE_VERSION} != ${VERSION}" exit 1 @@ -32,7 +31,7 @@ run: # Check that the release exists set +e - github-release info -t ${GIT_TAG} + github-release info -t ${RELEASE_TAG} RETVAL=$? set -e diff --git a/deploy/ci/tasks/dev-releases/create-chart.yml b/deploy/ci/tasks/dev-releases/create-chart.yml index 9cb8935fd7..55fb8f9eca 100644 --- a/deploy/ci/tasks/dev-releases/create-chart.yml +++ b/deploy/ci/tasks/dev-releases/create-chart.yml @@ -21,12 +21,14 @@ run: helm init --client-only ROOT_DIR=$PWD STRATOS=${ROOT_DIR}/stratos + source "${STRATOS}/deploy/ci/tasks/dev-releases/vars-helper.sh" + source ${STRATOS}/deploy/ci/tasks/dev-releases/create-chart-helper.sh HELM_REPO=${ROOT_DIR}/helm-repo/${HELM_REPO_PATH} - GIT_TAG=$(cat image-tag/v2-alpha-tag) - VERSION=$(cat image-tag/v2-version) - RELEASE_VERSION=$(cat image-tag/v2-tag) - COMMIT=$(cat image-tag/v2-commit) + + GIT_TAG=${LATEST_TAG} + RELEASE_VERSION=${RELEASE_TAG} + COMMIT=${COMMIT_HASH} # Required for setupAndPushChange commit message IMAGE_TAG=${GIT_TAG} diff --git a/deploy/ci/tasks/dev-releases/generate-tag-files.yml b/deploy/ci/tasks/dev-releases/generate-tag-files.yml index 18c0918107..1a56bae074 100644 --- a/deploy/ci/tasks/dev-releases/generate-tag-files.yml +++ b/deploy/ci/tasks/dev-releases/generate-tag-files.yml @@ -18,27 +18,12 @@ run: - | echo "Generate tag files started..." - cd stratos - - RELEASE_TAG=$(cat .git/ref) - - # RELEASE_TAG is the full tag that started the pipleinee, e.g. '2.0.0-rc.1' - VERSION=$(cat package.json | grep version | grep -Po "([0-9\.]?)*") - COMMIT_HASH=$(git log -1 --format="%h") - LATEST_TAG=$VERSION-${COMMIT_HASH} - SOURCE_CODE_REPO=$(git config --get remote.origin.url) + ROOT_DIR=${PWD} + STRATOS=${ROOT_DIR}/stratos + source "${STRATOS}/deploy/ci/tasks/dev-releases/vars-helper.sh" - echo "Got version $VERSION from package.json." - - echo "Got $LATEST_TAG as the latest git tag." + cd stratos - if [ ! -z ${TAG_SUFFIX} ]; then - if [ "${TAG_SUFFIX}" != "null" ]; then - echo "Adding tag suffix '$TAG_SUFFIX' to the latest tag." - LATEST_TAG=${LATEST_TAG}-${TAG_SUFFIX} - echo "The latest tag is now $LATEST_TAG." - fi - fi echo "Running store-git-metadata.sh..." ./build/store-git-metadata.sh @@ -73,17 +58,13 @@ run: echo "Created v2-alpha-tag, v2-version and build-args." - echo "Contents of build-args:" - cat build-args - - echo "Contents of ui-build-args:" - cat ui-build-args + echo "Contents of files to be packaged:" - echo "Contents of v2-alpha-tag:" - cat v2-alpha-tag - - echo "Contents of image-labels:" - cat image-labels + for f in *; do + echo "File: $f ______________________________________________________" + cat $f + echo "" + done set -x diff --git a/deploy/ci/tasks/dev-releases/update-gh-release.yml b/deploy/ci/tasks/dev-releases/update-gh-release.yml index 80a79a98ec..f3897c03d3 100644 --- a/deploy/ci/tasks/dev-releases/update-gh-release.yml +++ b/deploy/ci/tasks/dev-releases/update-gh-release.yml @@ -17,10 +17,12 @@ run: - | # Create Github release ROOT_DIR=${PWD} - VERSION=$(cat image-tag/v2-version) - FULL_VERSION=$(cat image-tag/v2-alpha-tag) - GIT_TAG=$(cat image-tag/v2-tag) STRATOS=${ROOT_DIR}/stratos + source "${STRATOS}/deploy/ci/tasks/dev-releases/vars-helper.sh" + + FULL_VERSION=${LATEST_TAG} + GIT_TAG=${RELEASE_TAG} + source ${STRATOS}/deploy/ci/tasks/dev-releases/github-helper.sh # Check tagged release version is consistent with package.json version diff --git a/deploy/ci/tasks/dev-releases/vars-helper.sh b/deploy/ci/tasks/dev-releases/vars-helper.sh new file mode 100755 index 0000000000..edc0c1cd16 --- /dev/null +++ b/deploy/ci/tasks/dev-releases/vars-helper.sh @@ -0,0 +1,32 @@ + +pushd stratos + +VERSION=$(cat package.json | grep version | grep -Po "([0-9\.]?)*") +COMMIT_HASH=$(git log -1 --format="%h") +LATEST_TAG=$VERSION-${COMMIT_HASH} +SOURCE_CODE_REPO=$(git config --get remote.origin.url) + +# Check that the RELEASE_TAG matches the version +RELEASE_TAG=$(git describe) +if [[ "${RELEASE_TAG}" != ${VERSION}* ]]; then + echo "Error: Can not get tag for this release - got ${RELEASE_TAG}" + exit 1 +fi + +if [ ! -z ${TAG_SUFFIX} ]; then + if [ "${TAG_SUFFIX}" != "null" ]; then + echo "Adding tag suffix '$TAG_SUFFIX' to the latest tag." + LATEST_TAG=${LATEST_TAG}-${TAG_SUFFIX} + echo "The latest tag is now $LATEST_TAG." + fi +fi + +popd + +set +x +echo "VERSION : ${VERSION}" +echo "COMMIT_HASH : ${COMMIT_HASH}" +echo "LATEST_TAG : ${LATEST_TAG}" +echo "SOURCE_CODE_REPO : ${SOURCE_CODE_REPO}" +echo "RELEASE_TAG : ${RELEASE_TAG}" +set -x diff --git a/deploy/kubernetes/console/templates/deployment.yaml b/deploy/kubernetes/console/templates/deployment.yaml index 40280e1ba8..919098add4 100644 --- a/deploy/kubernetes/console/templates/deployment.yaml +++ b/deploy/kubernetes/console/templates/deployment.yaml @@ -29,7 +29,7 @@ spec: type: RollingUpdate rollingUpdate: maxSurge: 0 - maxUnavailable: 1 + maxUnavailable: 1 selector: matchLabels: app.kubernetes.io/name: "stratos" diff --git a/src/jetstream/config.dev b/src/jetstream/config.dev index d22fff32f8..fa8c377f29 100644 --- a/src/jetstream/config.dev +++ b/src/jetstream/config.dev @@ -67,4 +67,4 @@ HELM_CACHE_FOLDER=./.helm-cache # DB_USER=stratos # DB_PASSWORD=strat0s # DB_DATABASE_NAME=stratosdb -# DB_SSL_MODE=disable \ No newline at end of file +# DB_SSL_MODE=disable From ad9149fd6d13868fa8747f47ab7a9bc6e3871404 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Tue, 22 Sep 2020 19:21:36 +0100 Subject: [PATCH 637/648] Kube Terminal: Appears to hang if it takes a while to start up (#506) * Fix issue where chart download link was not absolute - Expected url, got chart file name - We now check for this, if url is not absoulte assume filename and append to repo url * Fix issue with Schemas on Helm Hub * Update following changes in #503 * Fix issue with missing schema on upgrade for local chart * Fix failing unit test * Ensure kube terminal does not hang if it takes a while to start up * Fix timeout issue Co-authored-by: Richard Cox --- .../plugins/kubernetes/terminal/helpers.go | 19 ++++-- .../plugins/kubernetes/terminal/start.go | 68 +++++++++++++------ 2 files changed, 62 insertions(+), 25 deletions(-) diff --git a/src/jetstream/plugins/kubernetes/terminal/helpers.go b/src/jetstream/plugins/kubernetes/terminal/helpers.go index 634cfff1ab..08f5a918a8 100644 --- a/src/jetstream/plugins/kubernetes/terminal/helpers.go +++ b/src/jetstream/plugins/kubernetes/terminal/helpers.go @@ -23,8 +23,9 @@ import ( ) const ( - helmEndpointType = "helm" - helmRepoEndpointType = "repo" + helmEndpointType = "helm" + helmRepoEndpointType = "repo" + startingProgressMessage = "Waiting for Kubernetes Terminal to start up ..." ) // PodCreationData stores the clients and names used to create pod and secret @@ -87,6 +88,8 @@ func (k *KubeTerminal) createPod(c echo.Context, kubeConfig, kubeVersion string, Type: "Opaque", } + sendProgressMessage(ws, startingProgressMessage) + setResourcMetadata(&secretSpec.ObjectMeta, sessionID) secretSpec.Data = make(map[string][]byte) @@ -98,6 +101,7 @@ func (k *KubeTerminal) createPod(c echo.Context, kubeConfig, kubeVersion string, secretSpec.Data["helm-setup"] = []byte(helmSetup) } + sendProgressMessage(ws, startingProgressMessage) _, err = secretClient.Create(secretSpec) if err != nil { log.Warnf("Kubernetes Terminal: Unable to create Secret: %+v", err) @@ -154,6 +158,8 @@ func (k *KubeTerminal) createPod(c echo.Context, kubeConfig, kubeVersion string, } podSpec.Spec.Volumes = volumesSpec + sendProgressMessage(ws, startingProgressMessage) + // Create a new pod pod, err := podClient.Create(podSpec) if err != nil { @@ -165,12 +171,12 @@ func (k *KubeTerminal) createPod(c echo.Context, kubeConfig, kubeVersion string, result.PodClient = podClient result.PodName = podName - sendProgressMessage(ws, "Waiting for Kubernetes Terminal to start up ...") - // Wait for the pod to be running timeout := 60 statusOptions := metav1.GetOptions{} for { + // This ensures we keep the web socket alive while the container is creating + sendProgressMessage(ws, startingProgressMessage) status, err := podClient.Get(pod.Name, statusOptions) if err == nil && status.Status.Phase == "Running" { break @@ -201,6 +207,11 @@ func setResourcMetadata(metadata *metav1.ObjectMeta, sessionID string) { // Cleanup the pod and secret func (k *KubeTerminal) cleanupPodAndSecret(podData *PodCreationData) error { + if podData == nil { + // Already been cleaned up + return nil + } + if len(podData.PodName) > 0 { //captureBashHistory(podData) podData.PodClient.Delete(podData.PodName, nil) diff --git a/src/jetstream/plugins/kubernetes/terminal/start.go b/src/jetstream/plugins/kubernetes/terminal/start.go index 63c90fe519..a37a26e9a8 100644 --- a/src/jetstream/plugins/kubernetes/terminal/start.go +++ b/src/jetstream/plugins/kubernetes/terminal/start.go @@ -41,6 +41,15 @@ type terminalSize struct { const ( // Time allowed to write a message to the peer. writeWait = 10 * time.Second + + // Time allowed to read the next pong message from the peer. + pongWait = 60 * time.Second + + // Send pings to peer with this period. Must be less than pongWait. + pingPeriod = (pongWait * 9) / 10 + + // Time to wait before force close on connection. + closeGracePeriod = 10 * time.Second ) // Start handles web-socket request to launch a Kubernetes Terminal @@ -60,7 +69,7 @@ func (k *KubeTerminal) Start(c echo.Context) error { if !ok { return errors.New("Could not get token") } - + // This is the kube config for the kubernetes endpoint that we want configured in the Terminal kubeConfig, err := k.Kube.GetKubeConfigForEndpoint(cnsiRecord.APIEndpoint.String(), tokenRecord, "") if err != nil { @@ -79,7 +88,10 @@ func (k *KubeTerminal) Start(c echo.Context) error { defer ws.Close() defer pingTicker.Stop() - // We are now in web socket land - we don't want any middleware to change the HTTP response + // At this point we aer using web sockets, so we can not return errors to the client as the connection + // has been upgraded to a web socket + + // We are now in web socket land - we don't want any middleware to change the HTTP response c.Set("Stratos-WebSocket", "true") // Send a message to say that we are creating the pod @@ -95,8 +107,8 @@ func (k *KubeTerminal) Start(c echo.Context) error { k.cleanupPodAndSecret(podData) // Send error message - sendProgressMessage(ws, "!" + err.Error()) - return err + sendProgressMessage(ws, "!"+err.Error()) + return nil } // API Endpoint to SSH/exec into a container @@ -131,28 +143,33 @@ func (k *KubeTerminal) Start(c echo.Context) error { stdoutDone := make(chan bool) go pumpStdout(ws, wsConn, stdoutDone) + go ping(ws, stdoutDone) // If the downstream connection is closed, close the other web socket as well - ws.SetCloseHandler(func (code int, text string) error { + ws.SetCloseHandler(func(code int, text string) error { wsConn.Close() + // Cleanup + k.cleanupPodAndSecret(podData) + podData = nil return nil }) + // Wait a while when reading - can take some time for the container to launch + ws.SetReadDeadline(time.Now().Add(pongWait)) + ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil }) + // Read the input from the web socket and pipe it to the SSH client for { _, r, err := ws.ReadMessage() if err != nil { - // Check to see if this was because the web socket was closed cleanly - closed := false - select { - case msg := <-stdoutDone: - closed = msg - } - if !closed { - log.Errorf("Kubernetes terminal: error reading message from web socket: %+v", err) - } - log.Debug("Kube Terminal cleaning up ....") + // Error reading - so clean up k.cleanupPodAndSecret(podData) + podData = nil + + ws.SetWriteDeadline(time.Now().Add(writeWait)) + ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) + time.Sleep(closeGracePeriod) + ws.Close() // No point returning an error - we've already upgraded to web sockets, so we can't use the HTTP response now return nil @@ -160,7 +177,6 @@ func (k *KubeTerminal) Start(c echo.Context) error { res := KeyCode{} json.Unmarshal(r, &res) - if res.Cols == 0 { slice := make([]byte, 1) slice[0] = 0 @@ -177,11 +193,6 @@ func (k *KubeTerminal) Start(c echo.Context) error { wsConn.WriteMessage(websocket.TextMessage, slice) } } - - // Cleanup - log.Error("Kubernetes Terminal is cleaning up") - - return k.cleanupPodAndSecret(podData) } func pumpStdout(ws *websocket.Conn, source *websocket.Conn, done chan bool) { @@ -202,3 +213,18 @@ func pumpStdout(ws *websocket.Conn, source *websocket.Conn, done chan bool) { } } } + +func ping(ws *websocket.Conn, done chan bool) { + ticker := time.NewTicker(pingPeriod) + defer ticker.Stop() + for { + select { + case <-ticker.C: + if err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait)); err != nil { + log.Errorf("Web socket ping error: %+v", err) + } + case <-done: + return + } + } +} From d62a3c5290039ae545b4731426606b7dade40380 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Wed, 23 Sep 2020 10:17:42 +0100 Subject: [PATCH 638/648] Release pipeline tag fix (#507) * Release pipeline tag fix * Fix suse pipeline --- deploy/ci/console-dev-releases.yml | 5 ++++- deploy/ci/console-helm-chart-pr.yml | 1 - deploy/ci/console-make-release.yml | 1 + deploy/ci/suse-console-dev-releases.yml | 5 ++++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/deploy/ci/console-dev-releases.yml b/deploy/ci/console-dev-releases.yml index e52789e24f..efcb887ad2 100644 --- a/deploy/ci/console-dev-releases.yml +++ b/deploy/ci/console-dev-releases.yml @@ -101,6 +101,7 @@ jobs: GIT_USER: ((concourse-user)) GIT_EMAIL: ((concourse-email)) GIT_PRIVATE_KEY: ((github-private-key)) + TAG_SUFFIX: ((tag-suffix)) - name: build-images plan: - get: stratos @@ -170,6 +171,7 @@ jobs: DOCKER_REGISTRY: ((docker-registry)) HELM_REPO_PATH: ((helm-repo-path)) HELM_REPO_BRANCH: ((helm-repo-branch)) + TAG_SUFFIX: ((tag-suffix)) - put: helm-chart-tarball params: file: helm-chart/*.tgz @@ -196,4 +198,5 @@ jobs: GITHUB_REPO: ((helm-repo-github-repository)) GIT_USER: ((concourse-user)) GIT_EMAIL: ((concourse-email)) - GIT_PRIVATE_KEY: ((github-private-key)) \ No newline at end of file + GIT_PRIVATE_KEY: ((github-private-key)) + TAG_SUFFIX: ((tag-suffix)) diff --git a/deploy/ci/console-helm-chart-pr.yml b/deploy/ci/console-helm-chart-pr.yml index bee5e837d4..a476d10cf2 100644 --- a/deploy/ci/console-helm-chart-pr.yml +++ b/deploy/ci/console-helm-chart-pr.yml @@ -40,7 +40,6 @@ jobs: GITHUB_REPO: ((helm-repo-github-repository)) GITHUB_TOKEN: ((github-access-token)) GIT_PRIVATE_KEY: ((github-private-key)) - GITHUB_TOKEN: ((github-access-token)) DOCKER_ORG: ((docker-organization)) DOCKER_REGISTRY: ((docker-registry)) HELM_REPO_PATH: ((helm-repo-path)) diff --git a/deploy/ci/console-make-release.yml b/deploy/ci/console-make-release.yml index d803858ce9..1a5856a2ed 100644 --- a/deploy/ci/console-make-release.yml +++ b/deploy/ci/console-make-release.yml @@ -80,3 +80,4 @@ jobs: HELM_RELEASE_REPO_FOLDER: ((release-helm-stable-folder)) HELM_RELEASE_REGISTRY_HOST: ((release-repository)) HELM_RELEASE_REGISTRY_ORG: ((release-repository-organization)) + TAG_SUFFIX: ((tag-suffix)) diff --git a/deploy/ci/suse-console-dev-releases.yml b/deploy/ci/suse-console-dev-releases.yml index e05f578318..e4af7f2fff 100644 --- a/deploy/ci/suse-console-dev-releases.yml +++ b/deploy/ci/suse-console-dev-releases.yml @@ -114,6 +114,7 @@ jobs: GIT_USER: ((concourse-user)) GIT_EMAIL: ((concourse-email)) GIT_PRIVATE_KEY: ((github-private-key)) + TAG_SUFFIX: ((tag-suffix)) - name: build-images plan: - get: stratos @@ -199,6 +200,7 @@ jobs: DOCKER_REGISTRY: ((docker-registry)) HELM_REPO_PATH: ((helm-repo-path)) HELM_REPO_BRANCH: ((helm-repo-branch)) + TAG_SUFFIX: ((tag-suffix)) - put: helm-chart-tarball params: file: helm-chart/*.tgz @@ -225,4 +227,5 @@ jobs: GITHUB_REPO: ((helm-repo-github-repository)) GIT_USER: ((concourse-user)) GIT_EMAIL: ((concourse-email)) - GIT_PRIVATE_KEY: ((github-private-key)) \ No newline at end of file + GIT_PRIVATE_KEY: ((github-private-key)) + TAG_SUFFIX: ((tag-suffix)) From 4e92362f5d6ceee22054e3606e4ba6265e3c109a Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 24 Sep 2020 09:48:03 +0100 Subject: [PATCH 639/648] Fix caasp labels (#510) * Fix created date issue * Fix issue with CaaSP info always appearing * Remove blank line --- .../kubernetes-node-link.component.html | 2 +- .../kubernetes-node-link.component.scss | 3 +++ .../kubernetes-node-summary-card.component.ts | 5 ++++- .../kubernetes/services/kubernetes-endpoint.service.ts | 9 ++++++--- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html index c425c5f39e..a32c03f58b 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html @@ -1,4 +1,4 @@ \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.scss b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.scss index 24cbdb0c59..f95dfc2f4d 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.scss +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.scss @@ -5,4 +5,7 @@ mat-icon { padding-left: 10px; } + &__help { + cursor: help; + } } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.ts index 8b088c3d70..51342da974 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.ts @@ -22,7 +22,10 @@ export class KubernetesNodeSummaryCardComponent { public kubeNodeService: KubernetesNodeService ) { this.caaspNode$ = this.kubeNodeService.nodeEntity$.pipe( - map(node => kubeEndpointService.getCaaspNodeData(node)), + map(node => { + const nodeData = kubeEndpointService.getCaaspNodeData(node); + return !!nodeData.version ? nodeData : null; + }), ); this.caaspNodeUpdates$ = this.caaspNode$.pipe( diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-endpoint.service.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-endpoint.service.ts index 2fe71f3803..1bf6932297 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-endpoint.service.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-endpoint.service.ts @@ -105,10 +105,13 @@ export class KubernetesEndpointService { return; } - if (!versions[nodeData.version]) { - versions[nodeData.version] = 0; + // Only has a version if it is a CaaSP node + if (nodeData.version) { + if (!versions[nodeData.version]) { + versions[nodeData.version] = 0; + } + versions[nodeData.version]++; } - versions[nodeData.version]++; info.updates += nodeData.updates ? 1 : 0; info.disruptiveUpdates += nodeData.disruptiveUpdates ? 1 : 0; From 07059799c219d4af7cd4c1782855f86bb873836f Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Thu, 24 Sep 2020 10:28:16 +0100 Subject: [PATCH 640/648] Only use diff values from form (#511) --- .../chart-values-editor/chart-values-editor.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts index d7621a5e9b..60b56a6bf5 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts +++ b/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts @@ -363,7 +363,8 @@ export class ChartValuesEditorComponent implements OnInit, OnDestroy, AfterViewI } public getValues(): object { - return (this.mode === EditorMode.JSonSchemaForm) ? this.formData : yaml.safeLoad(this.code); + // Always diff the form with the Chart Values to get only the changes that the user has made + return (this.mode === EditorMode.JSonSchemaForm) ? diffObjects(this.formData, this.chartValues) : yaml.safeLoad(this.code); } public copyValues() { From cf6f655e93f8e0fdc2832ee30303bb85c1d268b2 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Mon, 28 Sep 2020 09:03:31 +0100 Subject: [PATCH 641/648] Move suse specific login theme out of default login theme (#508) --- .../login/login-page/login-page.component.theme.scss | 6 ------ .../packages/suse-extensions/sass/_all-theme.scss | 2 ++ .../custom/suse-login/suse-login.component.theme.scss | 10 ++++++++++ 3 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.theme.scss diff --git a/src/frontend/packages/core/src/features/login/login-page/login-page.component.theme.scss b/src/frontend/packages/core/src/features/login/login-page/login-page.component.theme.scss index e6a56746e2..2b41be411b 100644 --- a/src/frontend/packages/core/src/features/login/login-page/login-page.component.theme.scss +++ b/src/frontend/packages/core/src/features/login/login-page/login-page.component.theme.scss @@ -1,6 +1,5 @@ @mixin login-page-theme($theme, $app-theme) { $primary: map-get($theme, primary); - $warn: map-get($theme, warn); .login { background-color: map-get($app-theme, app-background-color); @@ -12,9 +11,4 @@ } } - .suse-login-sso { - .suse-login__message { - color: mat-color($warn); - } - } } diff --git a/src/frontend/packages/suse-extensions/sass/_all-theme.scss b/src/frontend/packages/suse-extensions/sass/_all-theme.scss index 3d775747a6..3f1be2c34b 100644 --- a/src/frontend/packages/suse-extensions/sass/_all-theme.scss +++ b/src/frontend/packages/suse-extensions/sass/_all-theme.scss @@ -7,6 +7,7 @@ @import '../src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.theme'; @import '../src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.theme'; @import '../src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.theme'; +@import '../src/custom/suse-login/suse-login.component.theme'; @mixin apply-theme-suse-extensions($stratos-theme) { @@ -20,4 +21,5 @@ @include helm-release-summary-tab-theme($theme, $app-theme); @include kube-node-link-theme($theme, $app-theme); @include app-chart-values-editor-theme($theme, $app-theme); + @include suse-login-page-theme($theme, $app-theme); } diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.theme.scss b/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.theme.scss new file mode 100644 index 0000000000..c0b4f3f115 --- /dev/null +++ b/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.theme.scss @@ -0,0 +1,10 @@ + +@mixin suse-login-page-theme($theme, $app-theme) { + $warn: map-get($theme, warn); + + .suse-login-sso { + .suse-login__message { + color: mat-color($warn); + } + } +} From 4cbbc07efdb556e8ad19efb9712f6c1043a4f2aa Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 2 Oct 2020 13:16:00 +0100 Subject: [PATCH 642/648] Split out Kube code (with Helm & Workloads) into it's own package (#475) * Ensure filename/no filename text in connect by file dialog is aligned (#4474) - needs porting back * Add typed entity access and `custom-src` removal to change log (#4496) * Fixes #4335: Create Stratos static web site with better documentation (#4446) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * * Review comments fix * Fix broken link to an image in frontend extensions doc * Final tweaks Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Richard Cox Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Richard Cox * Fix and update customization docs (#4478) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * Apply new theming process to acme example * First pass at customization docs, most of themeing done * Final pass at customization docs * Merge fixes Co-authored-by: Veerapuram Varadhan Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update base README, point to website (#4488) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * Apply new theming process to acme example * First pass at customization docs, most of themeing done * Final pass at customization docs * Update base README, point to website Co-authored-by: Veerapuram Varadhan Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update developer docs (#4489) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * Apply new theming process to acme example * First pass at customization docs, most of themeing done * Final pass at customization docs * Update base README, point to website * Update developer docs Still need an overhall, but updated to remove incorrect data and apply website context Co-authored-by: Veerapuram Varadhan Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix docs (#4497) * Fixes #4335: Create Stratos static web site with better documentation structure using docusaurus. Initial commit. * 1) Re-organize some more existing doc files. 2) Re-organize links in the doc file to refer to the new document hierarchy. * Landing Page WIP * Documentation tweaks * Use better action names * More visual improvements to landing page * More doc and landing page improvements * Remove publish workflow for now * Typo fix * Moer documentation improvements * Remove extra title from helm doc * Fix script when used from npm * Improve layout on mobile devices * More documentation improvements * Fix typo * Merge downstream (#4441) * Merge src/frontend from downstream * Merge src/jetstream from jetstream * Remove examples/custom-src * Merge deploy from downstream Does not include changes to - deploy/all-in-one/* - deploy/aio-entrypoint.sh - deploy/Dockerfile.all-in-one * Merge build from downstream * Add missing merge items from deploy * Updates to package-lock * Remove fdescribe * Fix e2e core tests * Remove favicon from packages/core/src * Changes following review * Show all favorites for an endpoint favorite if there is only one (#4440) * Show all favorites for the endpoint favorite if there is only one * Missing changes * Merge downstream - JSON Viewer with dark mode & Header Fixes (#4444) * Fix json-viewer dark mode * Fix profile page and side nav top position following header diet - Fix side nav top position - Update fix for profile page to also work in non-desktop mode * Fix issues with tests not running if build upload fails (#4453) * Fix issues with tests not running if build upload fails * Fix script * One more fix for script * Fix white space at start of file * Improve autoscaler e2e logging (#4456) * Improve autoscaler e2e logging - it looks like the AS returns scaling events 1-2 mins after they occur, which is too late for the test - add additional logging to print out event table data in case of alternative events being raised - fix logging if wait for events times out - add hint in later test that depends on AS scaling event * Ensure only the schedule rule results in scaling events * Fix check-e2e-pr.sh for pr's from other repos (#4459) - switch from TRAVIS_PULL_REQUEST_SLUG to TRAVIS_REPO_SLUG * Convert Client Secret Input Fields to `password` (#4455) * Insecure tlsv10 and tlsv11 ciphers in Stratos UI, bsc#1173295 (#411) (#4460) Co-authored-by: Michal Jura * [Security] Bump codecov from 3.7.0 to 3.7.1 (#4457) Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1. **This update includes a security fix.** - [Release notes](https://github.com/codecov/codecov-node/releases) - [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> * Bump lodash from 4.17.15 to 4.17.19 (#4452) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Website update: Wed 22 Jul 2020 21:55:00 BST * Website update: Wed 22 Jul 2020 21:56:45 BST * Website update: Wed 22 Jul 2020 21:58:22 BST * Remove dist * FIx deploy script to remove old files * Moer tidy ups * Add CNAME file when publishing * Add talks doc * * Fix broken links due to reorganization of documents * Move Troubleshooting content from cloud-foundry deployment doc to cf-troubleshooting * * Fix Getting Started broken link in page footer * Only publish if there are website changes * Remove old doc * Publish website on merge * Apply new theming process to acme example * First pass at customization docs, most of themeing done * Final pass at customization docs * Update base README, point to website * Update developer docs Still need an overhall, but updated to remove incorrect data and apply website context * Fix PR template Co-authored-by: Veerapuram Varadhan Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Ensure images dependent on techPreview flag are included in imagelist.txt (#4500) * Improve clean-symlinks script (#4509) * Add support for including breaking changes in the changelog * Improve clean-symlinks script * Update changelog.sh * Update changelog.sh * Remove trailing line * Remove tailing line * Change nginx ciphers and make configurable via helm chart values (#4507) * Change nginx ciphers and make configurable via helm chart values * Add support for including breaking changes in the changelog * Improve clean-symlinks script * Update changelog.sh * Update changelog.sh * Remove trailing line * Remove tailing line * Fix bug with cert path patching * Fig bug where protocols not patched * Update version to 4.0.1, add change log (#4514) * Update version to 4.0.1, add change log * Update package-lock * Fix deploy from gitlab using a group's repo (#4479) - only supported user based repos - fixes #4153 * Add additional time ranges to base metrics range selector (#4480) - fixes https://github.com/SUSE/stratos/issues/415 * Add some time saving comments to cf permissions checker (#4508) * Move tab-nav and xsrf module source files to the src folder (#4518) * Move tab-nav files * Move xsrf module * Fix GitHub branch limit (#4510) * Ensure we fetch all repo's and branches when deploying github apps * Ensure we only fetch branches once - SUSE/stratos vs suse/stratos - ensure branches are treated as 'local' lists * Increate page size for github commits and gitlab requests * Remove use of nodejs util module (#4521) * Remove use of nodejs util module * Fix comment typo * Remove dependencies between store and core that have crept in (#4517) * Remove dependencies between store and core that have crept in * Fix issue with unit tests * Import fixes and unit test fix * Fix store testing package (#4520) * Fix store testing package * Update public-api.ts Co-authored-by: Richard Cox Co-authored-by: Richard Cox * Fix the position of the header guide array (#4524) - shown when there are no registered endpoints (and some incoming scenarios) - position is determined by location of associated button in header - position of button can change given visibility of other buttons (notification bell, endpoint backup, etc) - now check positiion after a delay, add fade in to mask delay * Metrics: Ensure trailing slashes are ignored when comparing URLs (#4527) * Remove imports of the form 'frontend/....' (#4519) * Move tab-nav files * Move xsrf module * Remove imports of the form 'frontend/...' * Move endpopints-health-check.ts (#4530) * Add UMD Ids to external modules in store package (#4522) * Add UMB Ids to external modules in store package * Add moment * Address PR feedback * Improvements & more checks to autoscaler scheduled date tests (#4535) - allow more time to skip between date and time values in date fields - add a check to - add checks to ensure we have the correct number of scheduled date rules * Remove api driven views (#4537) * Remove logger service and action history (#4538) * Remove logger service * Missed a few and removed action history as it was not being used * Remove action history * A few more * Fix for removed method * Add support for API keys (#4515) * Add backend support for API keys * Add last_used field to API keys * Use secure random value as key secret * Add tests for ListAPIKeys and AddAPIKey * Cover the rest of psql_apikeys.go with tests * Refactor the code a bit * Storing SQL queries in a struct should ensure that `datastore.ModifySQLStatement` gets called on all of them. * A wrapper func around `db.Exec` reduces copypasta. * Actually call `InitRepositoryProvider` for API keys package * Add route handler tests using gomock * Actually use skipper in xsrfMiddleware; minor clean-up * Update moment imports to remove warning when building library (#4534) * Update moment imports to remove warning when building library * Fix references to moment-timezone and markdown - contains some changes to e2e tests as well, might need to be reverted * Fix unit test * Fix `import *` in e2e tests Co-authored-by: Richard Cox * Improve presentation and fix issue with development versions * WIP * Fix Helm upgrade bug (#4544) * Fix Helm upgrade bug * Fix unit tests * Add support for Helm Upgrade and Helm history * Remove console logging * Add comment * Minor tweaks following self-review * WIP * fix install button * Fix actual helm install * Remove helm repos view - need to add endpoint type specific actions to endpoints list view (helm sync) * Store api_keys.last_used in UTC (#4541) * Show per endpoint type actions in endpoints table - use for helm sync * Tidy up registration step * Tidy up provider interceptor pattern * Tidy up services, fix charts list filter by repo/helm hub * Add endpoint unRegisterable, make helm types sub types, tidy up * Remove debug logging * Fix whitespace * Add UI for Stratos API Keys (#4523) * Add backend support for API keys * Add last_used field to API keys * Add base api keys page * Add basic api key entity framework (untested) * Add a basic api keys list (untested, need to wire in properties/columns + actions) * Fix entity type related issues * Add basic way to create api key * Wire in delete to list * Improve 'no api keys' ux * Final tidy up * Other fixes * Fix unit tests * Add 'Last Used' column to API keys list * Don't flash up 'no entries' when we haven't loaded api keys yet * Fix last used sorting - takes into account timezone - use cacheing to cater for often called sort * Fix unit test after changes in master * Fix after moment change * Remove now unrequired sorting of api key last used date via moment Co-authored-by: Ivan Kapelyukhin * Minor tidy ups * Add basic e2e tests for API Keys (#4536) * Add backend support for API keys * Add last_used field to API keys * Add base api keys page * Add basic api key entity framework (untested) * Add a basic api keys list (untested, need to wire in properties/columns + actions) * Fix entity type related issues * Add basic way to create api key * Wire in delete to list * Improve 'no api keys' ux * Final tidy up * Other fixes * Fix unit tests * Add basic e2e tests for api keys * Add 'Last Used' column to API keys list * Don't flash up 'no entries' when we haven't loaded api keys yet * Fix last used sorting - takes into account timezone - use cacheing to cater for often called sort * Fix unit test after changes in master * Fix after moment change * Beef up test, fix for initial empty state * Remove now unrequired sorting of api key last used date via moment Co-authored-by: Ivan Kapelyukhin * Fix compile issue * Fixed versions again, and bugs following subtype split * Fix unit tests * Add db migration script to update existing helm endpoints with repo subtype * Bump docusaurus version and add support for versioning (#4506) * Bump docusaurus version and versioning - Bump docusaurus to latest 2.0 version - Fix errors thrown up by new linting process - Add version support, setup 4.0.0 - Enable dark mode and fix in home screen * Move status_updates back into root project docs folder * Fix links - fix dead links - ensure all links are relative (so work when versioned) * Remove version 4.0.0 * WIP * WIP * Completed * Temporarily remove docsVersionDropdown until first version added to internal-versions * Update website docs * Build fix for temp situation where there's no versions * Add custom version of 'not latest released docs' message * Improve versioning and dark mode toggle - Add `All Versions page` - Conditionally include versions in drop down - Improve dark mode toggle icons * Temporarily remove dependent on versions - will be added back in when 4.0 sha is known * Update Versions Process * Add 4.0.0 * Fix in `build`/`serve` world - worked fine in `start` world... * Update 4.0.0 with temp version * Multiple improvements - Fix clean git repo before run - Swizzle docs version drop down nav bar item, move in custom code - Improve docs (latest version must appear in drop down) * Changes following review * Reduce font in version warning * Fix front-end unit tests * API Keys: Make feature configurable for different user types (#4540) * Add a special type and parsing for the new config option * Add APIKeysEnabled to /info, check it in the middleware * Add test coverage for apiKeyMiddleware * Block API keys endpoints if disabled in the config; add tests * Fix failing test * Add API_KEYS_ENABLED to the Helm chart * Add JSON schema to the Helm chart * Add docs for UAA SSO user permissions management (#4554) * Bump docusaurus version and versioning - Bump docusaurus to latest 2.0 version - Fix errors thrown up by new linting process - Add version support, setup 4.0.0 - Enable dark mode and fix in home screen * Move status_updates back into root project docs folder * Fix links - fix dead links - ensure all links are relative (so work when versioned) * Remove version 4.0.0 * WIP * WIP * Completed * Temporarily remove docsVersionDropdown until first version added to internal-versions * Update website docs * Build fix for temp situation where there's no versions * Add custom version of 'not latest released docs' message * Improve versioning and dark mode toggle - Add `All Versions page` - Conditionally include versions in drop down - Improve dark mode toggle icons * Temporarily remove dependent on versions - will be added back in when 4.0 sha is known * Update Versions Process * Add 4.0.0 * Fix in `build`/`serve` world - worked fine in `start` world... * Update 4.0.0 with temp version * Add troubleshooting section for SSO * Update SSO auth troubleshooting section * Add basic developers guide for working with helm (#4511) * Bump docusaurus version and versioning - Bump docusaurus to latest 2.0 version - Fix errors thrown up by new linting process - Add version support, setup 4.0.0 - Enable dark mode and fix in home screen * Move status_updates back into root project docs folder * Fix links - fix dead links - ensure all links are relative (so work when versioned) * Add basic developers guide for working with helm * Fix post merge issue * Always show upgrade button * Minor entity store type updates * Enable/disable API keys UI given API keys config setting (#4559) * Add a special type and parsing for the new config option * Add APIKeysEnabled to /info, check it in the middleware * Add test coverage for apiKeyMiddleware * Block API keys endpoints if disabled in the config; add tests * Fix failing test * Add API_KEYS_ENABLED to the Helm chart * Add JSON schema to the Helm chart * Enable/disable API keys UI given API keys config setting * Changes following review * Fix unit tests Co-authored-by: Ivan Kapelyukhin * Convert unRegisterable to registeredLimit * Changes following review * Fix display of helm type in favourite cards - favourite cards are NOT by subtype, so parent type must have label info * Multiple Fixes - Fix display of disconected text & box in endpoint favourite card - Don't go out to fetch helm schema if there's none defined - Fixes following merge * Align icons in workload summary page * Enable linting for all packages (#4561) * Enable linting for all packages - lots to solve! - we may wish to exclude some files * Fix linting * Fix lint failures * Address PR feedback * Revert * Remove description when checking for similar charts * Only show button when the helm chart is available * Bump angular json schema form (#4564) * Bump angular json schema form * Handle bug where mat-error maring is too big - We may want to consider moving this upstream Co-authored-by: Richard Cox * Docs: Update internal versions & Automate future updates (#4558) * Docs: Update where 4.0.0 is built from, add 4.0.1 - means we can delete the old `docs-versioning` branch - both versions point to the same commit (when branch merged to master), but reduces user confusion when they can't find the docs for the latest released version. Going forward this will point directly to the release label * Automatically update website's internal-versions.json on new releases - Add new github action - Action will trigger on a release being published - Action looks at github `release` object associated with event and extracts a version label and git label - Use extracted values adds new value to internal-versions.json - Creates a PR with the change * Fix checkout issue * Update documentation-versioning.yml * Improve types, start work on helm hub upgrade * Fixes following merge * Remove TODOs * Merge fixes, remove fdescribe already in master * Fix linting * Create kubernetes package * Fix imports - removal of custom folder * Fix unit tests * Fixes following review * Fix unit tests * Fix theming for suse login Co-authored-by: Veerapuram Varadhan Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Neil MacDougall Co-authored-by: Michal Jura Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Veerapuram Varadhan Co-authored-by: Ivan Kapelyukhin --- angular.json | 28 ++++++++++++++ package.json | 1 + .../logout-page/logout-page.component.spec.ts | 6 +-- .../assets/custom/aks.svg | 0 .../assets/custom/app_placeholder.svg | 0 .../assets/custom/caasp.png | Bin .../assets/custom/eks.svg | 0 .../assets/custom/gke.svg | 0 .../assets/custom/helm.svg | 0 .../assets/custom/help/en/connecting_gke.md | 0 .../assets/custom/k3s.svg | 0 .../assets/custom/kube_import.png | Bin .../assets/custom/kubernetes.svg | 0 .../assets/custom/placeholder.png | Bin .../packages/kubernetes/karma.conf.js | 8 ++++ src/frontend/packages/kubernetes/package.json | 16 ++++++++ .../packages/kubernetes/sass/_all-theme.scss | 24 ++++++++++++ .../helm/chart-view/monocular.component.html | 0 .../helm/chart-view/monocular.component.scss | 0 .../helm/chart-view/monocular.component.ts | 0 .../src}/helm/helm-entity-catalog.ts | 12 +++++- .../src}/helm/helm-entity-factory.ts | 2 +- .../src}/helm/helm-entity-generator.ts | 14 +++---- .../helm-hub-registration.component.html | 0 .../helm-hub-registration.component.scss | 0 .../helm-hub-registration.component.spec.ts | 6 +-- .../helm-hub-registration.component.ts | 6 +-- .../src}/helm/helm-testing.module.ts | 17 +++++---- .../src}/helm/helm.module.ts | 4 +- .../src}/helm/helm.routing.ts | 0 .../src}/helm/helm.setup.module.ts | 14 +++---- .../src}/helm/helm.store.module.ts | 0 .../monocular-chart-card.component.html | 0 .../monocular-chart-card.component.scss | 0 .../monocular-chart-card.component.spec.ts | 2 +- .../monocular-chart-card.component.theme.scss | 0 .../monocular-chart-card.component.ts | 2 +- .../monocular-charts-data-source.ts | 10 ++--- .../monocular-charts-list-config.service.ts | 10 ++--- .../monocular-tab-base.component.html | 0 .../monocular-tab-base.component.scss | 0 .../monocular-tab-base.component.spec.ts | 4 +- .../monocular-tab-base.component.ts | 0 .../src}/helm/monocular.interceptor.ts | 0 .../src}/helm/monocular/app.component.scss | 0 .../chart-details-info.component.html | 0 .../chart-details-info.component.scss | 0 .../chart-details-info.component.spec.ts | 0 .../chart-details-info.component.ts | 0 .../chart-details-readme.component.html | 0 .../chart-details-readme.component.scss | 0 .../chart-details-readme.component.spec.ts | 0 .../chart-details-readme.component.ts | 0 .../chart-details-usage.component.html | 0 .../chart-details-usage.component.scss | 0 .../chart-details-usage.component.spec.ts | 8 ++-- .../chart-details-usage.component.ts | 2 +- .../chart-details-versions.component.html | 0 .../chart-details-versions.component.scss | 0 .../chart-details-versions.component.spec.ts | 0 .../chart-details-versions.component.ts | 0 .../chart-details.component.html | 0 .../chart-details.component.scss | 0 .../chart-details.component.spec.ts | 6 +-- .../chart-details/chart-details.component.ts | 0 .../chart-index/chart-index.component.html | 0 .../chart-index/chart-index.component.scss | 0 .../chart-index/chart-index.component.spec.ts | 0 .../chart-index/chart-index.component.ts | 0 .../chart-item/chart-item.component.html | 0 .../chart-item/chart-item.component.scss | 0 .../chart-item/chart-item.component.spec.ts | 2 +- .../chart-item/chart-item.component.ts | 0 .../chart-list/chart-list.component.html | 0 .../chart-list/chart-list.component.scss | 0 .../chart-list/chart-list.component.spec.ts | 0 .../chart-list/chart-list.component.ts | 0 .../monocular/charts/charts.component.html | 0 .../monocular/charts/charts.component.scss | 0 .../monocular/charts/charts.component.spec.ts | 2 +- .../helm/monocular/charts/charts.component.ts | 0 .../list-filters/list-filters.component.html | 0 .../list-filters/list-filters.component.scss | 0 .../list-filters.component.spec.ts | 0 .../list-filters/list-filters.component.ts | 0 .../list-item/list-item.component.html | 0 .../list-item/list-item.component.scss | 0 .../list-item/list-item.component.spec.ts | 0 .../list-item/list-item.component.ts | 0 .../monocular/loader/loader.component.html | 0 .../monocular/loader/loader.component.scss | 0 .../monocular/loader/loader.component.spec.ts | 0 .../helm/monocular/loader/loader.component.ts | 0 .../src}/helm/monocular/monocular.module.ts | 2 +- .../helm/monocular/panel/panel.component.html | 0 .../helm/monocular/panel/panel.component.scss | 0 .../monocular/panel/panel.component.spec.ts | 0 .../helm/monocular/panel/panel.component.ts | 0 .../monocular/shared/models/chart-version.ts | 0 .../helm/monocular/shared/models/chart.ts | 0 .../monocular/shared/models/maintainer.ts | 0 .../src}/helm/monocular/shared/models/repo.ts | 0 .../src}/helm/monocular/shared/seo.data.ts | 0 .../shared/services/chart.service.mock.ts | 0 .../shared/services/charts.service.ts | 4 +- .../shared/services/config.service.ts | 0 .../monocular/shared/services/menu.service.ts | 0 .../shared/services/repos.service.ts | 2 +- .../stratos-monocular-providers.helpers.ts | 0 .../monocular/stratos-monocular.helper.ts | 0 .../src}/helm/store/helm.action-builders.ts | 4 +- .../src}/helm/store/helm.actions.ts | 6 +-- .../src}/helm/store/helm.effects.ts | 32 ++++++++-------- .../src}/helm/store/helm.types.ts | 0 .../catalog-tab/catalog-tab.component.html | 0 .../catalog-tab/catalog-tab.component.scss | 0 .../catalog-tab/catalog-tab.component.spec.ts | 0 .../tabs/catalog-tab/catalog-tab.component.ts | 2 +- .../custom => kubernetes/src}/helm/theme.scss | 0 .../src/kube-package-routing.module.ts} | 2 +- .../kubernetes/src/kube-package.module.ts | 22 +++++++++++ .../analysis-report-runner.component.html | 0 .../analysis-report-runner.component.scss | 0 .../analysis-report-runner.component.spec.ts | 8 ++-- .../analysis-report-runner.component.ts | 0 .../analysis-report-selector.component.html | 0 .../analysis-report-selector.component.scss | 0 ...analysis-report-selector.component.spec.ts | 6 +-- .../analysis-report-selector.component.ts | 2 +- .../analysis-report-viewer.component.html | 0 .../analysis-report-viewer.component.scss | 0 .../analysis-report-viewer.component.spec.ts | 0 .../analysis-report-viewer.component.ts | 0 .../kube-score-report-viewer.component.html | 0 .../kube-score-report-viewer.component.scss | 0 ...kube-score-report-viewer.component.spec.ts | 6 +-- .../kube-score-report-viewer.component.ts | 0 .../popeye-report-viewer.component.html | 0 .../popeye-report-viewer.component.scss | 0 .../popeye-report-viewer.component.spec.ts | 6 +-- .../popeye-report-viewer.component.ts | 0 .../resource-alert-preview.component.html | 0 .../resource-alert-preview.component.scss | 0 .../resource-alert-preview.component.spec.ts | 0 .../resource-alert-preview.component.ts | 2 +- .../resource-alert-view.component.html | 0 .../resource-alert-view.component.scss | 0 .../resource-alert-view.component.spec.ts | 6 +-- .../resource-alert-view.component.ts | 0 .../kubernetes-aws-auth-form.component.html | 0 .../kubernetes-aws-auth-form.component.scss | 0 ...kubernetes-aws-auth-form.component.spec.ts | 2 +- .../kubernetes-aws-auth-form.component.ts | 3 +- .../kubernetes-certs-auth-form.component.html | 0 .../kubernetes-certs-auth-form.component.scss | 0 ...bernetes-certs-auth-form.component.spec.ts | 2 +- .../kubernetes-certs-auth-form.component.ts | 3 +- ...kubernetes-config-auth-form.component.html | 0 ...kubernetes-config-auth-form.component.scss | 0 ...ernetes-config-auth-form.component.spec.ts | 2 +- .../kubernetes-config-auth-form.component.ts | 3 +- .../kubernetes-gke-auth-form.component.html | 0 .../kubernetes-gke-auth-form.component.scss | 0 ...kubernetes-gke-auth-form.component.spec.ts | 2 +- .../kubernetes-gke-auth-form.component.ts | 3 +- .../kube-config-auth.helper.ts | 10 ++--- .../kube-config-import.component.html | 0 .../kube-config-import.component.scss | 0 .../kube-config-import.component.spec.ts | 0 .../kube-config-import.component.ts | 26 ++++++------- ...-config-table-import-status.component.html | 0 ...-config-table-import-status.component.scss | 0 ...nfig-table-import-status.component.spec.ts | 0 ...be-config-table-import-status.component.ts | 4 +- .../kube-config-registration.component.html | 0 .../kube-config-registration.component.scss | 0 ...kube-config-registration.component.spec.ts | 0 .../kube-config-registration.component.ts | 0 .../kube-config-selection.component.html | 0 .../kube-config-selection.component.scss | 0 .../kube-config-selection.component.spec.ts | 0 .../kube-config-selection.component.ts | 10 ++--- .../kube-config-table-cert.component.html | 0 .../kube-config-table-cert.component.scss | 0 .../kube-config-table-cert.component.spec.ts | 0 .../kube-config-table-cert.component.ts | 12 +++--- .../kube-config-table-name.component.html | 0 .../kube-config-table-name.component.scss | 0 .../kube-config-table-name.component.spec.ts | 2 +- .../kube-config-table-name.component.ts | 2 +- .../kube-config-table-select.component.html | 0 .../kube-config-table-select.component.scss | 0 ...kube-config-table-select.component.spec.ts | 0 .../kube-config-table-select.component.ts | 2 +- ...onfig-table-sub-type-select.component.html | 0 ...onfig-table-sub-type-select.component.scss | 0 ...ig-table-sub-type-select.component.spec.ts | 0 ...-config-table-sub-type-select.component.ts | 2 +- ...be-config-table-user-select.component.html | 0 ...be-config-table-user-select.component.scss | 0 ...config-table-user-select.component.spec.ts | 0 ...kube-config-table-user-select.component.ts | 2 +- .../kube-config.helper.ts | 20 +++++----- .../kube-config.types.ts | 8 ++-- .../kube-terminal/kube-console.component.html | 0 .../kube-terminal/kube-console.component.scss | 0 .../kube-console.component.spec.ts | 11 +++--- .../kube-terminal/kube-console.component.ts | 4 +- .../kubedash-configuration.component.html | 0 .../kubedash-configuration.component.scss | 0 .../kubedash-configuration.component.spec.ts | 2 +- .../kubedash-configuration.component.ts | 6 +-- .../kubernetes-dashboard.component.html | 0 .../kubernetes-dashboard.component.scss | 0 .../kubernetes-dashboard.component.spec.ts | 2 +- .../kubernetes-dashboard.component.ts | 4 +- .../kubernetes/kubernetes-entity-catalog.ts | 6 +-- .../kubernetes/kubernetes-entity-factory.ts | 8 ++-- .../kubernetes/kubernetes-entity-generator.ts | 12 +++--- .../kubernetes/kubernetes-metrics.helpers.ts | 0 ...s-namespace-analysis-report.component.html | 0 ...s-namespace-analysis-report.component.scss | 0 ...amespace-analysis-report.component.spec.ts | 4 +- ...tes-namespace-analysis-report.component.ts | 0 .../kubernetes-namespace-pods.component.html | 0 .../kubernetes-namespace-pods.component.scss | 0 .../kubernetes-namespace-pods.component.ts | 2 +- ...bernetes-namespace-services.component.html | 0 ...bernetes-namespace-services.component.scss | 0 ...netes-namespace-services.component.spec.ts | 2 +- ...kubernetes-namespace-services.component.ts | 2 +- .../kubernetes-namespace.component.html | 0 .../kubernetes-namespace.component.scss | 0 .../kubernetes-namespace.component.ts | 4 +- ...etes-node-metric-stats-card.component.html | 0 ...etes-node-metric-stats-card.component.scss | 0 ...s-node-metric-stats-card.component.spec.ts | 0 ...rnetes-node-metric-stats-card.component.ts | 0 ...bernetes-node-metrics-chart.component.html | 0 ...bernetes-node-metrics-chart.component.scss | 0 ...netes-node-metrics-chart.component.spec.ts | 0 ...kubernetes-node-metrics-chart.component.ts | 12 +++--- .../kubernetes-node-metrics.component.html | 0 .../kubernetes-node-metrics.component.scss | 0 .../kubernetes-node-metrics.component.spec.ts | 20 +++++++--- .../kubernetes-node-metrics.component.ts | 10 ++--- ...bernetes-node-simple-metric.component.html | 0 ...bernetes-node-simple-metric.component.scss | 0 ...netes-node-simple-metric.component.spec.ts | 0 ...kubernetes-node-simple-metric.component.ts | 0 .../kubernetes-node-pods.component.html | 0 .../kubernetes-node-pods.component.scss | 0 .../kubernetes-node-pods.component.spec.ts | 0 .../kubernetes-node-pods.component.ts | 2 +- .../kubernetes-node.component.html | 0 .../kubernetes-node.component.scss | 0 .../kubernetes-node.component.spec.ts | 2 +- .../kubernetes-node.component.ts | 4 +- .../src}/kubernetes/kubernetes-page.types.ts | 0 .../kubernetes-resource-viewer.component.html | 0 .../kubernetes-resource-viewer.component.scss | 0 ...bernetes-resource-viewer.component.spec.ts | 2 +- .../kubernetes-resource-viewer.component.ts | 4 +- .../kubernetes-tab-base.component.html | 0 .../kubernetes-tab-base.component.scss | 0 .../kubernetes-tab-base.component.spec.ts | 2 +- .../kubernetes-tab-base.component.ts | 4 +- .../src}/kubernetes/kubernetes.module.ts | 4 +- .../src}/kubernetes/kubernetes.routing.ts | 0 .../kubernetes/kubernetes.setup.module.ts | 10 ++--- .../kubernetes/kubernetes.store.module.ts | 0 .../kubernetes/kubernetes.testing.module.ts | 19 +++++---- .../kubernetes/kubernetes.component.html | 0 .../kubernetes/kubernetes.component.scss | 0 .../kubernetes/kubernetes.component.spec.ts | 2 +- .../kubernetes/kubernetes.component.ts | 10 ++--- .../analysis-reports-list-config.service.ts | 4 +- .../analysis-reports-list-source.ts | 4 +- .../analysis-status-cell.component.html | 0 .../analysis-status-cell.component.scss | 0 .../analysis-status-cell.component.spec.ts | 6 +-- .../analysis-status-cell.component.ts | 0 .../list-types/kube-helm-list-types.ts | 0 .../kubernetes/list-types/kube-list.helper.ts | 4 +- .../kubernetes-endpoints-data-source.ts | 15 ++++---- ...ubernetes-endpoints-list-config.service.ts | 19 +++++---- .../kubernetes-labels-cell.component.html | 0 .../kubernetes-labels-cell.component.scss | 0 .../kubernetes-labels-cell.component.spec.ts | 4 +- .../kubernetes-labels-cell.component.ts | 4 +- .../kubernetes-namespace-pods-data-source.ts | 6 +-- ...etes-namespace-pods-list-config.service.ts | 2 +- ...bernetes-namespace-services-data-source.ts | 4 +- ...-namespace-services-list-config.service.ts | 2 +- .../kube-namespace-pod-count.component.html | 0 .../kube-namespace-pod-count.component.scss | 0 ...kube-namespace-pod-count.component.spec.ts | 0 .../kube-namespace-pod-count.component.ts | 2 +- .../kubernetes-namespace-link.component.html | 0 .../kubernetes-namespace-link.component.scss | 0 ...ubernetes-namespace-link.component.spec.ts | 0 .../kubernetes-namespace-link.component.ts | 2 +- .../kubernetes-namespaces-data-source.ts | 8 ++-- ...bernetes-namespaces-list-config.service.ts | 6 +-- .../kubernetes-node-pods-data-source.ts | 6 +-- ...ubernetes-node-pods-list-config.service.ts | 2 +- .../condition-cell.component.html | 0 .../condition-cell.component.scss | 0 .../condition-cell.component.spec.ts | 2 +- .../condition-cell.component.ts | 2 +- .../kubernetes-node-capacity.component.html | 0 .../kubernetes-node-capacity.component.scss | 0 ...kubernetes-node-capacity.component.spec.ts | 2 +- .../kubernetes-node-capacity.component.ts | 2 +- .../kubernetes-node-ips.component.html | 0 .../kubernetes-node-ips.component.scss | 0 .../kubernetes-node-ips.component.spec.ts | 2 +- .../kubernetes-node-ips.component.ts | 2 +- .../kubernetes-node-labels.component.html | 0 .../kubernetes-node-labels.component.scss | 0 .../kubernetes-node-labels.component.spec.ts | 2 +- .../kubernetes-node-labels.component.ts | 2 +- .../kubernetes-node-link.component.html | 0 .../kubernetes-node-link.component.scss | 0 .../kubernetes-node-link.component.spec.ts | 0 .../kubernetes-node-link.component.theme.scss | 0 .../kubernetes-node-link.component.ts | 4 +- .../kubernetes-node-pressure.component.html | 0 .../kubernetes-node-pressure.component.scss | 0 ...kubernetes-node-pressure.component.spec.ts | 2 +- ...ernetes-node-pressure.component.theme.scss | 0 .../kubernetes-node-pressure.component.ts | 2 +- ...ernetes-node-condition-card.component.html | 0 ...ernetes-node-condition-card.component.scss | 0 ...etes-node-condition-card.component.spec.ts | 0 ...ubernetes-node-condition-card.component.ts | 0 .../kubernetes-node-condition.component.html | 0 .../kubernetes-node-condition.component.scss | 0 .../kubernetes-node-condition.component.ts | 0 .../kubernetes-node-info-card.component.html | 0 .../kubernetes-node-info-card.component.scss | 0 ...ubernetes-node-info-card.component.spec.ts | 0 .../kubernetes-node-info-card.component.ts | 0 ...ubernetes-node-summary-card.component.html | 0 ...ubernetes-node-summary-card.component.scss | 0 ...rnetes-node-summary-card.component.spec.ts | 0 .../kubernetes-node-summary-card.component.ts | 0 .../kubernetes-node-summary.component.html | 0 .../kubernetes-node-summary.component.scss | 0 .../kubernetes-node-summary.component.spec.ts | 0 .../kubernetes-node-summary.component.ts | 0 .../kubernetes-node-tags-card.component.html | 0 .../kubernetes-node-tags-card.component.scss | 0 ...ubernetes-node-tags-card.component.spec.ts | 0 .../kubernetes-node-tags-card.component.ts | 2 +- .../kubernetes-nodes-data-source.ts | 8 ++-- .../kubernetes-nodes-list-config.service.ts | 10 ++--- .../node-pod-count.component.html | 0 .../node-pod-count.component.scss | 0 .../node-pod-count.component.spec.ts | 0 .../node-pod-count.component.ts | 2 +- .../kubernetes-pod-containers.component.html | 0 .../kubernetes-pod-containers.component.scss | 0 ...ubernetes-pod-containers.component.spec.ts | 0 .../kubernetes-pod-containers.component.ts | 9 ++--- .../kubernetes-pod-status.component.html | 0 .../kubernetes-pod-status.component.scss | 0 .../kubernetes-pod-status.component.spec.ts | 0 .../kubernetes-pod-status.component.ts | 2 +- .../kubernetes-pod-tags.component.html | 0 .../kubernetes-pod-tags.component.scss | 0 .../kubernetes-pod-tags.component.spec.ts | 0 .../kubernetes-pod-tags.component.ts | 4 +- .../kubernetes-pods-data-source.ts | 6 +-- .../kubernetes-pods-list-config.service.ts | 8 ++-- .../kubernetes-service-ports.component.html | 0 .../kubernetes-service-ports.component.scss | 0 ...kubernetes-service-ports.component.spec.ts | 0 .../kubernetes-service-ports.component.ts | 2 +- .../kubernetes-service-card.component.html | 0 .../kubernetes-service-card.component.scss | 0 .../kubernetes-service-card.component.spec.ts | 0 .../kubernetes-service-card.component.ts | 0 .../kubernetes-service-list-config.service.ts | 8 ++-- .../kubernetes-services-data-source.ts | 8 ++-- .../pod-metrics/pod-metrics.component.html | 0 .../pod-metrics/pod-metrics.component.scss | 0 .../pod-metrics/pod-metrics.component.spec.ts | 2 +- .../pod-metrics/pod-metrics.component.ts | 16 ++++---- .../services/analysis-report.types.ts | 0 .../services/kubernetes-endpoint.service.ts | 23 +++++------ .../services/kubernetes-expanded-state.ts | 0 .../services/kubernetes-namespace.service.ts | 2 +- .../services/kubernetes-node.service.ts | 12 +++--- .../services/kubernetes.analysis.service.ts | 8 ++-- .../kubernetes/services/kubernetes.service.ts | 10 ++--- .../services/kubescore-report.helper.ts | 0 .../services/popeye-report.helper.ts | 0 .../src}/kubernetes/services/route.helper.ts | 0 .../action-builders/kube.action-builders.ts | 6 +-- .../src}/kubernetes/store/analysis.actions.ts | 4 +- .../src}/kubernetes/store/analysis.effects.ts | 14 ++----- .../src}/kubernetes/store/kube.getIds.ts | 10 ++--- .../src}/kubernetes/store/kube.types.ts | 0 .../kubernetes/store/kubernetes.actions.ts | 10 ++--- .../kubernetes/store/kubernetes.effects.ts | 20 ++++------ .../analysis-info-card.component.html | 0 .../analysis-info-card.component.scss | 0 .../analysis-info-card.component.spec.ts | 0 .../analysis-info-card.component.theme.scss | 0 .../analysis-info-card.component.ts | 0 .../kubernetes-analysis-info.component.html | 0 .../kubernetes-analysis-info.component.scss | 0 ...kubernetes-analysis-info.component.spec.ts | 8 ++-- .../kubernetes-analysis-info.component.ts | 2 +- .../kubernetes-analysis-report.component.html | 0 .../kubernetes-analysis-report.component.scss | 0 ...bernetes-analysis-report.component.spec.ts | 4 +- ...netes-analysis-report.component.theme.scss | 0 .../kubernetes-analysis-report.component.ts | 0 .../kubernetes-analysis-tab.component.html | 0 .../kubernetes-analysis-tab.component.scss | 0 .../kubernetes-analysis-tab.component.spec.ts | 4 +- .../kubernetes-analysis-tab.component.ts | 0 .../kubernetes-namespaces-tab.component.html | 0 .../kubernetes-namespaces-tab.component.scss | 0 .../kubernetes-namespaces-tab.component.ts | 2 +- .../kubernetes-nodes-tab.component.html | 0 .../kubernetes-nodes-tab.component.scss | 0 .../kubernetes-nodes-tab.component.spec.ts | 0 .../kubernetes-nodes-tab.component.ts | 2 +- .../kubernetes-pods-tab.component.html | 0 .../kubernetes-pods-tab.component.scss | 0 .../kubernetes-pods-tab.component.spec.ts | 0 .../kubernetes-pods-tab.component.ts | 2 +- .../kubernetes-summary.component.html | 0 .../kubernetes-summary.component.scss | 0 .../kubernetes-summary.component.spec.ts | 2 +- .../kubernetes-summary.component.theme.scss | 0 .../kubernetes-summary.component.ts | 21 +++++----- .../chart-values-editor.component.html | 0 .../chart-values-editor.component.scss | 0 .../chart-values-editor.component.spec.ts | 8 ++-- .../chart-values-editor.component.theme.scss | 0 .../chart-values-editor.component.ts | 6 +-- .../chart-values-editor/diffvalues.ts | 0 .../json-schema-generator.ts | 0 .../workloads/chart-values-editor/merge.ts | 0 .../create-release.component.html | 0 .../create-release.component.scss | 0 .../create-release.component.spec.ts | 11 +++--- .../create-release.component.ts | 11 ++---- .../helm-release-card.component.html | 0 .../helm-release-card.component.scss | 0 .../helm-release-card.component.spec.ts | 0 .../helm-release-card.component.ts | 2 +- .../helm-release-pods-list-config.service.ts | 0 .../helm-release-pods-list-source.ts | 0 ...lm-release-services-list-config.service.ts | 0 .../helm-release-services-list-source.ts | 0 .../helm-releases-list-config.service.ts | 2 +- .../list-types/helm-releases-list-source.ts | 0 .../kube-namespaces-filter-config.service.ts | 2 +- .../helm-release-socket-service.ts | 7 ++-- .../helm-release-tab-base.component.html | 0 .../helm-release-tab-base.component.scss | 0 .../helm-release-tab-base.component.spec.ts | 2 +- .../helm-release-tab-base.component.ts | 10 ++--- .../workloads/release/icon-helper.ts | 0 .../helm-release-analysis-tab.component.html | 0 .../helm-release-analysis-tab.component.scss | 0 ...elm-release-analysis-tab.component.spec.ts | 2 +- .../helm-release-analysis-tab.component.ts | 0 .../tabs/helm-release-helper.service.ts | 0 .../release/tabs/helm-release-helpers.scss | 0 .../helm-release-history-tab.component.html | 0 .../helm-release-history-tab.component.scss | 0 ...helm-release-history-tab.component.spec.ts | 0 .../helm-release-history-tab.component.ts | 4 +- .../helm-release-notes-tab.component.html | 0 .../helm-release-notes-tab.component.scss | 0 .../helm-release-notes-tab.component.spec.ts | 0 .../helm-release-notes-tab.component.ts | 0 .../helm-release-pods-tab.component.html | 0 .../helm-release-pods-tab.component.scss | 0 .../helm-release-pods-tab.component.spec.ts | 0 .../helm-release-pods-tab.component.ts | 0 ...helm-release-resource-graph.component.html | 0 ...helm-release-resource-graph.component.scss | 0 ...m-release-resource-graph.component.spec.ts | 2 +- .../helm-release-resource-graph.component.ts | 0 .../helm-release-services-tab.component.html | 0 .../helm-release-services-tab.component.scss | 0 ...elm-release-services-tab.component.spec.ts | 0 .../helm-release-services-tab.component.ts | 0 .../helm-release-summary-tab.component.html | 0 .../helm-release-summary-tab.component.scss | 2 +- ...helm-release-summary-tab.component.spec.ts | 4 +- ...m-release-summary-tab.component.theme.scss | 0 .../helm-release-summary-tab.component.ts | 4 +- .../helm-release-values-tab.component.html | 0 .../helm-release-values-tab.component.scss | 0 .../helm-release-values-tab.component.spec.ts | 2 +- .../helm-release-values-tab.component.ts | 0 .../workload-live-reload.component.html | 0 .../workload-live-reload.component.scss | 0 .../workload-live-reload.component.spec.ts | 0 .../workload-live-reload.component.ts | 0 .../releases-tab/releases-tab.component.html | 0 .../releases-tab/releases-tab.component.scss | 0 .../releases-tab.component.spec.ts | 2 +- .../releases-tab/releases-tab.component.ts | 2 +- .../store/workload-action-builders.ts | 4 +- .../store/workloads-entity-factory.ts | 2 +- .../store/workloads-entity-generator.ts | 6 +-- .../workloads/store/workloads.actions.ts | 0 .../workloads/store/workloads.effects.ts | 14 ++++--- .../workloads/store/workloads.reducers.ts | 0 .../workloads/store/workloads.store.module.ts | 0 .../release-version-data-source.ts | 8 ++-- .../release-version-list-config.ts | 8 ++-- .../upgrade-release.component.html | 0 .../upgrade-release.component.scss | 0 .../upgrade-release.component.spec.ts | 0 .../upgrade-release.component.ts | 4 +- .../kubernetes/workloads/workload.types.ts | 0 .../workloads/workloads-entity-catalog.ts | 4 +- .../kubernetes/workloads/workloads.module.ts | 4 +- .../kubernetes/workloads/workloads.routing.ts | 0 .../workloads/workloads.testing.module.ts | 19 +++++---- .../packages/kubernetes/src/public-api.ts | 4 ++ src/frontend/packages/kubernetes/src/test.ts | 36 ++++++++++++++++++ .../packages/kubernetes/tsconfig.spec.json | 17 +++++++++ src/frontend/packages/kubernetes/tslint.json | 3 ++ .../packages/suse-extensions/package.json | 1 - .../suse-extensions/sass/_all-theme.scss | 18 +-------- .../demo-helper/demo-helper.component.html | 0 .../demo-helper/demo-helper.component.scss | 0 .../demo-helper/demo-helper.component.spec.ts | 4 +- .../demo/demo-helper/demo-helper.component.ts | 4 +- .../suse-extensions/src/public-api.ts | 3 +- .../suse-about-info.component.html | 0 .../suse-about-info.component.scss | 0 .../suse-about-info.component.ts | 6 +-- .../suse-login/suse-login.component.html | 0 .../suse-login/suse-login.component.scss | 2 +- .../suse-login/suse-login.component.spec.ts | 10 ++--- .../suse-login.component.theme.scss | 0 .../suse-login/suse-login.component.ts | 8 ++-- .../suse-welcome/suse-welcome.component.html | 0 .../suse-welcome/suse-welcome.component.scss | 2 +- .../suse-welcome/suse-welcome.component.ts | 0 .../src/{custom => }/suse.module.ts | 17 ++++----- src/jetstream/go.mod | 1 + src/jetstream/go.sum | 18 +++++++++ src/tsconfig.json | 3 +- 556 files changed, 735 insertions(+), 571 deletions(-) rename src/frontend/packages/{suse-extensions => kubernetes}/assets/custom/aks.svg (100%) rename src/frontend/packages/{suse-extensions => kubernetes}/assets/custom/app_placeholder.svg (100%) rename src/frontend/packages/{suse-extensions => kubernetes}/assets/custom/caasp.png (100%) rename src/frontend/packages/{suse-extensions => kubernetes}/assets/custom/eks.svg (100%) rename src/frontend/packages/{suse-extensions => kubernetes}/assets/custom/gke.svg (100%) rename src/frontend/packages/{suse-extensions => kubernetes}/assets/custom/helm.svg (100%) rename src/frontend/packages/{suse-extensions => kubernetes}/assets/custom/help/en/connecting_gke.md (100%) rename src/frontend/packages/{suse-extensions => kubernetes}/assets/custom/k3s.svg (100%) rename src/frontend/packages/{suse-extensions => kubernetes}/assets/custom/kube_import.png (100%) rename src/frontend/packages/{suse-extensions => kubernetes}/assets/custom/kubernetes.svg (100%) rename src/frontend/packages/{suse-extensions => kubernetes}/assets/custom/placeholder.png (100%) create mode 100644 src/frontend/packages/kubernetes/karma.conf.js create mode 100644 src/frontend/packages/kubernetes/package.json create mode 100644 src/frontend/packages/kubernetes/sass/_all-theme.scss rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/chart-view/monocular.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/chart-view/monocular.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/chart-view/monocular.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/helm-entity-catalog.ts (63%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/helm-entity-factory.ts (96%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/helm-entity-generator.ts (88%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/helm-hub-registration/helm-hub-registration.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/helm-hub-registration/helm-hub-registration.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/helm-hub-registration/helm-hub-registration.component.spec.ts (77%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/helm-hub-registration/helm-hub-registration.component.ts (75%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/helm-testing.module.ts (72%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/helm.module.ts (87%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/helm.routing.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/helm.setup.module.ts (69%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/helm.store.module.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/list-types/monocular-chart-card/monocular-chart-card.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/list-types/monocular-chart-card/monocular-chart-card.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/list-types/monocular-chart-card/monocular-chart-card.component.spec.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/list-types/monocular-chart-card/monocular-chart-card.component.theme.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/list-types/monocular-chart-card/monocular-chart-card.component.ts (82%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/list-types/monocular-charts-data-source.ts (71%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/list-types/monocular-charts-list-config.service.ts (90%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular-tab-base/monocular-tab-base.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular-tab-base/monocular-tab-base.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular-tab-base/monocular-tab-base.component.spec.ts (83%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular-tab-base/monocular-tab-base.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular.interceptor.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/app.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-details/chart-details-info/chart-details-info.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-details/chart-details-info/chart-details-info.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-details/chart-details-info/chart-details-info.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-details/chart-details-info/chart-details-info.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.spec.ts (65%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.ts (94%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-details/chart-details.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-details/chart-details.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-details/chart-details.component.spec.ts (89%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-details/chart-details.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-index/chart-index.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-index/chart-index.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-index/chart-index.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-index/chart-index.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-item/chart-item.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-item/chart-item.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-item/chart-item.component.spec.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-item/chart-item.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-list/chart-list.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-list/chart-list.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-list/chart-list.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/chart-list/chart-list.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/charts/charts.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/charts/charts.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/charts/charts.component.spec.ts (96%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/charts/charts.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/list-filters/list-filters.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/list-filters/list-filters.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/list-filters/list-filters.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/list-filters/list-filters.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/list-item/list-item.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/list-item/list-item.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/list-item/list-item.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/list-item/list-item.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/loader/loader.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/loader/loader.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/loader/loader.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/loader/loader.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/monocular.module.ts (96%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/panel/panel.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/panel/panel.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/panel/panel.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/panel/panel.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/shared/models/chart-version.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/shared/models/chart.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/shared/models/maintainer.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/shared/models/repo.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/shared/seo.data.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/shared/services/chart.service.mock.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/shared/services/charts.service.ts (98%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/shared/services/config.service.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/shared/services/menu.service.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/shared/services/repos.service.ts (95%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/stratos-monocular-providers.helpers.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/monocular/stratos-monocular.helper.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/store/helm.action-builders.ts (89%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/store/helm.actions.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/store/helm.effects.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/store/helm.types.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/tabs/catalog-tab/catalog-tab.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/tabs/catalog-tab/catalog-tab.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/tabs/catalog-tab/catalog-tab.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/tabs/catalog-tab/catalog-tab.component.ts (80%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/helm/theme.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom/suse-routing.module.ts => kubernetes/src/kube-package-routing.module.ts} (96%) create mode 100644 src/frontend/packages/kubernetes/src/kube-package.module.ts rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.spec.ts (81%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.spec.ts (88%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.ts (96%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/analysis-report-viewer.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/analysis-report-viewer.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/analysis-report-viewer.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/analysis-report-viewer.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.spec.ts (88%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.spec.ts (88%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.ts (91%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.spec.ts (88%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.spec.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.ts (83%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.spec.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.ts (96%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.spec.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.spec.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-auth.helper.ts (92%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.ts (91%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.ts (76%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-registration.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-registration.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-registration.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-registration.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.ts (92%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.ts (92%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.spec.ts (90%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-name/kube-config-table-name.component.ts (79%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-select/kube-config-table-select.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-select/kube-config-table-select.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-select/kube-config-table-select.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-select/kube-config-table-select.component.ts (85%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-sub-type-select/kube-config-table-sub-type-select.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-sub-type-select/kube-config-table-sub-type-select.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-sub-type-select/kube-config-table-sub-type-select.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-sub-type-select/kube-config-table-sub-type-select.component.ts (90%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-user-select/kube-config-table-user-select.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-user-select/kube-config-table-user-select.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-user-select/kube-config-table-user-select.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-user-select/kube-config-table-user-select.component.ts (88%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config.helper.ts (91%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-config-registration/kube-config.types.ts (85%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-terminal/kube-console.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-terminal/kube-console.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-terminal/kube-console.component.spec.ts (65%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kube-terminal/kube-console.component.ts (92%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.spec.ts (94%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.ts (95%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.spec.ts (94%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.ts (96%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-entity-catalog.ts (89%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-entity-factory.ts (92%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-entity-generator.ts (95%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-metrics.helpers.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.spec.ts (92%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-namespace/kubernetes-namespace-pods/kubernetes-namespace-pods.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-namespace/kubernetes-namespace-pods/kubernetes-namespace-pods.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-namespace/kubernetes-namespace-pods/kubernetes-namespace-pods.component.ts (84%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.spec.ts (94%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.ts (84%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-namespace/kubernetes-namespace.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-namespace/kubernetes-namespace.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-namespace/kubernetes-namespace.component.ts (94%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.ts (74%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.spec.ts (71%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.ts (85%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-simple-metric/kubernetes-node-simple-metric.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-simple-metric/kubernetes-node-simple-metric.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-simple-metric/kubernetes-node-simple-metric.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-simple-metric/kubernetes-node-simple-metric.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.ts (82%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node.component.spec.ts (94%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-node/kubernetes-node.component.ts (91%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-page.types.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.spec.ts (94%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.ts (96%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.spec.ts (94%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes.module.ts (98%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes.routing.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes.setup.module.ts (90%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes.store.module.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes.testing.module.ts (68%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes/kubernetes.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes/kubernetes.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes/kubernetes.component.spec.ts (91%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/kubernetes/kubernetes.component.ts (79%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/analysis-reports-list-config.service.ts (96%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/analysis-reports-list-source.ts (92%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.spec.ts (82%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kube-helm-list-types.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kube-list.helper.ts (88%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-data-source.ts (52%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-list-config.service.ts (59%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.spec.ts (92%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.ts (76%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-namespace-pods/kubernetes-namespace-pods-data-source.ts (79%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-namespace-pods/kubernetes-namespace-pods-list-config.service.ts (94%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-namespace-services/kubernetes-namespace-services-data-source.ts (80%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-namespace-services/kubernetes-namespace-services-list-config.service.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.ts (90%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.ts (87%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-data-source.ts (71%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-list-config.service.ts (91%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-node-pods/kubernetes-node-pods-data-source.ts (76%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-node-pods/kubernetes-node-pods-list-config.service.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.spec.ts (92%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.ts (89%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.spec.ts (92%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.ts (85%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.spec.ts (92%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.ts (88%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.spec.ts (92%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.ts (85%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.theme.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.ts (91%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.spec.ts (92%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.theme.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.ts (88%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-info-card/kubernetes-node-info-card.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-info-card/kubernetes-node-info-card.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-info-card/kubernetes-node-info-card.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-info-card/kubernetes-node-info-card.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.ts (90%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-data-source.ts (73%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts (92%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.ts (89%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.ts (90%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.ts (80%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-pods/kubernetes-pods-data-source.ts (73%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-pods/kubernetes-pods-list-config.service.ts (92%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.ts (81%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-services/kubernetes-service-card/kubernetes-service-card.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-services/kubernetes-service-card/kubernetes-service-card.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-services/kubernetes-service-card/kubernetes-service-card.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-services/kubernetes-service-card/kubernetes-service-card.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-services/kubernetes-service-list-config.service.ts (83%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/list-types/kubernetes-services/kubernetes-services-data-source.ts (65%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/pod-metrics/pod-metrics.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/pod-metrics/pod-metrics.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/pod-metrics/pod-metrics.component.spec.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/pod-metrics/pod-metrics.component.ts (88%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/services/analysis-report.types.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/services/kubernetes-endpoint.service.ts (92%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/services/kubernetes-expanded-state.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/services/kubernetes-namespace.service.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/services/kubernetes-node.service.ts (84%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/services/kubernetes.analysis.service.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/services/kubernetes.service.ts (64%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/services/kubescore-report.helper.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/services/popeye-report.helper.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/services/route.helper.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/store/action-builders/kube.action-builders.ts (97%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/store/analysis.actions.ts (94%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/store/analysis.effects.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/store/kube.getIds.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/store/kube.types.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/store/kubernetes.actions.ts (97%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/store/kubernetes.effects.ts (95%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.theme.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.spec.ts (81%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.spec.ts (89%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.theme.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.spec.ts (90%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-namespaces-tab/kubernetes-namespaces-tab.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-namespaces-tab/kubernetes-namespaces-tab.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-namespaces-tab/kubernetes-namespaces-tab.component.ts (86%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.ts (83%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.ts (82%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.spec.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.theme.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts (91%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/chart-values-editor/chart-values-editor.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/chart-values-editor/chart-values-editor.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/chart-values-editor/chart-values-editor.component.spec.ts (80%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/chart-values-editor/chart-values-editor.component.theme.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts (97%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/chart-values-editor/diffvalues.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/chart-values-editor/json-schema-generator.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/chart-values-editor/merge.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/create-release/create-release.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/create-release/create-release.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/create-release/create-release.component.spec.ts (74%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/create-release/create-release.component.ts (96%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/list-types/helm-release-pods-list-config.service.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/list-types/helm-release-pods-list-source.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/list-types/helm-release-services-list-config.service.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/list-types/helm-release-services-list-source.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/list-types/helm-releases-list-config.service.ts (98%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/list-types/helm-releases-list-source.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/list-types/kube-namespaces-filter-config.service.ts (97%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/helm-release-tab-base/helm-release-socket-service.ts (94%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.spec.ts (95%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts (86%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/icon-helper.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.spec.ts (95%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-helper.service.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-helpers.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.ts (91%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-notes-tab/helm-release-notes-tab.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-notes-tab/helm-release-notes-tab.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-notes-tab/helm-release-notes-tab.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-notes-tab/helm-release-notes-tab.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.spec.ts (95%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss (96%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts (89%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.theme.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts (98%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.spec.ts (92%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/releases-tab/releases-tab.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/releases-tab/releases-tab.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/releases-tab/releases-tab.component.spec.ts (92%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/releases-tab/releases-tab.component.ts (92%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/store/workload-action-builders.ts (94%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/store/workloads-entity-factory.ts (96%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/store/workloads-entity-generator.ts (93%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/store/workloads.actions.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/store/workloads.effects.ts (94%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/store/workloads.reducers.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/store/workloads.store.module.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/upgrade-release/release-version-data-source.ts (80%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/upgrade-release/release-version-list-config.ts (88%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/upgrade-release/upgrade-release.component.html (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/upgrade-release/upgrade-release.component.scss (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/upgrade-release/upgrade-release.component.spec.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/upgrade-release/upgrade-release.component.ts (97%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/workload.types.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/workloads-entity-catalog.ts (84%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/workloads.module.ts (95%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/workloads.routing.ts (100%) rename src/frontend/packages/{suse-extensions/src/custom => kubernetes/src}/kubernetes/workloads/workloads.testing.module.ts (66%) create mode 100644 src/frontend/packages/kubernetes/src/public-api.ts create mode 100644 src/frontend/packages/kubernetes/src/test.ts create mode 100644 src/frontend/packages/kubernetes/tsconfig.spec.json create mode 100644 src/frontend/packages/kubernetes/tslint.json rename src/frontend/packages/suse-extensions/src/{custom => }/demo/demo-helper/demo-helper.component.html (100%) rename src/frontend/packages/suse-extensions/src/{custom => }/demo/demo-helper/demo-helper.component.scss (100%) rename src/frontend/packages/suse-extensions/src/{custom => }/demo/demo-helper/demo-helper.component.spec.ts (84%) rename src/frontend/packages/suse-extensions/src/{custom => }/demo/demo-helper/demo-helper.component.ts (83%) rename src/frontend/packages/suse-extensions/src/{custom => }/suse-about-info/suse-about-info.component.html (100%) rename src/frontend/packages/suse-extensions/src/{custom => }/suse-about-info/suse-about-info.component.scss (100%) rename src/frontend/packages/suse-extensions/src/{custom => }/suse-about-info/suse-about-info.component.ts (79%) rename src/frontend/packages/suse-extensions/src/{custom => }/suse-login/suse-login.component.html (100%) rename src/frontend/packages/suse-extensions/src/{custom => }/suse-login/suse-login.component.scss (98%) rename src/frontend/packages/suse-extensions/src/{custom => }/suse-login/suse-login.component.spec.ts (78%) rename src/frontend/packages/suse-extensions/src/{custom => }/suse-login/suse-login.component.theme.scss (100%) rename src/frontend/packages/suse-extensions/src/{custom => }/suse-login/suse-login.component.ts (66%) rename src/frontend/packages/suse-extensions/src/{custom => }/suse-welcome/suse-welcome.component.html (100%) rename src/frontend/packages/suse-extensions/src/{custom => }/suse-welcome/suse-welcome.component.scss (93%) rename src/frontend/packages/suse-extensions/src/{custom => }/suse-welcome/suse-welcome.component.ts (100%) rename src/frontend/packages/suse-extensions/src/{custom => }/suse.module.ts (68%) diff --git a/angular.json b/angular.json index 18d9daa972..31c5a47d56 100644 --- a/angular.json +++ b/angular.json @@ -376,6 +376,34 @@ } } } + }, + "kubernetes": { + "root": "src/frontend/packages/kubernetes", + "sourceRoot": "src/frontend/packages/kubernetes/src", + "projectType": "library", + "prefix": "lib", + "architect": { + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/frontend/packages/kubernetes/src/test.ts", + "tsConfig": "src/frontend/packages/kubernetes/tsconfig.spec.json", + "karmaConfig": "src/frontend/packages/kubernetes/karma.conf.js" + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "src/tsconfig.json" + ], + "tslintConfig": "src/frontend/packages/kubernetes/tslint.json", + "files": [ + "src/frontend/packages/kubernetes/src/**/*.ts" + ] + } + } + } } }, diff --git a/package.json b/package.json index 91cc3b68b1..6a2065af21 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "test-frontend:cloud-foundry": "NG_TEST_SUITE=cloud-foundry ng test cloud-foundry --code-coverage --watch=false", "test-frontend:cf-autoscaler": "NG_TEST_SUITE=autoscaler ng test cf-autoscaler --code-coverage --watch=false", "test-frontend:extensions": "NG_TEST_SUITE=extensions ng test extensions --code-coverage --watch=false", + "test-frontend:kubernetes": "NG_TEST_SUITE=kubernetes ng test kubernetes --code-coverage --watch=false", "posttest": "nyc report --reporter=html --reporter=lcovonly --reporter=json --tempDir=coverage/nyc", "codecov": "codecov -f coverage/coverage-final.json", "lint": "ng lint --format stylish", diff --git a/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.spec.ts b/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.spec.ts index fff7bac83e..5029385a87 100644 --- a/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.spec.ts +++ b/src/frontend/packages/core/src/features/login/logout-page/logout-page.component.spec.ts @@ -1,11 +1,11 @@ import { CommonModule } from '@angular/common'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { CoreModule } from '@angular/flex-layout'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; import { StoreModule } from '@ngrx/store'; import { appReducers } from '../../../../../store/src/reducers.module'; +import { CoreModule } from '../../../core/core.module'; import { SharedModule } from '../../../public-api'; import { LogoutPageComponent } from './logout-page.component'; @@ -15,7 +15,7 @@ describe('LogoutPageComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ LogoutPageComponent ], + declarations: [LogoutPageComponent], imports: [ CommonModule, CoreModule, @@ -27,7 +27,7 @@ describe('LogoutPageComponent', () => { ) ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/frontend/packages/suse-extensions/assets/custom/aks.svg b/src/frontend/packages/kubernetes/assets/custom/aks.svg similarity index 100% rename from src/frontend/packages/suse-extensions/assets/custom/aks.svg rename to src/frontend/packages/kubernetes/assets/custom/aks.svg diff --git a/src/frontend/packages/suse-extensions/assets/custom/app_placeholder.svg b/src/frontend/packages/kubernetes/assets/custom/app_placeholder.svg similarity index 100% rename from src/frontend/packages/suse-extensions/assets/custom/app_placeholder.svg rename to src/frontend/packages/kubernetes/assets/custom/app_placeholder.svg diff --git a/src/frontend/packages/suse-extensions/assets/custom/caasp.png b/src/frontend/packages/kubernetes/assets/custom/caasp.png similarity index 100% rename from src/frontend/packages/suse-extensions/assets/custom/caasp.png rename to src/frontend/packages/kubernetes/assets/custom/caasp.png diff --git a/src/frontend/packages/suse-extensions/assets/custom/eks.svg b/src/frontend/packages/kubernetes/assets/custom/eks.svg similarity index 100% rename from src/frontend/packages/suse-extensions/assets/custom/eks.svg rename to src/frontend/packages/kubernetes/assets/custom/eks.svg diff --git a/src/frontend/packages/suse-extensions/assets/custom/gke.svg b/src/frontend/packages/kubernetes/assets/custom/gke.svg similarity index 100% rename from src/frontend/packages/suse-extensions/assets/custom/gke.svg rename to src/frontend/packages/kubernetes/assets/custom/gke.svg diff --git a/src/frontend/packages/suse-extensions/assets/custom/helm.svg b/src/frontend/packages/kubernetes/assets/custom/helm.svg similarity index 100% rename from src/frontend/packages/suse-extensions/assets/custom/helm.svg rename to src/frontend/packages/kubernetes/assets/custom/helm.svg diff --git a/src/frontend/packages/suse-extensions/assets/custom/help/en/connecting_gke.md b/src/frontend/packages/kubernetes/assets/custom/help/en/connecting_gke.md similarity index 100% rename from src/frontend/packages/suse-extensions/assets/custom/help/en/connecting_gke.md rename to src/frontend/packages/kubernetes/assets/custom/help/en/connecting_gke.md diff --git a/src/frontend/packages/suse-extensions/assets/custom/k3s.svg b/src/frontend/packages/kubernetes/assets/custom/k3s.svg similarity index 100% rename from src/frontend/packages/suse-extensions/assets/custom/k3s.svg rename to src/frontend/packages/kubernetes/assets/custom/k3s.svg diff --git a/src/frontend/packages/suse-extensions/assets/custom/kube_import.png b/src/frontend/packages/kubernetes/assets/custom/kube_import.png similarity index 100% rename from src/frontend/packages/suse-extensions/assets/custom/kube_import.png rename to src/frontend/packages/kubernetes/assets/custom/kube_import.png diff --git a/src/frontend/packages/suse-extensions/assets/custom/kubernetes.svg b/src/frontend/packages/kubernetes/assets/custom/kubernetes.svg similarity index 100% rename from src/frontend/packages/suse-extensions/assets/custom/kubernetes.svg rename to src/frontend/packages/kubernetes/assets/custom/kubernetes.svg diff --git a/src/frontend/packages/suse-extensions/assets/custom/placeholder.png b/src/frontend/packages/kubernetes/assets/custom/placeholder.png similarity index 100% rename from src/frontend/packages/suse-extensions/assets/custom/placeholder.png rename to src/frontend/packages/kubernetes/assets/custom/placeholder.png diff --git a/src/frontend/packages/kubernetes/karma.conf.js b/src/frontend/packages/kubernetes/karma.conf.js new file mode 100644 index 0000000000..c23e417786 --- /dev/null +++ b/src/frontend/packages/kubernetes/karma.conf.js @@ -0,0 +1,8 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + ...require('../../../../build/karma.conf.creator.js')('extensions')(config) + }) +} diff --git a/src/frontend/packages/kubernetes/package.json b/src/frontend/packages/kubernetes/package.json new file mode 100644 index 0000000000..81032a063e --- /dev/null +++ b/src/frontend/packages/kubernetes/package.json @@ -0,0 +1,16 @@ +{ + "name": "@stratosui/kubernetes", + "version": "0.0.1", + "peerDependencies": { + "@angular/common": "^6.0.0-rc.0 || ^6.0.0", + "@angular/core": "^6.0.0-rc.0 || ^6.0.0" + }, + "stratos": { + "module": "KubePackageModule", + "routingModule": "KubePackageModuleRoutingModule", + "theming": "sass/_all-theme#apply-theme-kubernetes", + "assets": { + "assets": "core/assets" + } + } +} diff --git a/src/frontend/packages/kubernetes/sass/_all-theme.scss b/src/frontend/packages/kubernetes/sass/_all-theme.scss new file mode 100644 index 0000000000..8a917544fa --- /dev/null +++ b/src/frontend/packages/kubernetes/sass/_all-theme.scss @@ -0,0 +1,24 @@ +// Theming for the copmponents in the Kubernetes package + +@import '../src/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.theme'; +@import '../src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.theme'; +@import '../src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.theme'; +@import '../src/helm/list-types/monocular-chart-card/monocular-chart-card.component.theme'; +@import '../src/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.theme'; +@import '../src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.theme'; +@import '../src/kubernetes/workloads/chart-values-editor/chart-values-editor.component.theme'; + +@mixin apply-theme-kubernetes($stratos-theme) { + + $theme: map-get($stratos-theme, theme); + $app-theme: map-get($stratos-theme, app-theme); + + @include kube-summary-theme($theme, $app-theme); + @include kube-analysis-report-theme($theme, $app-theme); + @include kube-analysis-card-theme($theme, $app-theme); + @include monocular-chart-card($theme, $app-theme); + @include helm-release-summary-tab-theme($theme, $app-theme); + @include kube-node-link-theme($theme, $app-theme); + @include app-chart-values-editor-theme($theme, $app-theme); + +} diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/chart-view/monocular.component.html b/src/frontend/packages/kubernetes/src/helm/chart-view/monocular.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/chart-view/monocular.component.html rename to src/frontend/packages/kubernetes/src/helm/chart-view/monocular.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/chart-view/monocular.component.scss b/src/frontend/packages/kubernetes/src/helm/chart-view/monocular.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/chart-view/monocular.component.scss rename to src/frontend/packages/kubernetes/src/helm/chart-view/monocular.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/chart-view/monocular.component.ts b/src/frontend/packages/kubernetes/src/helm/chart-view/monocular.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/chart-view/monocular.component.ts rename to src/frontend/packages/kubernetes/src/helm/chart-view/monocular.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-catalog.ts b/src/frontend/packages/kubernetes/src/helm/helm-entity-catalog.ts similarity index 63% rename from src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-catalog.ts rename to src/frontend/packages/kubernetes/src/helm/helm-entity-catalog.ts index 83dda2cd49..ec62f85798 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-catalog.ts +++ b/src/frontend/packages/kubernetes/src/helm/helm-entity-catalog.ts @@ -1,4 +1,14 @@ -import { StratosCatalogEndpointEntity, StratosCatalogEntity } from '../../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; import { IFavoriteMetadata } from '../../../../store/src/types/user-favorites.types'; import { MonocularChart, HelmVersion, MonocularVersion } from './store/helm.types'; import { HelmChartActionBuilders, HelmVersionActionBuilders, HelmChartVersionsActionBuilders } from './store/helm.action-builders'; +import { + StratosCatalogEndpointEntity, + StratosCatalogEntity, +} from '../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; +import { IFavoriteMetadata } from '../../../store/src/types/user-favorites.types'; +import { + HelmChartActionBuilders, + HelmChartVersionsActionBuilders, + HelmVersionActionBuilders, +} from './store/helm.action-builders'; +import { HelmVersion, MonocularChart, MonocularVersion } from './store/helm.types'; /** * A strongly typed collection of Helm Catalog Entities. diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-factory.ts b/src/frontend/packages/kubernetes/src/helm/helm-entity-factory.ts similarity index 96% rename from src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-factory.ts rename to src/frontend/packages/kubernetes/src/helm/helm-entity-factory.ts index 3449e60e33..3746c87fef 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-factory.ts +++ b/src/frontend/packages/kubernetes/src/helm/helm-entity-factory.ts @@ -1,6 +1,6 @@ import { Schema, schema } from 'normalizr'; -import { EntitySchema } from '../../../../store/src/helpers/entity-schema'; +import { EntitySchema } from '../../../store/src/helpers/entity-schema'; import { stratosMonocularEndpointGuid } from './monocular/stratos-monocular.helper'; import { HelmVersion, MonocularChart } from './store/helm.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-generator.ts b/src/frontend/packages/kubernetes/src/helm/helm-entity-generator.ts similarity index 88% rename from src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-generator.ts rename to src/frontend/packages/kubernetes/src/helm/helm-entity-generator.ts index 23722669e3..7e14101f7a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm-entity-generator.ts +++ b/src/frontend/packages/kubernetes/src/helm/helm-entity-generator.ts @@ -2,17 +2,17 @@ import { Store } from '@ngrx/store'; import { of } from 'rxjs'; import { catchError, first, map } from 'rxjs/operators'; -import { IListAction } from '../../../../core/src/shared/components/list/list.component.types'; -import { AppState } from '../../../../store/src/app-state'; +import { IListAction } from '../../../core/src/shared/components/list/list.component.types'; +import { AppState } from '../../../store/src/app-state'; import { StratosBaseCatalogEntity, StratosCatalogEndpointEntity, StratosCatalogEntity, -} from '../../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; -import { StratosEndpointExtensionDefinition } from '../../../../store/src/entity-catalog/entity-catalog.types'; -import { stratosEntityCatalog } from '../../../../store/src/stratos-entity-catalog'; -import { EndpointModel } from '../../../../store/src/types/endpoint.types'; -import { IFavoriteMetadata } from '../../../../store/src/types/user-favorites.types'; +} from '../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; +import { StratosEndpointExtensionDefinition } from '../../../store/src/entity-catalog/entity-catalog.types'; +import { EndpointModel } from '../../../store/src/public-api'; +import { stratosEntityCatalog } from '../../../store/src/stratos-entity-catalog'; +import { IFavoriteMetadata } from '../../../store/src/types/user-favorites.types'; import { helmEntityCatalog } from './helm-entity-catalog'; import { HELM_ENDPOINT_TYPE, diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.html b/src/frontend/packages/kubernetes/src/helm/helm-hub-registration/helm-hub-registration.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.html rename to src/frontend/packages/kubernetes/src/helm/helm-hub-registration/helm-hub-registration.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.scss b/src/frontend/packages/kubernetes/src/helm/helm-hub-registration/helm-hub-registration.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.scss rename to src/frontend/packages/kubernetes/src/helm/helm-hub-registration/helm-hub-registration.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.spec.ts b/src/frontend/packages/kubernetes/src/helm/helm-hub-registration/helm-hub-registration.component.spec.ts similarity index 77% rename from src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.spec.ts rename to src/frontend/packages/kubernetes/src/helm/helm-hub-registration/helm-hub-registration.component.spec.ts index df9c494821..69893fc648 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/helm/helm-hub-registration/helm-hub-registration.component.spec.ts @@ -1,8 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { EndpointsService } from '../../../../../core/src/core/endpoints.service'; -import { UserService } from '../../../../../core/src/core/user.service'; -import { BaseTestModules } from '../../../../../core/test-framework/core-test.helper'; +import { EndpointsService } from '../../../../core/src/core/endpoints.service'; +import { UserService } from '../../../../core/src/core/user.service'; +import { BaseTestModules } from '../../../../core/test-framework/core-test.helper'; import { HelmHubRegistrationComponent } from './helm-hub-registration.component'; describe('HelmHubRegistrationComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.ts b/src/frontend/packages/kubernetes/src/helm/helm-hub-registration/helm-hub-registration.component.ts similarity index 75% rename from src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.ts rename to src/frontend/packages/kubernetes/src/helm/helm-hub-registration/helm-hub-registration.component.ts index 2b1a2ceeb3..45adcc2cdc 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm-hub-registration/helm-hub-registration.component.ts +++ b/src/frontend/packages/kubernetes/src/helm/helm-hub-registration/helm-hub-registration.component.ts @@ -1,9 +1,9 @@ import { Component } from '@angular/core'; import { filter, map, pairwise } from 'rxjs/operators'; -import { StepOnNextFunction } from '../../../../../core/src/shared/components/stepper/step/step.component'; -import { ActionState } from '../../../../../store/src/reducers/api-request-reducer/types'; -import { stratosEntityCatalog } from '../../../../../store/src/stratos-entity-catalog'; +import { StepOnNextFunction } from '../../../../core/src/shared/components/stepper/step/step.component'; +import { ActionState } from '../../../../store/src/reducers/api-request-reducer/types'; +import { stratosEntityCatalog } from '../../../../store/src/stratos-entity-catalog'; import { HELM_ENDPOINT_TYPE, HELM_HUB_ENDPOINT_TYPE } from '../helm-entity-factory'; @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm-testing.module.ts b/src/frontend/packages/kubernetes/src/helm/helm-testing.module.ts similarity index 72% rename from src/frontend/packages/suse-extensions/src/custom/helm/helm-testing.module.ts rename to src/frontend/packages/kubernetes/src/helm/helm-testing.module.ts index d995b63748..db5308232e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm-testing.module.ts +++ b/src/frontend/packages/kubernetes/src/helm/helm-testing.module.ts @@ -1,16 +1,19 @@ import { HttpClient, HttpClientModule, HttpHandler } from '@angular/common/http'; import { NgModule } from '@angular/core'; -import { CoreModule } from '@angular/flex-layout'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; -import { SharedModule } from '@stratosui/core'; +import { CoreModule, SharedModule } from '@stratosui/core'; -import { AppTestModule } from '../../../../core/test-framework/core-test.helper'; -import { CATALOGUE_ENTITIES, EntityCatalogFeatureModule } from '../../../../store/src/entity-catalog.module'; -import { entityCatalog, TestEntityCatalog } from '../../../../store/src/entity-catalog/entity-catalog'; -import { generateStratosEntities } from '../../../../store/src/stratos-entity-generator'; -import { createBasicStoreModule } from '../../../../store/testing/public-api'; +import { AppTestModule } from '../../../core/test-framework/core-test.helper'; +import { + CATALOGUE_ENTITIES, + entityCatalog, + EntityCatalogFeatureModule, + TestEntityCatalog, +} from '../../../store/src/public-api'; +import { generateStratosEntities } from '../../../store/src/stratos-entity-generator'; +import { createBasicStoreModule } from '../../../store/testing/public-api'; import { HelmReleaseGuid } from '../kubernetes/workloads/workload.types'; import { generateHelmEntities } from './helm-entity-generator'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm.module.ts b/src/frontend/packages/kubernetes/src/helm/helm.module.ts similarity index 87% rename from src/frontend/packages/suse-extensions/src/custom/helm/helm.module.ts rename to src/frontend/packages/kubernetes/src/helm/helm.module.ts index 02f818996c..e2229215bf 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm.module.ts +++ b/src/frontend/packages/kubernetes/src/helm/helm.module.ts @@ -1,8 +1,8 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { CoreModule } from '../../../../core/src/core/core.module'; -import { SharedModule } from '../../../../core/src/shared/shared.module'; +import { CoreModule } from '../../../core/src/core/core.module'; +import { SharedModule } from '../../../core/src/shared/shared.module'; import { MonocularChartViewComponent } from './chart-view/monocular.component'; import { HelmRoutingModule } from './helm.routing'; import { MonocularChartCardComponent } from './list-types/monocular-chart-card/monocular-chart-card.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm.routing.ts b/src/frontend/packages/kubernetes/src/helm/helm.routing.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/helm.routing.ts rename to src/frontend/packages/kubernetes/src/helm/helm.routing.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm.setup.module.ts b/src/frontend/packages/kubernetes/src/helm/helm.setup.module.ts similarity index 69% rename from src/frontend/packages/suse-extensions/src/custom/helm/helm.setup.module.ts rename to src/frontend/packages/kubernetes/src/helm/helm.setup.module.ts index 3a57d47e51..29ca39d039 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/helm.setup.module.ts +++ b/src/frontend/packages/kubernetes/src/helm/helm.setup.module.ts @@ -2,13 +2,13 @@ import { CommonModule } from '@angular/common'; import { NgModule, Optional, SkipSelf } from '@angular/core'; import { Store } from '@ngrx/store'; -import { CoreModule } from '../../../../core/src/core/core.module'; -import { EndpointsService } from '../../../../core/src/core/endpoints.service'; -import { SharedModule } from '../../../../core/src/shared/shared.module'; -import { GetSystemInfo } from '../../../../store/src/actions/system.actions'; -import { AppState } from '../../../../store/src/app-state'; -import { EntityCatalogModule } from '../../../../store/src/entity-catalog.module'; -import { EndpointHealthCheck } from '../../../../store/src/entity-catalog/entity-catalog.types'; +import { EndpointsService } from '../../../core/src/core/endpoints.service'; +import { CoreModule } from '../../../core/src/public-api'; +import { SharedModule } from '../../../core/src/shared/shared.module'; +import { GetSystemInfo } from '../../../store/src/actions/system.actions'; +import { EntityCatalogModule } from '../../../store/src/entity-catalog.module'; +import { EndpointHealthCheck } from '../../../store/src/entity-catalog/entity-catalog.types'; +import { AppState } from '../../../store/src/public-api'; import { HELM_ENDPOINT_TYPE } from './helm-entity-factory'; import { generateHelmEntities } from './helm-entity-generator'; import { HelmHubRegistrationComponent } from './helm-hub-registration/helm-hub-registration.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/helm.store.module.ts b/src/frontend/packages/kubernetes/src/helm/helm.store.module.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/helm.store.module.ts rename to src/frontend/packages/kubernetes/src/helm/helm.store.module.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.html b/src/frontend/packages/kubernetes/src/helm/list-types/monocular-chart-card/monocular-chart-card.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.html rename to src/frontend/packages/kubernetes/src/helm/list-types/monocular-chart-card/monocular-chart-card.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.scss b/src/frontend/packages/kubernetes/src/helm/list-types/monocular-chart-card/monocular-chart-card.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.scss rename to src/frontend/packages/kubernetes/src/helm/list-types/monocular-chart-card/monocular-chart-card.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.spec.ts b/src/frontend/packages/kubernetes/src/helm/list-types/monocular-chart-card/monocular-chart-card.component.spec.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.spec.ts rename to src/frontend/packages/kubernetes/src/helm/list-types/monocular-chart-card/monocular-chart-card.component.spec.ts index 14b4272b60..7075b41929 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/helm/list-types/monocular-chart-card/monocular-chart-card.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseTestModulesNoShared } from '../../../../../../core/test-framework/core-test.helper'; +import { BaseTestModulesNoShared } from '../../../../../core/test-framework/core-test.helper'; import { ChartItemComponent } from '../../monocular/chart-item/chart-item.component'; import { ListItemComponent } from '../../monocular/list-item/list-item.component'; import { ChartsService } from '../../monocular/shared/services/charts.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.theme.scss b/src/frontend/packages/kubernetes/src/helm/list-types/monocular-chart-card/monocular-chart-card.component.theme.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.theme.scss rename to src/frontend/packages/kubernetes/src/helm/list-types/monocular-chart-card/monocular-chart-card.component.theme.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.ts b/src/frontend/packages/kubernetes/src/helm/list-types/monocular-chart-card/monocular-chart-card.component.ts similarity index 82% rename from src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.ts rename to src/frontend/packages/kubernetes/src/helm/list-types/monocular-chart-card/monocular-chart-card.component.ts index 06aa2fa3cb..fe540b5c52 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.ts +++ b/src/frontend/packages/kubernetes/src/helm/list-types/monocular-chart-card/monocular-chart-card.component.ts @@ -1,6 +1,6 @@ import { Component, Input } from '@angular/core'; -import { CardCell } from '../../../../../../core/src/shared/components/list/list.types'; +import { CardCell } from '../../../../../core/src/shared/components/list/list.types'; import { MonocularChart } from '../../store/helm.types'; @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-charts-data-source.ts b/src/frontend/packages/kubernetes/src/helm/list-types/monocular-charts-data-source.ts similarity index 71% rename from src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-charts-data-source.ts rename to src/frontend/packages/kubernetes/src/helm/list-types/monocular-charts-data-source.ts index 00d04b583f..e71de82d77 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-charts-data-source.ts +++ b/src/frontend/packages/kubernetes/src/helm/list-types/monocular-charts-data-source.ts @@ -1,10 +1,10 @@ import { Store } from '@ngrx/store'; -import { ListDataSource } from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; -import { IListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; -import { AppState, IRequestEntityTypeState } from '../../../../../store/src/app-state'; -import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; -import { PaginationEntityState } from '../../../../../store/src/types/pagination.types'; +import { ListDataSource } from '../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; +import { IListConfig } from '../../../../core/src/shared/components/list/list.component.types'; +import { IRequestEntityTypeState } from '../../../../store/src/app-state'; +import { AppState, EndpointModel } from '../../../../store/src/public-api'; +import { PaginationEntityState } from '../../../../store/src/types/pagination.types'; import { helmEntityCatalog } from '../helm-entity-catalog'; import { MonocularChart } from '../store/helm.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-charts-list-config.service.ts b/src/frontend/packages/kubernetes/src/helm/list-types/monocular-charts-list-config.service.ts similarity index 90% rename from src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-charts-list-config.service.ts rename to src/frontend/packages/kubernetes/src/helm/list-types/monocular-charts-list-config.service.ts index 427a61b9ae..5cf3124c39 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/list-types/monocular-charts-list-config.service.ts +++ b/src/frontend/packages/kubernetes/src/helm/list-types/monocular-charts-list-config.service.ts @@ -4,15 +4,15 @@ import { Store } from '@ngrx/store'; import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; import { filter, map } from 'rxjs/operators'; -import { EndpointsService } from '../../../../../core/src/core/endpoints.service'; -import { ITableColumn } from '../../../../../core/src/shared/components/list/list-table/table.types'; +import { EndpointsService } from '../../../../core/src/core/endpoints.service'; +import { ITableColumn } from '../../../../core/src/shared/components/list/list-table/table.types'; import { IListConfig, IListMultiFilterConfig, ListViewTypes, -} from '../../../../../core/src/shared/components/list/list.component.types'; -import { ListView } from '../../../../../store/src/actions/list.actions'; -import { AppState } from '../../../../../store/src/app-state'; +} from '../../../../core/src/shared/components/list/list.component.types'; +import { ListView } from '../../../../store/src/actions/list.actions'; +import { AppState } from '../../../../store/src/public-api'; import { defaultHelmKubeListPageSize } from '../../kubernetes/list-types/kube-helm-list-types'; import { HELM_ENDPOINT_TYPE } from '../helm-entity-factory'; import { ChartsService } from '../monocular/shared/services/charts.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.html b/src/frontend/packages/kubernetes/src/helm/monocular-tab-base/monocular-tab-base.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.html rename to src/frontend/packages/kubernetes/src/helm/monocular-tab-base/monocular-tab-base.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.scss b/src/frontend/packages/kubernetes/src/helm/monocular-tab-base/monocular-tab-base.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.scss rename to src/frontend/packages/kubernetes/src/helm/monocular-tab-base/monocular-tab-base.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.spec.ts b/src/frontend/packages/kubernetes/src/helm/monocular-tab-base/monocular-tab-base.component.spec.ts similarity index 83% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.spec.ts rename to src/frontend/packages/kubernetes/src/helm/monocular-tab-base/monocular-tab-base.component.spec.ts index 462903feb4..44d51292b1 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/helm/monocular-tab-base/monocular-tab-base.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../core/src/tab-nav.service'; -import { BaseTestModulesNoShared } from '../../../../../core/test-framework/core-test.helper'; +import { TabNavService } from '../../../../core/src/tab-nav.service'; +import { BaseTestModulesNoShared } from '../../../../core/test-framework/core-test.helper'; import { HelmModule } from '../helm.module'; import { MonocularTabBaseComponent } from './monocular-tab-base.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.ts b/src/frontend/packages/kubernetes/src/helm/monocular-tab-base/monocular-tab-base.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular-tab-base/monocular-tab-base.component.ts rename to src/frontend/packages/kubernetes/src/helm/monocular-tab-base/monocular-tab-base.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular.interceptor.ts b/src/frontend/packages/kubernetes/src/helm/monocular.interceptor.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular.interceptor.ts rename to src/frontend/packages/kubernetes/src/helm/monocular.interceptor.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/app.component.scss b/src/frontend/packages/kubernetes/src/helm/monocular/app.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/app.component.scss rename to src/frontend/packages/kubernetes/src/helm/monocular/app.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.html b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-info/chart-details-info.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.html rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-info/chart-details-info.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.scss b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-info/chart-details-info.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.scss rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-info/chart-details-info.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.spec.ts b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-info/chart-details-info.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.spec.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-info/chart-details-info.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.ts b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-info/chart-details-info.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-info/chart-details-info.component.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-info/chart-details-info.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.html b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.html rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.scss b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.scss rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.spec.ts b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.spec.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.ts b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-readme/chart-details-readme.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.html b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.html rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.scss b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.scss rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.spec.ts b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.spec.ts similarity index 65% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.spec.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.spec.ts index 0964fec6e2..dcd88dc1ab 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.spec.ts @@ -1,10 +1,10 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; -import { EndpointsService } from '../../../../../../../core/src/core/endpoints.service'; -import { UtilsService } from '../../../../../../../core/src/core/utils.service'; -import { BaseTestModulesNoShared } from '../../../../../../../core/test-framework/core-test.helper'; -import { PaginationMonitorFactory } from '../../../../../../../store/src/monitors/pagination-monitor.factory'; +import { EndpointsService } from '../../../../../../core/src/core/endpoints.service'; +import { UtilsService } from '../../../../../../core/src/core/utils.service'; +import { BaseTestModulesNoShared } from '../../../../../../core/test-framework/core-test.helper'; +import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; import { ChartDetailsUsageComponent } from './chart-details-usage.component'; describe('Component: ChartDetailsUsage', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.ts b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.ts similarity index 94% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.ts index 0f213b27e8..425fe87746 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.ts +++ b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-usage/chart-details-usage.component.ts @@ -4,7 +4,7 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { DomSanitizer } from '@angular/platform-browser'; import { ActivatedRoute } from '@angular/router'; -import { EndpointsService } from '../../../../../../../core/src/core/endpoints.service'; +import { EndpointsService } from '../../../../../../core/src/core/endpoints.service'; import { Chart } from '../../shared/models/chart'; import { getMonocularEndpoint } from '../../stratos-monocular.helper'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.html b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.html rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.scss b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.scss rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.spec.ts b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.spec.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.ts b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details-versions/chart-details-versions.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.html b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.html rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.scss b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.scss rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.spec.ts b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details.component.spec.ts similarity index 89% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.spec.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details.component.spec.ts index 93812f2d5b..4e1be686cd 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details.component.spec.ts @@ -5,9 +5,9 @@ import { RouterTestingModule } from '@angular/router/testing'; import { EntitySummaryTitleComponent, -} from '../../../../../../core/src/shared/components/entity-summary-title/entity-summary-title.component'; -import { BaseTestModulesNoShared } from '../../../../../../core/test-framework/core-test.helper'; -import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; +} from '../../../../../core/src/shared/components/entity-summary-title/entity-summary-title.component'; +import { BaseTestModulesNoShared } from '../../../../../core/test-framework/core-test.helper'; +import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; import { ChartItemComponent } from '../chart-item/chart-item.component'; import { ListItemComponent } from '../list-item/list-item.component'; import { LoaderComponent } from '../loader/loader.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.ts b/src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-details/chart-details.component.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-details/chart-details.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-index/chart-index.component.html b/src/frontend/packages/kubernetes/src/helm/monocular/chart-index/chart-index.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-index/chart-index.component.html rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-index/chart-index.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-index/chart-index.component.scss b/src/frontend/packages/kubernetes/src/helm/monocular/chart-index/chart-index.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-index/chart-index.component.scss rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-index/chart-index.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-index/chart-index.component.spec.ts b/src/frontend/packages/kubernetes/src/helm/monocular/chart-index/chart-index.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-index/chart-index.component.spec.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-index/chart-index.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-index/chart-index.component.ts b/src/frontend/packages/kubernetes/src/helm/monocular/chart-index/chart-index.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-index/chart-index.component.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-index/chart-index.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.html b/src/frontend/packages/kubernetes/src/helm/monocular/chart-item/chart-item.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.html rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-item/chart-item.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.scss b/src/frontend/packages/kubernetes/src/helm/monocular/chart-item/chart-item.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.scss rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-item/chart-item.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.spec.ts b/src/frontend/packages/kubernetes/src/helm/monocular/chart-item/chart-item.component.spec.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.spec.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-item/chart-item.component.spec.ts index e07911a9da..e8e62854b0 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/helm/monocular/chart-item/chart-item.component.spec.ts @@ -4,7 +4,7 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { createBasicStoreModule } from '../../../../../../store/testing/public-api'; +import { createBasicStoreModule } from '../../../../../store/testing/public-api'; import { ChartsService } from '../shared/services/charts.service'; import { ConfigService } from '../shared/services/config.service'; import { ChartItemComponent } from './chart-item.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.ts b/src/frontend/packages/kubernetes/src/helm/monocular/chart-item/chart-item.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-item/chart-item.component.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-item/chart-item.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-list/chart-list.component.html b/src/frontend/packages/kubernetes/src/helm/monocular/chart-list/chart-list.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-list/chart-list.component.html rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-list/chart-list.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-list/chart-list.component.scss b/src/frontend/packages/kubernetes/src/helm/monocular/chart-list/chart-list.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-list/chart-list.component.scss rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-list/chart-list.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-list/chart-list.component.spec.ts b/src/frontend/packages/kubernetes/src/helm/monocular/chart-list/chart-list.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-list/chart-list.component.spec.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-list/chart-list.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-list/chart-list.component.ts b/src/frontend/packages/kubernetes/src/helm/monocular/chart-list/chart-list.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/chart-list/chart-list.component.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/chart-list/chart-list.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/charts/charts.component.html b/src/frontend/packages/kubernetes/src/helm/monocular/charts/charts.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/charts/charts.component.html rename to src/frontend/packages/kubernetes/src/helm/monocular/charts/charts.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/charts/charts.component.scss b/src/frontend/packages/kubernetes/src/helm/monocular/charts/charts.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/charts/charts.component.scss rename to src/frontend/packages/kubernetes/src/helm/monocular/charts/charts.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/charts/charts.component.spec.ts b/src/frontend/packages/kubernetes/src/helm/monocular/charts/charts.component.spec.ts similarity index 96% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/charts/charts.component.spec.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/charts/charts.component.spec.ts index eae5836ec8..abe09fe639 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/charts/charts.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/helm/monocular/charts/charts.component.spec.ts @@ -6,7 +6,7 @@ import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { of } from 'rxjs'; -import { createBasicStoreModule } from '../../../../../../store/testing/public-api'; +import { createBasicStoreModule } from '../../../../../store/testing/public-api'; import { ChartItemComponent } from '../chart-item/chart-item.component'; import { ChartListComponent } from '../chart-list/chart-list.component'; import { LoaderComponent } from '../loader/loader.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/charts/charts.component.ts b/src/frontend/packages/kubernetes/src/helm/monocular/charts/charts.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/charts/charts.component.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/charts/charts.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-filters/list-filters.component.html b/src/frontend/packages/kubernetes/src/helm/monocular/list-filters/list-filters.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-filters/list-filters.component.html rename to src/frontend/packages/kubernetes/src/helm/monocular/list-filters/list-filters.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-filters/list-filters.component.scss b/src/frontend/packages/kubernetes/src/helm/monocular/list-filters/list-filters.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-filters/list-filters.component.scss rename to src/frontend/packages/kubernetes/src/helm/monocular/list-filters/list-filters.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-filters/list-filters.component.spec.ts b/src/frontend/packages/kubernetes/src/helm/monocular/list-filters/list-filters.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-filters/list-filters.component.spec.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/list-filters/list-filters.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-filters/list-filters.component.ts b/src/frontend/packages/kubernetes/src/helm/monocular/list-filters/list-filters.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-filters/list-filters.component.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/list-filters/list-filters.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.html b/src/frontend/packages/kubernetes/src/helm/monocular/list-item/list-item.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.html rename to src/frontend/packages/kubernetes/src/helm/monocular/list-item/list-item.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.scss b/src/frontend/packages/kubernetes/src/helm/monocular/list-item/list-item.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.scss rename to src/frontend/packages/kubernetes/src/helm/monocular/list-item/list-item.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.spec.ts b/src/frontend/packages/kubernetes/src/helm/monocular/list-item/list-item.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.spec.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/list-item/list-item.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.ts b/src/frontend/packages/kubernetes/src/helm/monocular/list-item/list-item.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/list-item/list-item.component.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/list-item/list-item.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/loader/loader.component.html b/src/frontend/packages/kubernetes/src/helm/monocular/loader/loader.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/loader/loader.component.html rename to src/frontend/packages/kubernetes/src/helm/monocular/loader/loader.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/loader/loader.component.scss b/src/frontend/packages/kubernetes/src/helm/monocular/loader/loader.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/loader/loader.component.scss rename to src/frontend/packages/kubernetes/src/helm/monocular/loader/loader.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/loader/loader.component.spec.ts b/src/frontend/packages/kubernetes/src/helm/monocular/loader/loader.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/loader/loader.component.spec.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/loader/loader.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/loader/loader.component.ts b/src/frontend/packages/kubernetes/src/helm/monocular/loader/loader.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/loader/loader.component.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/loader/loader.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/monocular.module.ts b/src/frontend/packages/kubernetes/src/helm/monocular/monocular.module.ts similarity index 96% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/monocular.module.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/monocular.module.ts index 240e637d44..450e9f8864 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/monocular.module.ts +++ b/src/frontend/packages/kubernetes/src/helm/monocular/monocular.module.ts @@ -1,7 +1,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { CoreModule, SharedModule } from '../../../../../core/src/public-api'; +import { CoreModule, SharedModule } from '../../../../core/src/public-api'; import { ChartDetailsInfoComponent } from './chart-details/chart-details-info/chart-details-info.component'; import { ChartDetailsReadmeComponent } from './chart-details/chart-details-readme/chart-details-readme.component'; import { ChartDetailsUsageComponent } from './chart-details/chart-details-usage/chart-details-usage.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/panel/panel.component.html b/src/frontend/packages/kubernetes/src/helm/monocular/panel/panel.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/panel/panel.component.html rename to src/frontend/packages/kubernetes/src/helm/monocular/panel/panel.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/panel/panel.component.scss b/src/frontend/packages/kubernetes/src/helm/monocular/panel/panel.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/panel/panel.component.scss rename to src/frontend/packages/kubernetes/src/helm/monocular/panel/panel.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/panel/panel.component.spec.ts b/src/frontend/packages/kubernetes/src/helm/monocular/panel/panel.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/panel/panel.component.spec.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/panel/panel.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/panel/panel.component.ts b/src/frontend/packages/kubernetes/src/helm/monocular/panel/panel.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/panel/panel.component.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/panel/panel.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/models/chart-version.ts b/src/frontend/packages/kubernetes/src/helm/monocular/shared/models/chart-version.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/models/chart-version.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/shared/models/chart-version.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/models/chart.ts b/src/frontend/packages/kubernetes/src/helm/monocular/shared/models/chart.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/models/chart.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/shared/models/chart.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/models/maintainer.ts b/src/frontend/packages/kubernetes/src/helm/monocular/shared/models/maintainer.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/models/maintainer.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/shared/models/maintainer.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/models/repo.ts b/src/frontend/packages/kubernetes/src/helm/monocular/shared/models/repo.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/models/repo.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/shared/models/repo.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/seo.data.ts b/src/frontend/packages/kubernetes/src/helm/monocular/shared/seo.data.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/seo.data.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/shared/seo.data.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/chart.service.mock.ts b/src/frontend/packages/kubernetes/src/helm/monocular/shared/services/chart.service.mock.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/chart.service.mock.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/shared/services/chart.service.mock.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts b/src/frontend/packages/kubernetes/src/helm/monocular/shared/services/charts.service.ts similarity index 98% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/shared/services/charts.service.ts index 80590f39ba..f239ab2c97 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/charts.service.ts +++ b/src/frontend/packages/kubernetes/src/helm/monocular/shared/services/charts.service.ts @@ -193,7 +193,7 @@ export class ChartsService { * @return An observable containing an array of ChartVersions */ getVersions(repo: string, chartName: string): Observable { - return this.http.get<{ data: any; }>(`${this.hostname}/v1/charts/${repo}/${chartName}/versions`).pipe( + return this.http.get<{ data: any, }>(`${this.hostname}/v1/charts/${repo}/${chartName}/versions`).pipe( map(m => this.extractData(m)), catchError(this.handleError) ); @@ -253,7 +253,7 @@ export class ChartsService { } - private extractData(res: { data: any; }) { + private extractData(res: { data: any, }) { return res.data || {}; } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/config.service.ts b/src/frontend/packages/kubernetes/src/helm/monocular/shared/services/config.service.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/config.service.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/shared/services/config.service.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/menu.service.ts b/src/frontend/packages/kubernetes/src/helm/monocular/shared/services/menu.service.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/menu.service.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/shared/services/menu.service.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/repos.service.ts b/src/frontend/packages/kubernetes/src/helm/monocular/shared/services/repos.service.ts similarity index 95% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/repos.service.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/shared/services/repos.service.ts index 8903061080..56dbe19649 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/shared/services/repos.service.ts +++ b/src/frontend/packages/kubernetes/src/helm/monocular/shared/services/repos.service.ts @@ -31,7 +31,7 @@ export class ReposService { ); } - private extractData(res: { data: any; }) { + private extractData(res: { data: any, }) { return res.data || {}; } diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/stratos-monocular-providers.helpers.ts b/src/frontend/packages/kubernetes/src/helm/monocular/stratos-monocular-providers.helpers.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/stratos-monocular-providers.helpers.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/stratos-monocular-providers.helpers.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/monocular/stratos-monocular.helper.ts b/src/frontend/packages/kubernetes/src/helm/monocular/stratos-monocular.helper.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/monocular/stratos-monocular.helper.ts rename to src/frontend/packages/kubernetes/src/helm/monocular/stratos-monocular.helper.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.action-builders.ts b/src/frontend/packages/kubernetes/src/helm/store/helm.action-builders.ts similarity index 89% rename from src/frontend/packages/suse-extensions/src/custom/helm/store/helm.action-builders.ts rename to src/frontend/packages/kubernetes/src/helm/store/helm.action-builders.ts index 708227b098..b6b37b9566 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.action-builders.ts +++ b/src/frontend/packages/kubernetes/src/helm/store/helm.action-builders.ts @@ -1,5 +1,5 @@ -import { OrchestratedActionBuilders } from '../../../../../store/src/entity-catalog/action-orchestrator/action-orchestrator'; -import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; +import { OrchestratedActionBuilders } from '../../../../store/src/entity-catalog/action-orchestrator/action-orchestrator'; +import { EndpointModel } from '../../../../store/src/public-api'; import { GetHelmChartVersions, GetHelmVersions, GetMonocularCharts, HelmInstall, HelmSynchronise } from './helm.actions'; import { HelmInstallValues } from './helm.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.actions.ts b/src/frontend/packages/kubernetes/src/helm/store/helm.actions.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/helm/store/helm.actions.ts rename to src/frontend/packages/kubernetes/src/helm/store/helm.actions.ts index 883cdd4414..ef32453dbf 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.actions.ts +++ b/src/frontend/packages/kubernetes/src/helm/store/helm.actions.ts @@ -1,8 +1,8 @@ import { Action } from '@ngrx/store'; -import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; -import { PaginatedAction } from '../../../../../store/src/types/pagination.types'; -import { EntityRequestAction } from '../../../../../store/src/types/request.types'; +import { EndpointModel } from '../../../../store/src/public-api'; +import { PaginatedAction } from '../../../../store/src/types/pagination.types'; +import { EntityRequestAction } from '../../../../store/src/types/request.types'; import { HELM_ENDPOINT_TYPE, helmEntityFactory, diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts b/src/frontend/packages/kubernetes/src/helm/store/helm.effects.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts rename to src/frontend/packages/kubernetes/src/helm/store/helm.effects.ts index 7a05a8c357..a8723b347f 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.effects.ts +++ b/src/frontend/packages/kubernetes/src/helm/store/helm.effects.ts @@ -6,7 +6,7 @@ import { Action, Store } from '@ngrx/store'; import { combineLatest, Observable, of } from 'rxjs'; import { catchError, first, flatMap, map, mergeMap, withLatestFrom } from 'rxjs/operators'; -import { environment } from '../../../../../core/src/environments/environment'; +import { environment } from '../../../../core/src/environments/environment'; import { EndpointActionComplete, GET_ENDPOINTS_SUCCESS, @@ -14,23 +14,25 @@ import { REGISTER_ENDPOINTS_SUCCESS, UNREGISTER_ENDPOINTS_SUCCESS, UnregisterEndpoint, -} from '../../../../../store/src/actions/endpoint.actions'; -import { ClearPaginationOfType, ResetPaginationOfType } from '../../../../../store/src/actions/pagination.actions'; -import { AppState } from '../../../../../store/src/app-state'; -import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; -import { EntitySchema } from '../../../../../store/src/helpers/entity-schema'; -import { isJetstreamError } from '../../../../../store/src/jetstream'; -import { ApiRequestTypes } from '../../../../../store/src/reducers/api-request-reducer/request-helpers'; -import { endpointOfTypeSelector } from '../../../../../store/src/selectors/endpoint.selectors'; -import { stratosEntityCatalog } from '../../../../../store/src/stratos-entity-catalog'; -import { NormalizedResponse } from '../../../../../store/src/types/api.types'; -import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; +} from '../../../../store/src/actions/endpoint.actions'; +import { ClearPaginationOfType, ResetPaginationOfType } from '../../../../store/src/actions/pagination.actions'; +import { EntitySchema } from '../../../../store/src/helpers/entity-schema'; +import { isJetstreamError } from '../../../../store/src/jetstream'; +import { + AppState, + EndpointModel, + entityCatalog, + NormalizedResponse, + WrapperRequestActionSuccess, +} from '../../../../store/src/public-api'; +import { ApiRequestTypes } from '../../../../store/src/reducers/api-request-reducer/request-helpers'; +import { endpointOfTypeSelector } from '../../../../store/src/selectors/endpoint.selectors'; +import { stratosEntityCatalog } from '../../../../store/src/stratos-entity-catalog'; import { EntityRequestAction, StartRequestAction, WrapperRequestActionFailed, - WrapperRequestActionSuccess, -} from '../../../../../store/src/types/request.types'; +} from '../../../../store/src/types/request.types'; import { helmEntityCatalog } from '../helm-entity-catalog'; import { HELM_ENDPOINT_TYPE, HELM_HUB_ENDPOINT_TYPE, HELM_REPO_ENDPOINT_TYPE } from '../helm-entity-factory'; import { Chart } from '../monocular/shared/models/chart'; @@ -339,7 +341,7 @@ export class HelmEffects { return 'Helm API request error'; } - public static createHelmError(err: any): { status: string, message: string; } { + public static createHelmError(err: any): { status: string, message: string, } { let unwrapped = err; if (err.error) { unwrapped = err.error; diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/store/helm.types.ts b/src/frontend/packages/kubernetes/src/helm/store/helm.types.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/store/helm.types.ts rename to src/frontend/packages/kubernetes/src/helm/store/helm.types.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/tabs/catalog-tab/catalog-tab.component.html b/src/frontend/packages/kubernetes/src/helm/tabs/catalog-tab/catalog-tab.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/tabs/catalog-tab/catalog-tab.component.html rename to src/frontend/packages/kubernetes/src/helm/tabs/catalog-tab/catalog-tab.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/tabs/catalog-tab/catalog-tab.component.scss b/src/frontend/packages/kubernetes/src/helm/tabs/catalog-tab/catalog-tab.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/tabs/catalog-tab/catalog-tab.component.scss rename to src/frontend/packages/kubernetes/src/helm/tabs/catalog-tab/catalog-tab.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/tabs/catalog-tab/catalog-tab.component.spec.ts b/src/frontend/packages/kubernetes/src/helm/tabs/catalog-tab/catalog-tab.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/tabs/catalog-tab/catalog-tab.component.spec.ts rename to src/frontend/packages/kubernetes/src/helm/tabs/catalog-tab/catalog-tab.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/tabs/catalog-tab/catalog-tab.component.ts b/src/frontend/packages/kubernetes/src/helm/tabs/catalog-tab/catalog-tab.component.ts similarity index 80% rename from src/frontend/packages/suse-extensions/src/custom/helm/tabs/catalog-tab/catalog-tab.component.ts rename to src/frontend/packages/kubernetes/src/helm/tabs/catalog-tab/catalog-tab.component.ts index 4628d67ca9..f6047d2db4 100644 --- a/src/frontend/packages/suse-extensions/src/custom/helm/tabs/catalog-tab/catalog-tab.component.ts +++ b/src/frontend/packages/kubernetes/src/helm/tabs/catalog-tab/catalog-tab.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; -import { ListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; +import { ListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; import { MonocularChartsListConfig } from '../../list-types/monocular-charts-list-config.service'; @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/helm/theme.scss b/src/frontend/packages/kubernetes/src/helm/theme.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/helm/theme.scss rename to src/frontend/packages/kubernetes/src/helm/theme.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-routing.module.ts b/src/frontend/packages/kubernetes/src/kube-package-routing.module.ts similarity index 96% rename from src/frontend/packages/suse-extensions/src/custom/suse-routing.module.ts rename to src/frontend/packages/kubernetes/src/kube-package-routing.module.ts index efabd4582e..ed3d3e80e7 100644 --- a/src/frontend/packages/suse-extensions/src/custom/suse-routing.module.ts +++ b/src/frontend/packages/kubernetes/src/kube-package-routing.module.ts @@ -54,4 +54,4 @@ const customRoutes: Routes = [ ], declarations: [] }) -export class SuseRoutingModule { } +export class KubePackageModuleRoutingModule { } diff --git a/src/frontend/packages/kubernetes/src/kube-package.module.ts b/src/frontend/packages/kubernetes/src/kube-package.module.ts new file mode 100644 index 0000000000..286802cf3a --- /dev/null +++ b/src/frontend/packages/kubernetes/src/kube-package.module.ts @@ -0,0 +1,22 @@ +import { NgModule } from '@angular/core'; + +import { CoreModule, MDAppModule, SharedModule } from '../../core/src/public-api'; +import { HelmSetupModule } from './helm/helm.setup.module'; +import { KubernetesSetupModule } from './kubernetes/kubernetes.setup.module'; + + +@NgModule({ + imports: [ + CoreModule, + SharedModule, + MDAppModule, + KubernetesSetupModule, + HelmSetupModule, + ], + // FIXME: Ensure that anything lazy loaded/in kube endpoint pages is not included here - #3675 + declarations: [ + ], + entryComponents: [ + ] +}) +export class KubePackageModule { } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.html b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.spec.ts similarity index 81% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.spec.ts index 8757c86532..60794e0912 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.spec.ts @@ -1,10 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { SharedModule } from '../../../../../core/src/public-api'; +import { SidePanelService } from '../../../../../core/src/shared/services/side-panel.service'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; -import { SidePanelService } from './../../../../../../core/src/shared/services/side-panel.service'; -import { SharedModule } from './../../../../../../core/src/shared/shared.module'; import { AnalysisReportRunnerComponent } from './analysis-report-runner.component'; describe('AnalysisReportRunnerComponent', () => { @@ -13,7 +13,7 @@ describe('AnalysisReportRunnerComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ AnalysisReportRunnerComponent ], + declarations: [AnalysisReportRunnerComponent], imports: [ SharedModule, KubernetesBaseTestModules, @@ -25,7 +25,7 @@ describe('AnalysisReportRunnerComponent', () => { SidePanelService, ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-runner/analysis-report-runner.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.html b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.spec.ts similarity index 88% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.spec.ts index 4d421a38e4..7740777a66 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { MDAppModule } from '../../../../../../core/src/public-api'; +import { MDAppModule } from '../../../../../core/src/public-api'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; @@ -12,7 +12,7 @@ describe('AnalysisReportSelectorComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ AnalysisReportSelectorComponent ], + declarations: [AnalysisReportSelectorComponent], imports: [ KubernetesBaseTestModules, MDAppModule @@ -23,7 +23,7 @@ describe('AnalysisReportSelectorComponent', () => { KubeBaseGuidMock, ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.ts similarity index 96% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.ts index 5b5bce37a5..07f430e4cb 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-selector/analysis-report-selector.component.ts @@ -3,7 +3,7 @@ import moment from 'moment'; import { Observable, Subscription } from 'rxjs'; import { map, tap } from 'rxjs/operators'; -import { safeUnsubscribe } from '../../../../../../core/src/core/utils.service'; +import { safeUnsubscribe } from '../../../../../core/src/core/utils.service'; import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; import { AnalysisReport } from '../../store/kube.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.html b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-viewer.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-viewer.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-viewer.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-viewer.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-viewer.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-viewer.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-viewer.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/analysis-report-viewer.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/analysis-report-viewer.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.html b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.spec.ts similarity index 88% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.spec.ts index babd043739..6ad49bf3aa 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { MDAppModule } from '../../../../../../core/src/public-api'; +import { MDAppModule } from '../../../../../core/src/public-api'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; @@ -12,7 +12,7 @@ describe('KubeScoreReportViewerComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ KubeScoreReportViewerComponent ], + declarations: [KubeScoreReportViewerComponent], imports: [ KubernetesBaseTestModules, MDAppModule @@ -23,7 +23,7 @@ describe('KubeScoreReportViewerComponent', () => { KubeBaseGuidMock, ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/kube-score-report-viewer/kube-score-report-viewer.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.html b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.spec.ts similarity index 88% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.spec.ts index 192d66f8da..eb03a6aa2f 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { MDAppModule } from '../../../../../../core/src/public-api'; +import { MDAppModule } from '../../../../../core/src/public-api'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; @@ -12,7 +12,7 @@ describe('PopeyeReportViewerComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ PopeyeReportViewerComponent ], + declarations: [PopeyeReportViewerComponent], imports: [ KubernetesBaseTestModules, MDAppModule @@ -23,7 +23,7 @@ describe('PopeyeReportViewerComponent', () => { KubeBaseGuidMock, ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/popeye-report-viewer/popeye-report-viewer.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.html b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.ts similarity index 91% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.ts index 5abfac176e..139644c612 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-preview.component.ts @@ -15,7 +15,7 @@ export class ResourceAlertPreviewComponent implements PreviewableComponent { constructor() { } - setProps(props: { [key: string]: any; }): void { + setProps(props: { [key: string]: any, }): void { this.resource = props.resource; this.title = `${this.resource.kind} Alerts`; } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.html b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.spec.ts similarity index 88% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.spec.ts index 9f158d36cf..b80151d9b3 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { MDAppModule } from '../../../../../../../core/src/public-api'; +import { MDAppModule } from '../../../../../../core/src/public-api'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; import { KubernetesAnalysisService } from '../../../services/kubernetes.analysis.service'; @@ -12,7 +12,7 @@ describe('ResourceAlertViewComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ ResourceAlertViewComponent ], + declarations: [ResourceAlertViewComponent], imports: [ KubernetesBaseTestModules, MDAppModule @@ -23,7 +23,7 @@ describe('ResourceAlertViewComponent', () => { KubeBaseGuidMock, ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/analysis-report-viewer/resource-alert-preview/resource-alert-view/resource-alert-view.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.html b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.spec.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.spec.ts index f9ef26134c..66400910ac 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.spec.ts @@ -2,7 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { FormBuilder } from '@angular/forms'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { MDAppModule, SharedModule } from '../../../../../../core/src/public-api'; +import { MDAppModule, SharedModule } from '../../../../../core/src/public-api'; import { KubernetesAWSAuthFormComponent } from './kubernetes-aws-auth-form.component'; describe('KubernetesAWSAuthFormComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.ts similarity index 83% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.ts index 2c67e0049c..78f5d08465 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component.ts @@ -1,7 +1,8 @@ import { Component, Input } from '@angular/core'; import { FormGroup } from '@angular/forms'; -import { IAuthForm } from '../../../../../../store/src/extension-types'; +import { IAuthForm } from '../../../../../store/src/extension-types'; + @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.html b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.spec.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.spec.ts index 74ccf5ef71..860cbec1e8 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.spec.ts @@ -3,7 +3,7 @@ import { FormBuilder } from '@angular/forms'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { CoreModule } from 'frontend/packages/core/src/core/core.module'; -import { SharedModule } from './../../../../../../core/src/shared/shared.module'; +import { SharedModule } from './../../../../../core/src/shared/shared.module'; import { KubernetesCertsAuthFormComponent } from './kubernetes-certs-auth-form.component'; describe('KubernetesCertsAuthFormComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.ts similarity index 96% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.ts index 889f8b5a7d..7fae0d007d 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-certs-auth-form/kubernetes-certs-auth-form.component.ts @@ -1,8 +1,7 @@ import { Component, Input } from '@angular/core'; import { FormGroup } from '@angular/forms'; -import { EndpointAuthValues, IEndpointAuthComponent } from '../../../../../../store/src/extension-types'; - +import { EndpointAuthValues, IEndpointAuthComponent } from '../../../../../store/src/extension-types'; @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.html b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.spec.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.spec.ts index 756e42f1bc..dba933224c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.spec.ts @@ -2,7 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { FormBuilder } from '@angular/forms'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { SharedModule } from './../../../../../../core/src/shared/shared.module'; +import { SharedModule } from './../../../../../core/src/shared/shared.module'; import { KubernetesConfigAuthFormComponent } from './kubernetes-config-auth-form.component'; describe('KubernetesConfigAuthFormComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.ts index 424f0ce2ee..dc7491f0e8 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component.ts @@ -1,7 +1,8 @@ import { Component, Input } from '@angular/core'; import { FormGroup } from '@angular/forms'; -import { EndpointAuthValues, IEndpointAuthComponent } from '../../../../../../store/src/extension-types'; +import { EndpointAuthValues, IEndpointAuthComponent } from '../../../../../store/src/extension-types'; + @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.html b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.spec.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.spec.ts index 32c5fa6b31..24fe572feb 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.spec.ts @@ -2,7 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { FormBuilder } from '@angular/forms'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { SharedModule } from './../../../../../../core/src/shared/shared.module'; +import { SharedModule } from './../../../../../core/src/shared/shared.module'; import { KubernetesGKEAuthFormComponent } from './kubernetes-gke-auth-form.component'; describe('KubernetesGKEAuthFormComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.ts index ef2c74b0ed..c810b406a1 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component.ts @@ -1,7 +1,8 @@ import { Component, Input } from '@angular/core'; import { FormGroup } from '@angular/forms'; -import { EndpointAuthValues, IEndpointAuthComponent } from '../../../../../../store/src/extension-types'; +import { EndpointAuthValues, IEndpointAuthComponent } from '../../../../../store/src/extension-types'; + @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-auth.helper.ts b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-auth.helper.ts similarity index 92% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-auth.helper.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-auth.helper.ts index 158d36edd0..e6caba2fd0 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-auth.helper.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-auth.helper.ts @@ -1,10 +1,10 @@ import { ComponentFactoryResolver, Injector } from '@angular/core'; import { FormBuilder } from '@angular/forms'; -import { ConnectEndpointData } from '../../../../../core/src/features/endpoints/connect.service'; -import { RowState } from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; -import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; -import { EndpointAuthTypeConfig, IAuthForm } from '../../../../../store/src/extension-types'; +import { ConnectEndpointData } from '../../../../core/src/features/endpoints/connect.service'; +import { RowState } from '../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; +import { EndpointAuthTypeConfig, IAuthForm } from '../../../../store/src/extension-types'; +import { entityCatalog } from '../../../../store/src/public-api'; import { KUBERNETES_ENDPOINT_TYPE } from '../kubernetes-entity-factory'; import { KubeConfigFileCluster, KubeConfigFileUser } from './kube-config.types'; @@ -14,7 +14,7 @@ import { KubeConfigFileCluster, KubeConfigFileUser } from './kube-config.types'; */ export class KubeConfigAuthHelper { - authTypes: { [name: string]: EndpointAuthTypeConfig } = {}; + authTypes: { [name: string]: EndpointAuthTypeConfig, } = {}; public subTypes = []; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.ts similarity index 91% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.ts index a330b5a994..a8d27482c5 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-import/kube-config-import.component.ts @@ -4,25 +4,25 @@ import { Store } from '@ngrx/store'; import { BehaviorSubject, Observable, of as observableOf, Subscription } from 'rxjs'; import { distinctUntilChanged, filter, first, map, pairwise, startWith, withLatestFrom } from 'rxjs/operators'; -import { EndpointsService } from '../../../../../../core/src/core/endpoints.service'; -import { safeUnsubscribe } from '../../../../../../core/src/core/utils.service'; +import { EndpointsService } from '../../../../../core/src/core/endpoints.service'; +import { safeUnsubscribe } from '../../../../../core/src/core/utils.service'; import { ConnectEndpointConfig, ConnectEndpointData, ConnectEndpointService, -} from '../../../../../../core/src/features/endpoints/connect.service'; +} from '../../../../../core/src/features/endpoints/connect.service'; import { IActionMonitorComponentState, -} from '../../../../../../core/src/shared/components/app-action-monitor-icon/app-action-monitor-icon.component'; +} from '../../../../../core/src/shared/components/app-action-monitor-icon/app-action-monitor-icon.component'; import { ITableListDataSource, RowState, -} from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; -import { ITableColumn } from '../../../../../../core/src/shared/components/list/list-table/table.types'; -import { StepOnNextFunction } from '../../../../../../core/src/shared/components/stepper/step/step.component'; -import { AppState } from '../../../../../../store/src/app-state'; -import { ActionState } from '../../../../../../store/src/reducers/api-request-reducer/types'; -import { stratosEntityCatalog } from '../../../../../../store/src/stratos-entity-catalog'; +} from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; +import { ITableColumn } from '../../../../../core/src/shared/components/list/list-table/table.types'; +import { StepOnNextFunction } from '../../../../../core/src/shared/components/stepper/step/step.component'; +import { AppState } from '../../../../../store/src/public-api'; +import { ActionState } from '../../../../../store/src/reducers/api-request-reducer/types'; +import { stratosEntityCatalog } from '../../../../../store/src/stratos-entity-catalog'; import { KUBERNETES_ENDPOINT_TYPE } from '../../kubernetes-entity-factory'; import { KubeConfigAuthHelper } from '../kube-config-auth.helper'; import { KubeConfigFileCluster, KubeConfigImportAction, KubeImportState } from '../kube-config.types'; @@ -272,7 +272,7 @@ export class KubeConfigImportComponent implements OnDestroy { } }); this.data.next(imports); - } + }; // Finish - go back to the endpoints view onNext: StepOnNextFunction = () => { @@ -289,10 +289,10 @@ export class KubeConfigImportComponent implements OnDestroy { ).subscribe(imports => { // Go through the imports and dispatch the actions to perform them in sequence this.processAction([...imports]); - }) + }); return observableOf({ success: true, ignoreSuccess: true }); } - } + }; // These two should be somewhere else private getUpdatingState(actionState$: Observable): Observable { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.ts similarity index 76% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.ts index ee59c019f8..d9d8da3697 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-import/kube-config-table-import-status/kube-config-table-import-status.component.ts @@ -3,8 +3,8 @@ import { Observable } from 'rxjs'; import { IActionMonitorComponentState, -} from '../../../../../../../core/src/shared/components/app-action-monitor-icon/app-action-monitor-icon.component'; -import { TableCellCustom } from '../../../../../../../core/src/shared/components/list/list.types'; +} from '../../../../../../core/src/shared/components/app-action-monitor-icon/app-action-monitor-icon.component'; +import { TableCellCustom } from '../../../../../../core/src/shared/components/list/list.types'; import { KubeConfigFileCluster } from '../../kube-config.types'; @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-registration.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-registration.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-registration.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-registration.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-registration.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-registration.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-registration.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-registration.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-registration.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-registration.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-registration.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-registration.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-registration.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-registration.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-registration.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-registration.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.ts similarity index 92% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.ts index 7a0f02b017..9f3655ce82 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-selection/kube-config-selection.component.ts @@ -6,13 +6,13 @@ import { first, map, switchMap } from 'rxjs/operators'; import { ITableListDataSource, RowState, -} from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; +} from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; import { TableHeaderSelectComponent, -} from '../../../../../../core/src/shared/components/list/list-table/table-header-select/table-header-select.component'; -import { ITableColumn } from '../../../../../../core/src/shared/components/list/list-table/table.types'; -import { SnackBarService } from '../../../../../../core/src/shared/services/snackbar.service'; -import { AppState } from '../../../../../../store/src/app-state'; +} from '../../../../../core/src/shared/components/list/list-table/table-header-select/table-header-select.component'; +import { ITableColumn } from '../../../../../core/src/shared/components/list/list-table/table.types'; +import { SnackBarService } from '../../../../../core/src/shared/services/snackbar.service'; +import { AppState } from '../../../../../store/src/public-api'; import { KubeConfigHelper } from '../kube-config.helper'; import { KubeConfigFileCluster } from '../kube-config.types'; import { KubeConfigTableCertComponent } from './kube-config-table-cert/kube-config-table-cert.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.ts similarity index 92% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.ts index c7deb2270d..31f71b63fc 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config-selection/kube-config-table-cert/kube-config-table-cert.component.ts @@ -3,7 +3,7 @@ import { Component, Input } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; import { timeout } from 'rxjs/operators'; -import { TableCellCustom } from '../../../../../../../core/src/shared/components/list/list.types'; +import { TableCellCustom } from '../../../../../../core/src/shared/components/list/list.types'; import { KubeConfigHelper } from '../../kube-config.helper'; import { KubeConfigFileCluster } from '../../kube-config.types'; @@ -12,7 +12,7 @@ type CertResponse = { Required: boolean; Error: boolean; Message: string; -} +}; @Component({ selector: 'app-kube-config-table-cert', @@ -22,8 +22,8 @@ type CertResponse = { export class KubeConfigTableCertComponent extends TableCellCustom { initialValue = new BehaviorSubject<{ - checked: boolean - }>(null) + checked: boolean; + }>(null); initialValue$ = this.initialValue.asObservable(); private pRow: KubeConfigFileCluster; @@ -45,7 +45,7 @@ export class KubeConfigTableCertComponent extends TableCellCustom this.update(res.Required), // Failed, check for specific cert required error (e: HttpErrorResponse) => this.update(false) - ) + ); } } } @@ -57,7 +57,7 @@ export class KubeConfigTableCertComponent extends TableCellCustom(null) + clusters = new BehaviorSubject(null); clusters$ = this.clusters.asObservable().pipe( filter(clusters => !!clusters) ); @@ -36,18 +36,18 @@ export class KubeConfigHelper { public clustersChanged: () => void; public update = (cluster: KubeConfigFileCluster) => { this.checkValidity(cluster).subscribe(() => this.clustersChanged()); - } + }; public updateAll(): Observable { return this.clusters$.pipe( tap(clusters => clusters.forEach(cluster => this.update(cluster))), - ) + ); } public parse(config: string): Observable { let doc: KubeConfigFile; - const clusters: { [name: string]: KubeConfigFileCluster } = {}; + const clusters: { [name: string]: KubeConfigFileCluster, } = {}; try { doc = yaml.safeLoad(config); @@ -160,7 +160,7 @@ export class KubeConfigHelper { const newState = this.authHelper.parseAuth(cluster, user); if (!!newState && !!newState.message) { reset = false; - cluster._invalid = newState.error || newState.warning + cluster._invalid = newState.error || newState.warning; cluster._state.next(newState); } } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config.types.ts b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config.types.ts similarity index 85% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config.types.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config.types.ts index 0a34ee0611..bf38e74e03 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-config-registration/kube-config.types.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kube-config-registration/kube-config.types.ts @@ -2,10 +2,10 @@ import { Observable, Subject } from 'rxjs'; import { IActionMonitorComponentState, -} from '../../../../../core/src/shared/components/app-action-monitor-icon/app-action-monitor-icon.component'; -import { RowState } from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; -import { EndpointAuthTypeConfig } from '../../../../../store/src/extension-types'; -import { ActionStatus } from './../../../../../store/src/reducers/api-request-reducer/types'; +} from '../../../../core/src/shared/components/app-action-monitor-icon/app-action-monitor-icon.component'; +import { RowState } from '../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; +import { EndpointAuthTypeConfig } from '../../../../store/src/extension-types'; +import { ActionStatus } from './../../../../store/src/reducers/api-request-reducer/types'; // Types for a Kubernetes Configuration file diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-terminal/kube-console.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kube-terminal/kube-console.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-terminal/kube-console.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kube-terminal/kube-console.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-terminal/kube-console.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kube-terminal/kube-console.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-terminal/kube-console.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kube-terminal/kube-console.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-terminal/kube-console.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/kube-terminal/kube-console.component.spec.ts similarity index 65% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-terminal/kube-console.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kube-terminal/kube-console.component.spec.ts index 75932802f6..a4dd9b2411 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-terminal/kube-console.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kube-terminal/kube-console.component.spec.ts @@ -2,12 +2,11 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { createBasicStoreModule } from '@stratosui/store/testing'; -import { ApplicationService } from '../../../../../cloud-foundry/src/features/applications/application.service'; -import { ApplicationServiceMock } from '../../../../../cloud-foundry/test-framework/application-service-helper'; -import { CurrentUserPermissionsService } from '../../../../../core/src/core/permissions/current-user-permissions.service'; -import { SharedModule } from '../../../../../core/src/public-api'; -import { TabNavService } from '../../../../../core/src/tab-nav.service'; -import { CoreModule } from './../../../../../core/src/core/core.module'; +import { ApplicationService } from '../../../../cloud-foundry/src/features/applications/application.service'; +import { ApplicationServiceMock } from '../../../../cloud-foundry/test-framework/application-service-helper'; +import { CurrentUserPermissionsService } from '../../../../core/src/core/permissions/current-user-permissions.service'; +import { CoreModule, SharedModule } from '../../../../core/src/public-api'; +import { TabNavService } from '../../../../core/src/tab-nav.service'; import { KubeConsoleComponent } from './kube-console.component'; describe('KubeConsoleComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-terminal/kube-console.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kube-terminal/kube-console.component.ts similarity index 92% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-terminal/kube-console.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kube-terminal/kube-console.component.ts index cb14bef481..514546b5a3 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kube-terminal/kube-console.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kube-terminal/kube-console.component.ts @@ -4,8 +4,8 @@ import { NEVER, Observable, Subject } from 'rxjs'; import websocketConnect, { normalClosureMessage } from 'rxjs-websockets'; import { catchError, map, switchMap, tap } from 'rxjs/operators'; -import { IHeaderBreadcrumb } from '../../../../../core/src/shared/components/page-header/page-header.types'; -import { SshViewerComponent } from '../../../../../core/src/shared/components/ssh-viewer/ssh-viewer.component'; +import { IHeaderBreadcrumb } from '../../../../core/src/shared/components/page-header/page-header.types'; +import { SshViewerComponent } from '../../../../core/src/shared/components/ssh-viewer/ssh-viewer.component'; import { BaseKubeGuid } from '../kubernetes-page.types'; import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; import { KubernetesService } from '../services/kubernetes.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.spec.ts similarity index 94% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.spec.ts index 0779af7d2c..caff198d41 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.spec.ts @@ -2,7 +2,7 @@ import { HttpClient, HttpHandler } from '@angular/common/http'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { TabNavService } from '../../../../../../core/src/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { KubedashConfigurationComponent } from './kubedash-configuration.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.ts similarity index 95% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.ts index 5a0d0a1a0a..8510847ea2 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-dashboard/kubedash-configuration/kubedash-configuration.component.ts @@ -5,9 +5,9 @@ import { ActivatedRoute } from '@angular/router'; import { BehaviorSubject, Observable, Subscription } from 'rxjs'; import { distinctUntilChanged, filter, map } from 'rxjs/operators'; -import { ConfirmationDialogConfig } from '../../../../../../core/src/shared/components/confirmation-dialog.config'; -import { ConfirmationDialogService } from '../../../../../../core/src/shared/components/confirmation-dialog.service'; -import { IHeaderBreadcrumb } from '../../../../../../core/src/shared/components/page-header/page-header.types'; +import { ConfirmationDialogConfig } from '../../../../../core/src/shared/components/confirmation-dialog.config'; +import { ConfirmationDialogService } from '../../../../../core/src/shared/components/confirmation-dialog.service'; +import { IHeaderBreadcrumb } from '../../../../../core/src/shared/components/page-header/page-header.types'; import { BaseKubeGuid } from '../../kubernetes-page.types'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; import { KubernetesService } from '../../services/kubernetes.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.spec.ts similarity index 94% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.spec.ts index 92ee1c9d9d..25fc305d97 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { TabNavService } from '../../../../../core/src/tab-nav.service'; +import { TabNavService } from '../../../../core/src/tab-nav.service'; import { KubernetesBaseTestModules } from '../kubernetes.testing.module'; import { KubernetesDashboardTabComponent } from './kubernetes-dashboard.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.ts similarity index 96% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.ts index f54541c1b2..92047d7909 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-dashboard/kubernetes-dashboard.component.ts @@ -6,8 +6,8 @@ import { map } from 'rxjs/operators'; import { EndpointMissingMessageParts, -} from '../../../../../core/src/shared/components/endpoints-missing/endpoints-missing.component'; -import { IHeaderBreadcrumb } from '../../../../../core/src/shared/components/page-header/page-header.types'; +} from '../../../../core/src/shared/components/endpoints-missing/endpoints-missing.component'; +import { IHeaderBreadcrumb } from '../../../../core/src/shared/components/page-header/page-header.types'; import { BaseKubeGuid } from '../kubernetes-page.types'; import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; import { KubernetesService } from '../services/kubernetes.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-catalog.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-entity-catalog.ts similarity index 89% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-catalog.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-entity-catalog.ts index e41dfd779d..f90866eebe 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-catalog.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-entity-catalog.ts @@ -1,8 +1,8 @@ import { StratosCatalogEndpointEntity, StratosCatalogEntity, -} from '../../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; -import { IFavoriteMetadata } from '../../../../store/src/types/user-favorites.types'; +} from '../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; +import { IFavoriteMetadata } from '../../../store/src/types/user-favorites.types'; import { AnalysisReportsActionBuilders, KubeDashboardActionBuilders, @@ -34,7 +34,7 @@ export class KubeEntityCatalog { public deployment: StratosCatalogEntity; public node: StratosCatalogEntity; public namespace: StratosCatalogEntity; - public service: StratosCatalogEntity + public service: StratosCatalogEntity; public dashboard: StratosCatalogEntity; public analysisReport: StratosCatalogEntity; } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-factory.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-entity-factory.ts similarity index 92% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-factory.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-entity-factory.ts index 02637d2f5a..3b82c7d712 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-factory.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-entity-factory.ts @@ -1,8 +1,8 @@ import { Schema, schema } from 'normalizr'; -import { getAPIResourceGuid } from '../../../../cloud-foundry/src/store/selectors/api.selectors'; -import { EntitySchema } from '../../../../store/src/helpers/entity-schema'; -import { metricEntityType } from '../../../../store/src/helpers/stratos-entity-factory'; +import { getAPIResourceGuid } from '../../../cloud-foundry/src/store/selectors/api.selectors'; +import { EntitySchema } from '../../../store/src/helpers/entity-schema'; +import { metricEntityType } from '../../../store/src/helpers/stratos-entity-factory'; import { getGuidFromKubeDashboardObj, getGuidFromKubeDeploymentObj, @@ -29,7 +29,7 @@ export const getKubeAppId = (object: KubernetesApp) => object.name; export const KUBERNETES_ENDPOINT_TYPE = 'k8s'; const entityCache: { - [key: string]: EntitySchema + [key: string]: EntitySchema; } = {}; export class KubernetesEntitySchema extends EntitySchema { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-generator.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-entity-generator.ts similarity index 95% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-generator.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-entity-generator.ts index 0e526b8aeb..7b7d77a960 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-entity-generator.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-entity-generator.ts @@ -1,18 +1,18 @@ import { Validators } from '@angular/forms'; -import { BaseEndpointAuth } from '../../../../core/src/core/endpoint-auth'; +import { BaseEndpointAuth } from '../../../core/src/core/endpoint-auth'; import { StratosBaseCatalogEntity, StratosCatalogEndpointEntity, StratosCatalogEntity, -} from '../../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; +} from '../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; import { IStratosEntityDefinition, StratosEndpointExtensionDefinition, -} from '../../../../store/src/entity-catalog/entity-catalog.types'; -import { EndpointAuthTypeConfig, EndpointType } from '../../../../store/src/extension-types'; -import { metricEntityType } from '../../../../store/src/helpers/stratos-entity-factory'; -import { IFavoriteMetadata } from '../../../../store/src/types/user-favorites.types'; +} from '../../../store/src/entity-catalog/entity-catalog.types'; +import { EndpointAuthTypeConfig, EndpointType } from '../../../store/src/extension-types'; +import { metricEntityType } from '../../../store/src/helpers/stratos-entity-factory'; +import { IFavoriteMetadata } from '../../../store/src/types/user-favorites.types'; import { KubernetesAWSAuthFormComponent } from './auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component'; import { KubernetesCertsAuthFormComponent, diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-metrics.helpers.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-metrics.helpers.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-metrics.helpers.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-metrics.helpers.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.spec.ts similarity index 92% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.spec.ts index c247e9f24e..2e150486ae 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from 'frontend/packages/core/src/tab-nav.service'; -import { MDAppModule } from '../../../../../../core/src/public-api'; +import { MDAppModule } from '../../../../../core/src/public-api'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; import { KubernetesNamespaceService } from '../../services/kubernetes-namespace.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-analysis-report/kubernetes-namespace-analysis-report.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-pods/kubernetes-namespace-pods.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-pods/kubernetes-namespace-pods.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-pods/kubernetes-namespace-pods.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-pods/kubernetes-namespace-pods.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-pods/kubernetes-namespace-pods.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-pods/kubernetes-namespace-pods.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-pods/kubernetes-namespace-pods.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-pods/kubernetes-namespace-pods.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-pods/kubernetes-namespace-pods.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-pods/kubernetes-namespace-pods.component.ts similarity index 84% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-pods/kubernetes-namespace-pods.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-pods/kubernetes-namespace-pods.component.ts index 812ad3aaa1..a9127d468f 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-pods/kubernetes-namespace-pods.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-pods/kubernetes-namespace-pods.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; -import { ListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; +import { ListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; import { KubernetesNamespacePodsListConfigService, } from '../../list-types/kubernetes-namespace-pods/kubernetes-namespace-pods-list-config.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.spec.ts similarity index 94% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.spec.ts index 083db88ddf..68f5dd3509 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../../core/src/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { BaseKubeGuid } from '../../kubernetes-page.types'; import { KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.ts similarity index 84% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.ts index f8a81aa887..02a76e577e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace-services/kubernetes-namespace-services.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; -import { ListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; +import { ListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; import { KubernetesNamespaceServicesListConfig, } from '../../list-types/kubernetes-namespace-services/kubernetes-namespace-services-list-config.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace.component.ts similarity index 94% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace.component.ts index 45ab5eb4a9..0a7eab7a1e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-namespace/kubernetes-namespace.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-namespace/kubernetes-namespace.component.ts @@ -3,12 +3,12 @@ import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { IHeaderBreadcrumb } from '../../../../../core/src/shared/components/page-header/page-header.types'; +import { IHeaderBreadcrumb } from '../../../../core/src/shared/components/page-header/page-header.types'; import { BaseKubeGuid } from '../kubernetes-page.types'; import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; import { KubernetesNamespaceService } from '../services/kubernetes-namespace.service'; -import { KubernetesService } from '../services/kubernetes.service'; import { KubernetesAnalysisService } from '../services/kubernetes.analysis.service'; +import { KubernetesService } from '../services/kubernetes.service'; @Component({ selector: 'app-kubernetes-namespace', diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.ts similarity index 74% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.ts index 9be744ec76..e9c226f588 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics-chart/kubernetes-node-metrics-chart.component.ts @@ -1,12 +1,10 @@ import { Component, Input, OnInit } from '@angular/core'; -import { MetricsConfig } from '../../../../../../../core/src/shared/components/metrics-chart/metrics-chart.component'; -import { MetricsLineChartConfig } from '../../../../../../../core/src/shared/components/metrics-chart/metrics-chart.types'; -import { - MetricsChartHelpers, -} from '../../../../../../../core/src/shared/components/metrics-chart/metrics.component.helpers'; -import { IMetricMatrixResult } from '../../../../../../../store/src/types/base-metric.types'; -import { IMetricApplication } from '../../../../../../../store/src/types/metric.types'; +import { MetricsConfig } from '../../../../../../core/src/shared/components/metrics-chart/metrics-chart.component'; +import { MetricsLineChartConfig } from '../../../../../../core/src/shared/components/metrics-chart/metrics-chart.types'; +import { MetricsChartHelpers } from '../../../../../../core/src/shared/components/metrics-chart/metrics.component.helpers'; +import { IMetricMatrixResult } from '../../../../../../store/src/types/base-metric.types'; +import { IMetricApplication } from '../../../../../../store/src/types/metric.types'; import { FetchKubernetesMetricsAction } from '../../../store/kubernetes.actions'; @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.spec.ts similarity index 71% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.spec.ts index 5a5624b412..8cf61f7366 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.spec.ts @@ -1,12 +1,16 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { KubernetesNodeMetricsComponent } from './kubernetes-node-metrics.component'; -import { KubernetesNodeMetricStatsCardComponent } from './kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component'; -import { KubernetesNodeSimpleMetricComponent } from './kubernetes-node-simple-metric/kubernetes-node-simple-metric.component'; -import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; import { BaseKubeGuid } from '../../kubernetes-page.types'; -import { KubernetesNodeService } from '../../services/kubernetes-node.service'; import { KubernetesBaseTestModules } from '../../kubernetes.testing.module'; +import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; +import { KubernetesNodeService } from '../../services/kubernetes-node.service'; +import { + KubernetesNodeMetricStatsCardComponent, +} from './kubernetes-node-metric-stats-card/kubernetes-node-metric-stats-card.component'; +import { KubernetesNodeMetricsComponent } from './kubernetes-node-metrics.component'; +import { + KubernetesNodeSimpleMetricComponent, +} from './kubernetes-node-simple-metric/kubernetes-node-simple-metric.component'; describe('KubernetesNodeMetricsComponent', () => { let component: KubernetesNodeMetricsComponent; @@ -14,7 +18,11 @@ describe('KubernetesNodeMetricsComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [KubernetesNodeMetricsComponent, KubernetesNodeMetricStatsCardComponent, KubernetesNodeSimpleMetricComponent], + declarations: [ + KubernetesNodeMetricsComponent, + KubernetesNodeMetricStatsCardComponent, + KubernetesNodeSimpleMetricComponent + ], imports: KubernetesBaseTestModules, providers: [BaseKubeGuid, KubernetesEndpointService, KubernetesNodeService] }) diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.ts similarity index 85% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.ts index f91208e9c1..7e2cd38a56 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-metrics.component.ts @@ -1,13 +1,13 @@ import { Component, OnInit } from '@angular/core'; -import { MetricsConfig } from '../../../../../../core/src/shared/components/metrics-chart/metrics-chart.component'; -import { MetricsLineChartConfig } from '../../../../../../core/src/shared/components/metrics-chart/metrics-chart.types'; +import { MetricsConfig } from '../../../../../core/src/shared/components/metrics-chart/metrics-chart.component'; +import { MetricsLineChartConfig } from '../../../../../core/src/shared/components/metrics-chart/metrics-chart.types'; import { ChartDataTypes, getMetricsChartConfigBuilder, -} from '../../../../../../core/src/shared/components/metrics-chart/metrics.component.helpers'; -import { ChartSeries, IMetricMatrixResult } from '../../../../../../store/src/types/base-metric.types'; -import { IMetricApplication } from '../../../../../../store/src/types/metric.types'; +} from '../../../../../core/src/shared/components/metrics-chart/metrics.component.helpers'; +import { ChartSeries, IMetricMatrixResult } from '../../../../../store/src/types/base-metric.types'; +import { IMetricApplication } from '../../../../../store/src/types/metric.types'; import { formatAxisCPUTime, formatCPUTime } from '../../kubernetes-metrics.helpers'; import { KubeNodeMetric, KubernetesNodeService } from '../../services/kubernetes-node.service'; import { FetchKubernetesChartMetricsAction } from '../../store/kubernetes.actions'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-simple-metric/kubernetes-node-simple-metric.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-simple-metric/kubernetes-node-simple-metric.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-simple-metric/kubernetes-node-simple-metric.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-simple-metric/kubernetes-node-simple-metric.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-simple-metric/kubernetes-node-simple-metric.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-simple-metric/kubernetes-node-simple-metric.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-simple-metric/kubernetes-node-simple-metric.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-simple-metric/kubernetes-node-simple-metric.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-simple-metric/kubernetes-node-simple-metric.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-simple-metric/kubernetes-node-simple-metric.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-simple-metric/kubernetes-node-simple-metric.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-simple-metric/kubernetes-node-simple-metric.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-simple-metric/kubernetes-node-simple-metric.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-simple-metric/kubernetes-node-simple-metric.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-simple-metric/kubernetes-node-simple-metric.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-metrics/kubernetes-node-simple-metric/kubernetes-node-simple-metric.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.ts similarity index 82% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.ts index 92d3de996c..5cd5f67945 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node-pods/kubernetes-node-pods.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; -import { ListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; +import { ListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; import { KubernetesNodePodsListConfigService, } from '../../list-types/kubernetes-node-pods/kubernetes-node-pods-list-config.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node.component.spec.ts similarity index 94% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node.component.spec.ts index b06b4eee40..b0530a0935 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { TabNavService } from '../../../../../core/src/tab-nav.service'; +import { TabNavService } from '../../../../core/src/tab-nav.service'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../kubernetes.testing.module'; import { KubernetesNodeComponent } from './kubernetes-node.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node.component.ts similarity index 91% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node.component.ts index 14b6bb6e3e..17b057f286 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-node/kubernetes-node.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-node/kubernetes-node.component.ts @@ -3,8 +3,8 @@ import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs'; import { first, map, tap } from 'rxjs/operators'; -import { EndpointsService } from '../../../../../core/src/core/endpoints.service'; -import { IHeaderBreadcrumb } from '../../../../../core/src/shared/components/page-header/page-header.types'; +import { EndpointsService } from '../../../../core/src/core/endpoints.service'; +import { IHeaderBreadcrumb } from '../../../../core/src/shared/components/page-header/page-header.types'; import { BaseKubeGuid } from '../kubernetes-page.types'; import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; import { KubernetesNodeService } from '../services/kubernetes-node.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-page.types.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-page.types.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-page.types.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-page.types.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.spec.ts similarity index 94% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.spec.ts index 38b577beaa..c7d6ae77a4 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { SidePanelService } from '../../../../../core/src/shared/services/side-panel.service'; +import { SidePanelService } from '../../../../core/src/shared/services/side-panel.service'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../kubernetes.testing.module'; import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; import { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.ts similarity index 96% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.ts index 8944cb03ef..3f868ec9ef 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-resource-viewer/kubernetes-resource-viewer.component.ts @@ -3,8 +3,8 @@ import moment from 'moment'; import { Observable, of } from 'rxjs'; import { filter, first, map, publishReplay, refCount, switchMap } from 'rxjs/operators'; -import { EndpointsService } from '../../../../../core/src/core/endpoints.service'; -import { PreviewableComponent } from '../../../../../core/src/shared/previewable-component'; +import { EndpointsService } from '../../../../core/src/core/endpoints.service'; +import { PreviewableComponent } from '../../../../core/src/shared/previewable-component'; import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; import { BasicKubeAPIResource, KubeAPIResource, KubeStatus } from '../store/kube.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.spec.ts similarity index 94% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.spec.ts index d714ab66a2..27e21b1be5 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { TabNavService } from '../../../../../core/src/tab-nav.service'; +import { TabNavService } from '../../../../core/src/tab-nav.service'; import { KubernetesBaseTestModules } from '../kubernetes.testing.module'; import { KubernetesTabBaseComponent } from './kubernetes-tab-base.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.ts index fcd9c0ba5f..c250a2de07 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes-tab-base/kubernetes-tab-base.component.ts @@ -3,8 +3,8 @@ import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs'; import { first, map, startWith } from 'rxjs/operators'; -import { FavoritesConfigMapper } from '../../../../../store/src/favorite-config-mapper'; -import { UserFavoriteEndpoint } from '../../../../../store/src/types/user-favorites.types'; +import { FavoritesConfigMapper } from '../../../../store/src/favorite-config-mapper'; +import { UserFavoriteEndpoint } from '../../../../store/src/types/user-favorites.types'; import { BaseKubeGuid } from '../kubernetes-page.types'; import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; import { KubernetesAnalysisService } from '../services/kubernetes.analysis.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.module.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes.module.ts similarity index 98% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.module.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes.module.ts index 9d5c1db436..2ee5760928 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.module.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes.module.ts @@ -2,8 +2,8 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { NgxChartsModule } from '@swimlane/ngx-charts'; -import { CoreModule } from '../../../../core/src/core/core.module'; -import { SharedModule } from '../../../../core/src/shared/shared.module'; +import { CoreModule } from '../../../core/src/core/core.module'; +import { SharedModule } from '../../../core/src/shared/shared.module'; import { AnalysisReportRunnerComponent, } from './analysis-report-viewer/analysis-report-runner/analysis-report-runner.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.routing.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes.routing.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.routing.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes.routing.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.setup.module.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes.setup.module.ts similarity index 90% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.setup.module.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes.setup.module.ts index f064cb04e2..c7afda8eee 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.setup.module.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes.setup.module.ts @@ -1,11 +1,11 @@ import { CommonModule } from '@angular/common'; import { NgModule, Optional, SkipSelf } from '@angular/core'; -import { CoreModule } from '../../../../core/src/core/core.module'; -import { EndpointsService } from '../../../../core/src/core/endpoints.service'; -import { SharedModule } from '../../../../core/src/shared/shared.module'; -import { EntityCatalogModule } from '../../../../store/src/entity-catalog.module'; -import { EndpointHealthCheck } from '../../../../store/src/entity-catalog/entity-catalog.types'; +import { CoreModule } from '../../../core/src/core/core.module'; +import { EndpointsService } from '../../../core/src/core/endpoints.service'; +import { SharedModule } from '../../../core/src/shared/shared.module'; +import { EntityCatalogModule } from '../../../store/src/entity-catalog.module'; +import { EndpointHealthCheck } from '../../../store/src/entity-catalog/entity-catalog.types'; import { KubernetesAWSAuthFormComponent } from './auth-forms/kubernetes-aws-auth-form/kubernetes-aws-auth-form.component'; import { KubernetesCertsAuthFormComponent, diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.store.module.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes.store.module.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.store.module.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes.store.module.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.testing.module.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes.testing.module.ts similarity index 68% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.testing.module.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes.testing.module.ts index af0520b267..bffc35ba08 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes.testing.module.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes.testing.module.ts @@ -3,14 +3,17 @@ import { NgModule } from '@angular/core'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { CoreModule } from '../../../../core/src/core/core.module'; -import { SharedModule } from '../../../../core/src/shared/shared.module'; -import { TabNavService } from '../../../../core/src/tab-nav.service'; -import { AppTestModule } from '../../../../core/test-framework/core-test.helper'; -import { CATALOGUE_ENTITIES, EntityCatalogFeatureModule } from '../../../../store/src/entity-catalog.module'; -import { entityCatalog, TestEntityCatalog } from '../../../../store/src/entity-catalog/entity-catalog'; -import { generateStratosEntities } from '../../../../store/src/stratos-entity-generator'; -import { createBasicStoreModule } from '../../../../store/testing/public-api'; +import { CoreModule, SharedModule } from '../../../core/src/public-api'; +import { TabNavService } from '../../../core/src/tab-nav.service'; +import { AppTestModule } from '../../../core/test-framework/core-test.helper'; +import { + CATALOGUE_ENTITIES, + entityCatalog, + EntityCatalogFeatureModule, + TestEntityCatalog, +} from '../../../store/src/public-api'; +import { generateStratosEntities } from '../../../store/src/stratos-entity-generator'; +import { createBasicStoreModule } from '../../../store/testing/public-api'; import { HelmReleaseActivatedRouteMock, HelmReleaseGuidMock } from '../helm/helm-testing.module'; import { generateKubernetesEntities } from './kubernetes-entity-generator'; import { BaseKubeGuid } from './kubernetes-page.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes/kubernetes.component.html b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes/kubernetes.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes/kubernetes.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes/kubernetes.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes/kubernetes.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes/kubernetes.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes/kubernetes.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes/kubernetes.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes/kubernetes.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes/kubernetes.component.spec.ts similarity index 91% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes/kubernetes.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes/kubernetes.component.spec.ts index d222961131..89e6bc2ced 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes/kubernetes.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes/kubernetes.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../core/src/tab-nav.service'; +import { TabNavService } from '../../../../core/src/tab-nav.service'; import { KubernetesBaseTestModules } from '../kubernetes.testing.module'; import { KubernetesComponent } from './kubernetes.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes/kubernetes.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes/kubernetes.component.ts similarity index 79% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes/kubernetes.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/kubernetes/kubernetes.component.ts index 9d0c2975f4..b94175816b 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/kubernetes/kubernetes.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/kubernetes/kubernetes.component.ts @@ -3,12 +3,10 @@ import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { filter, first, map } from 'rxjs/operators'; -import { - EndpointListHelper, -} from '../../../../../core/src/shared/components/list/list-types/endpoint/endpoint-list.helpers'; -import { ListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; -import { RouterNav } from '../../../../../store/src/actions/router.actions'; -import { AppState } from '../../../../../store/src/app-state'; +import { EndpointListHelper } from '../../../../core/src/shared/components/list/list-types/endpoint/endpoint-list.helpers'; +import { ListConfig } from '../../../../core/src/shared/components/list/list.component.types'; +import { RouterNav } from '../../../../store/src/actions/router.actions'; +import { AppState } from '../../../../store/src/public-api'; import { KubernetesEndpointsListConfigService, } from '../list-types/kubernetes-endpoints/kubernetes-endpoints-list-config.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-config.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/analysis-reports-list-config.service.ts similarity index 96% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-config.service.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/analysis-reports-list-config.service.ts index 8755eda8b2..1288c57637 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-config.service.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/analysis-reports-list-config.service.ts @@ -10,8 +10,8 @@ import { import moment from 'moment'; import { of } from 'rxjs'; -import { ListView } from '../../../../../store/src/actions/list.actions'; -import { AppState } from '../../../../../store/src/app-state'; +import { ListView } from '../../../../store/src/actions/list.actions'; +import { AppState } from '../../../../store/src/public-api'; import { defaultHelmKubeListPageSize } from '../../kubernetes/list-types/kube-helm-list-types'; import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; import { KubernetesAnalysisService } from '../services/kubernetes.analysis.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-source.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/analysis-reports-list-source.ts similarity index 92% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-source.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/analysis-reports-list-source.ts index d2f9330a3f..0b1cccec2f 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-reports-list-source.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/analysis-reports-list-source.ts @@ -6,8 +6,8 @@ import { IListConfig } from 'frontend/packages/core/src/shared/components/list/l import { interval, Subscription } from 'rxjs'; import { first, map } from 'rxjs/operators'; -import { AppState } from '../../../../../store/src/app-state'; -import { isFetchingPage } from '../../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper'; +import { AppState } from '../../../../store/src/public-api'; +import { isFetchingPage } from '../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper'; import { kubeEntityCatalog } from '../kubernetes-entity-catalog'; import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; import { GetAnalysisReports } from '../store/analysis.actions'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.spec.ts similarity index 82% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.spec.ts index aca29a6d84..d7c7f58503 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { MDAppModule } from '../../../../../../core/src/public-api'; +import { MDAppModule } from '../../../../../core/src/public-api'; import { AnalysisReportSelectorComponent, } from './../../analysis-report-viewer/analysis-report-selector/analysis-report-selector.component'; @@ -12,12 +12,12 @@ describe('AnalysisStatusCellComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ AnalysisStatusCellComponent, AnalysisReportSelectorComponent ], + declarations: [AnalysisStatusCellComponent, AnalysisReportSelectorComponent], imports: [ MDAppModule, ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/analysis-status-cell/analysis-status-cell.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kube-helm-list-types.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kube-helm-list-types.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kube-helm-list-types.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kube-helm-list-types.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kube-list.helper.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kube-list.helper.ts similarity index 88% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kube-list.helper.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kube-list.helper.ts index 1509c97e9c..b2ac0ae8bb 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kube-list.helper.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kube-list.helper.ts @@ -1,7 +1,7 @@ import moment from 'moment'; -import { DataFunction } from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; -import { ITableColumn } from '../../../../../core/src/shared/components/list/list-table/table.types'; +import { DataFunction } from '../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; +import { ITableColumn } from '../../../../core/src/shared/components/list/list-table/table.types'; import { BasicKubeAPIResource, ConditionType, KubernetesNode } from '../store/kube.types'; export function getConditionSort(condition: ConditionType): DataFunction { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-data-source.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-data-source.ts similarity index 52% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-data-source.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-data-source.ts index 3eaaf1f26e..c91acd3774 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-data-source.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-data-source.ts @@ -3,14 +3,13 @@ import { Store } from '@ngrx/store'; import { BaseEndpointsDataSource, syncPaginationSection, -} from '../../../../../../core/src/shared/components/list/list-types/endpoint/base-endpoints-data-source'; -import { IListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; -import { GetAllEndpoints } from '../../../../../../store/src/actions/endpoint.actions'; -import { AppState } from '../../../../../../store/src/app-state'; -import { EntityMonitorFactory } from '../../../../../../store/src/monitors/entity-monitor.factory.service'; -import { InternalEventMonitorFactory } from '../../../../../../store/src/monitors/internal-event-monitor.factory'; -import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; -import { EndpointModel } from '../../../../../../store/src/types/endpoint.types'; +} from '../../../../../core/src/shared/components/list/list-types/endpoint/base-endpoints-data-source'; +import { IListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; +import { GetAllEndpoints } from '../../../../../store/src/actions/endpoint.actions'; +import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; +import { InternalEventMonitorFactory } from '../../../../../store/src/monitors/internal-event-monitor.factory'; +import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; +import { AppState, EndpointModel } from '../../../../../store/src/public-api'; export class KubernetesEndpointsDataSource extends BaseEndpointsDataSource { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-list-config.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-list-config.service.ts similarity index 59% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-list-config.service.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-list-config.service.ts index 2c384fefa9..f4efc09eca 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-list-config.service.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-endpoints/kubernetes-endpoints-list-config.service.ts @@ -1,22 +1,21 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import { ITableColumn } from '../../../../../../core/src/shared/components/list/list-table/table.types'; +import { ITableColumn } from '../../../../../core/src/shared/components/list/list-table/table.types'; import { BaseEndpointsDataSource, -} from '../../../../../../core/src/shared/components/list/list-types/endpoint/base-endpoints-data-source'; +} from '../../../../../core/src/shared/components/list/list-types/endpoint/base-endpoints-data-source'; import { EndpointCardComponent, -} from '../../../../../../core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component'; +} from '../../../../../core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component'; import { EndpointsListConfigService, -} from '../../../../../../core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service'; -import { IListConfig, ListViewTypes } from '../../../../../../core/src/shared/components/list/list.component.types'; -import { AppState } from '../../../../../../store/src/app-state'; -import { EntityMonitorFactory } from '../../../../../../store/src/monitors/entity-monitor.factory.service'; -import { InternalEventMonitorFactory } from '../../../../../../store/src/monitors/internal-event-monitor.factory'; -import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; -import { EndpointModel } from '../../../../../../store/src/types/endpoint.types'; +} from '../../../../../core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service'; +import { IListConfig, ListViewTypes } from '../../../../../core/src/shared/components/list/list.component.types'; +import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; +import { InternalEventMonitorFactory } from '../../../../../store/src/monitors/internal-event-monitor.factory'; +import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; +import { AppState, EndpointModel } from '../../../../../store/src/public-api'; import { KubernetesEndpointsDataSource } from './kubernetes-endpoints-data-source'; @Injectable() diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.spec.ts similarity index 92% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.spec.ts index 68f0f28e01..208f7e2be3 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.spec.ts @@ -1,8 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseTestModules } from '../../../../../../core/test-framework/core-test.helper'; -import { KubernetesLabelsCellComponent } from './kubernetes-labels-cell.component'; +import { BaseTestModules } from '../../../../../core/test-framework/core-test.helper'; import { KubernetesStatus } from '../../store/kube.types'; +import { KubernetesLabelsCellComponent } from './kubernetes-labels-cell.component'; describe('KubernetesLabelsCellComponent', () => { let component: KubernetesLabelsCellComponent; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.ts similarity index 76% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.ts index a22c8018e7..20a8b2e5b0 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-labels-cell/kubernetes-labels-cell.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; -import { AppChip } from '../../../../../../core/src/shared/components/chips/chips.component'; -import { TableCellCustom } from '../../../../../../core/src/shared/components/list/list.types'; +import { AppChip } from '../../../../../core/src/shared/components/chips/chips.component'; +import { TableCellCustom } from '../../../../../core/src/shared/components/list/list.types'; import { KubeAPIResource } from '../../store/kube.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespace-pods/kubernetes-namespace-pods-data-source.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespace-pods/kubernetes-namespace-pods-data-source.ts similarity index 79% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespace-pods/kubernetes-namespace-pods-data-source.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespace-pods/kubernetes-namespace-pods-data-source.ts index 17d13b91a9..8ed8c1128c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespace-pods/kubernetes-namespace-pods-data-source.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespace-pods/kubernetes-namespace-pods-data-source.ts @@ -1,8 +1,8 @@ import { Store } from '@ngrx/store'; -import { ListDataSource } from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; -import { IListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; -import { AppState } from '../../../../../../store/src/app-state'; +import { ListDataSource } from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; +import { IListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; +import { AppState } from '../../../../../store/src/public-api'; import { kubeEntityCatalog } from '../../kubernetes-entity-catalog'; import { kubernetesEntityFactory, kubernetesPodsEntityType } from '../../kubernetes-entity-factory'; import { BaseKubeGuid } from '../../kubernetes-page.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespace-pods/kubernetes-namespace-pods-list-config.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespace-pods/kubernetes-namespace-pods-list-config.service.ts similarity index 94% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespace-pods/kubernetes-namespace-pods-list-config.service.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespace-pods/kubernetes-namespace-pods-list-config.service.ts index 98830df419..c5e48a4935 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespace-pods/kubernetes-namespace-pods-list-config.service.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespace-pods/kubernetes-namespace-pods-list-config.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import { AppState } from '../../../../../../store/src/app-state'; +import { AppState } from '../../../../../store/src/public-api'; import { BaseKubeGuid } from '../../kubernetes-page.types'; import { KubernetesNamespaceService } from '../../services/kubernetes-namespace.service'; import { BaseKubernetesPodsListConfigService } from '../kubernetes-pods/kubernetes-pods-list-config.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespace-services/kubernetes-namespace-services-data-source.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespace-services/kubernetes-namespace-services-data-source.ts similarity index 80% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespace-services/kubernetes-namespace-services-data-source.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespace-services/kubernetes-namespace-services-data-source.ts index 7ac8f28263..bf533daf17 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespace-services/kubernetes-namespace-services-data-source.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespace-services/kubernetes-namespace-services-data-source.ts @@ -1,7 +1,7 @@ import { Store } from '@ngrx/store'; -import { IListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; -import { AppState } from '../../../../../../store/src/app-state'; +import { IListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; +import { AppState } from '../../../../../store/src/public-api'; import { kubeEntityCatalog } from '../../kubernetes-entity-catalog'; import { BaseKubeGuid } from '../../kubernetes-page.types'; import { KubeService } from '../../store/kube.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespace-services/kubernetes-namespace-services-list-config.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespace-services/kubernetes-namespace-services-list-config.service.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespace-services/kubernetes-namespace-services-list-config.service.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespace-services/kubernetes-namespace-services-list-config.service.ts index d6bf8a0385..ebf6103361 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespace-services/kubernetes-namespace-services-list-config.service.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespace-services/kubernetes-namespace-services-list-config.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import { AppState } from '../../../../../../store/src/app-state'; +import { AppState } from '../../../../../store/src/public-api'; import { BaseKubeGuid } from '../../kubernetes-page.types'; import { KubernetesNamespaceService } from '../../services/kubernetes-namespace.service'; import { BaseKubernetesServicesListConfig } from '../kubernetes-services/kubernetes-service-list-config.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.ts similarity index 90% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.ts index 239363c136..d1e7679573 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kube-namespace-pod-count/kube-namespace-pod-count.component.ts @@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { TableCellCustom } from '../../../../../../../core/src/shared/components/list/list.types'; +import { TableCellCustom } from '../../../../../../core/src/shared/components/list/list.types'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; import { KubernetesNamespace } from '../../../store/kube.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.ts similarity index 87% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.ts index e39f0e95ce..58fde4613a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespace-link/kubernetes-namespace-link.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; -import { TableCellCustom } from '../../../../../../../core/src/shared/components/list/list.types'; +import { TableCellCustom } from '../../../../../../core/src/shared/components/list/list.types'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; import { KubernetesNamespace } from '../../../store/kube.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-data-source.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-data-source.ts similarity index 71% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-data-source.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-data-source.ts index 76555c46ee..c95efb1f23 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-data-source.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-data-source.ts @@ -1,9 +1,9 @@ import { Store } from '@ngrx/store'; -import { ListDataSource } from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; -import { IListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; -import { getPaginationKey } from '../../../../../../store/src/actions/pagination.actions'; -import { AppState } from '../../../../../../store/src/app-state'; +import { ListDataSource } from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; +import { IListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; +import { getPaginationKey } from '../../../../../store/src/actions/pagination.actions'; +import { AppState } from '../../../../../store/src/public-api'; import { kubeEntityCatalog } from '../../kubernetes-entity-catalog'; import { kubernetesNamespacesEntityType } from '../../kubernetes-entity-factory'; import { BaseKubeGuid } from '../../kubernetes-page.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-list-config.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-list-config.service.ts similarity index 91% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-list-config.service.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-list-config.service.ts index 58b66774a6..587fcf631d 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-list-config.service.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-namespaces/kubernetes-namespaces-list-config.service.ts @@ -3,9 +3,9 @@ import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { filter, first, map, tap } from 'rxjs/operators'; -import { ITableColumn } from '../../../../../../core/src/shared/components/list/list-table/table.types'; -import { IListConfig, ListViewTypes } from '../../../../../../core/src/shared/components/list/list.component.types'; -import { AppState } from '../../../../../../store/src/app-state'; +import { ITableColumn } from '../../../../../core/src/shared/components/list/list-table/table.types'; +import { IListConfig, ListViewTypes } from '../../../../../core/src/shared/components/list/list.component.types'; +import { AppState } from '../../../../../store/src/public-api'; import { BaseKubeGuid } from '../../kubernetes-page.types'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; import { KubernetesNamespace } from '../../store/kube.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-node-pods/kubernetes-node-pods-data-source.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-node-pods/kubernetes-node-pods-data-source.ts similarity index 76% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-node-pods/kubernetes-node-pods-data-source.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-node-pods/kubernetes-node-pods-data-source.ts index 746bf5e95a..6ff5893190 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-node-pods/kubernetes-node-pods-data-source.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-node-pods/kubernetes-node-pods-data-source.ts @@ -1,8 +1,8 @@ import { Store } from '@ngrx/store'; -import { ListDataSource } from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; -import { IListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; -import { AppState } from '../../../../../../store/src/app-state'; +import { ListDataSource } from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; +import { IListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; +import { AppState } from '../../../../../store/src/public-api'; import { kubeEntityCatalog } from '../../kubernetes-entity-catalog'; import { BaseKubeGuid } from '../../kubernetes-page.types'; import { KubernetesNodeService } from '../../services/kubernetes-node.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-node-pods/kubernetes-node-pods-list-config.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-node-pods/kubernetes-node-pods-list-config.service.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-node-pods/kubernetes-node-pods-list-config.service.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-node-pods/kubernetes-node-pods-list-config.service.ts index be0d52354d..4f93ff213b 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-node-pods/kubernetes-node-pods-list-config.service.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-node-pods/kubernetes-node-pods-list-config.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import { AppState } from '../../../../../../store/src/app-state'; +import { AppState } from '../../../../../store/src/public-api'; import { BaseKubeGuid } from '../../kubernetes-page.types'; import { KubernetesNodeService } from '../../services/kubernetes-node.service'; import { BaseKubernetesPodsListConfigService } from '../kubernetes-pods/kubernetes-pods-list-config.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.spec.ts similarity index 92% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.spec.ts index 382f3b21ba..dedb8f791a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseTestModules } from '../../../../../../../core/test-framework/core-test.helper'; +import { BaseTestModules } from '../../../../../../core/test-framework/core-test.helper'; import { ConditionCellComponent } from './condition-cell.component'; describe('ConditionCellComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.ts similarity index 89% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.ts index da889a853f..03b0b2e878 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/condition-cell/condition-cell.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; -import { TableCellCustom } from '../../../../../../../core/src/shared/components/list/list.types'; +import { TableCellCustom } from '../../../../../../core/src/shared/components/list/list.types'; import { KubernetesNode } from '../../../store/kube.types'; @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.spec.ts similarity index 92% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.spec.ts index e71a76120f..bc04b8d360 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseTestModules } from '../../../../../../../core/test-framework/core-test.helper'; +import { BaseTestModules } from '../../../../../../core/test-framework/core-test.helper'; import { KubernetesNodeCapacityComponent } from './kubernetes-node-capacity.component'; describe('KubernetesNodeCapacityComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.ts similarity index 85% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.ts index 8b1738b7d0..17491cde85 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-capacity/kubernetes-node-capacity.component.ts @@ -1,6 +1,6 @@ import { Component, ViewEncapsulation } from '@angular/core'; -import { TableCellCustom } from '../../../../../../../core/src/shared/components/list/list.types'; +import { TableCellCustom } from '../../../../../../core/src/shared/components/list/list.types'; @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.spec.ts similarity index 92% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.spec.ts index cbb540177f..942059a5b5 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseTestModules } from '../../../../../../../core/test-framework/core-test.helper'; +import { BaseTestModules } from '../../../../../../core/test-framework/core-test.helper'; import { KubernetesNodeIpsComponent } from './kubernetes-node-ips.component'; describe('KubernetesNodeIpsComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.ts similarity index 88% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.ts index d037fbce9e..feb188954f 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-ips/kubernetes-node-ips.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; -import { TableCellCustom } from '../../../../../../../core/src/shared/components/list/list.types'; +import { TableCellCustom } from '../../../../../../core/src/shared/components/list/list.types'; import { KubernetesAddressExternal, KubernetesAddressInternal, KubernetesNode } from '../../../store/kube.types'; @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.spec.ts similarity index 92% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.spec.ts index 2f7e112135..050be6121c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseTestModules } from '../../../../../../../core/test-framework/core-test.helper'; +import { BaseTestModules } from '../../../../../../core/test-framework/core-test.helper'; import { KubernetesNodeLabelsComponent } from './kubernetes-node-labels.component'; describe('KubernetesNodeLabelsComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.ts similarity index 85% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.ts index 07f0f35f7a..f15c018bec 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-labels/kubernetes-node-labels.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; -import { TableCellCustom } from '../../../../../../../core/src/shared/components/list/list.types'; +import { TableCellCustom } from '../../../../../../core/src/shared/components/list/list.types'; import { KubernetesNode } from '../../../store/kube.types'; @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.theme.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.theme.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.theme.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.theme.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.ts similarity index 91% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.ts index d5c05226f8..ccf197c570 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; -import { TableCellCustom } from '../../../../../../../core/src/shared/components/list/list.types'; +import { TableCellCustom } from '../../../../../../core/src/shared/components/list/list.types'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; import { KubernetesNode } from '../../../store/kube.types'; @@ -16,7 +16,7 @@ export class KubernetesNodeLinkComponent extends TableCellCustom public icon: { icon: string, class: string, - message: string + message: string, }; constructor( diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.spec.ts similarity index 92% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.spec.ts index 0a3579cd55..cb7eb3fa12 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { BaseTestModules } from '../../../../../../../core/test-framework/core-test.helper'; +import { BaseTestModules } from '../../../../../../core/test-framework/core-test.helper'; import { KubernetesNodePressureComponent } from './kubernetes-node-pressure.component'; describe('KubernetesNodePressureComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.theme.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.theme.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.theme.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.theme.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.ts similarity index 88% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.ts index 1352f25bc6..8f532f808f 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-pressure/kubernetes-node-pressure.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; -import { TableCellCustom } from '../../../../../../../core/src/shared/components/list/list.types'; +import { TableCellCustom } from '../../../../../../core/src/shared/components/list/list.types'; import { ConditionType, ConditionTypeLabels, KubernetesNode } from '../../../store/kube.types'; @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition-card.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-condition-card/kubernetes-node-condition/kubernetes-node-condition.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-info-card/kubernetes-node-info-card.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-info-card/kubernetes-node-info-card.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-info-card/kubernetes-node-info-card.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-info-card/kubernetes-node-info-card.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-info-card/kubernetes-node-info-card.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-info-card/kubernetes-node-info-card.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-info-card/kubernetes-node-info-card.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-info-card/kubernetes-node-info-card.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-info-card/kubernetes-node-info-card.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-info-card/kubernetes-node-info-card.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-info-card/kubernetes-node-info-card.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-info-card/kubernetes-node-info-card.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-info-card/kubernetes-node-info-card.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-info-card/kubernetes-node-info-card.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-info-card/kubernetes-node-info-card.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-info-card/kubernetes-node-info-card.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary-card/kubernetes-node-summary-card.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-summary.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.ts similarity index 90% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.ts index 4aa3994061..347734d3eb 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-node-summary/kubernetes-node-tags-card/kubernetes-node-tags-card.component.ts @@ -2,7 +2,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { AppChip } from '../../../../../../../../core/src/shared/components/chips/chips.component'; +import { AppChip } from '../../../../../../../core/src/shared/components/chips/chips.component'; import { KubernetesNodeService } from '../../../../services/kubernetes-node.service'; @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-data-source.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-data-source.ts similarity index 73% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-data-source.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-data-source.ts index a51fbe5a8d..2550ea4e65 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-data-source.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-data-source.ts @@ -4,10 +4,10 @@ import { DataFunction, DataFunctionDefinition, ListDataSource, -} from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; -import { IListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; -import { getPaginationKey } from '../../../../../../store/src/actions/pagination.actions'; -import { AppState } from '../../../../../../store/src/app-state'; +} from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; +import { IListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; +import { getPaginationKey } from '../../../../../store/src/actions/pagination.actions'; +import { AppState } from '../../../../../store/src/public-api'; import { kubeEntityCatalog } from '../../kubernetes-entity-catalog'; import { kubernetesNodesEntityType } from '../../kubernetes-entity-factory'; import { BaseKubeGuid } from '../../kubernetes-page.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts similarity index 92% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts index 244d2739e7..8fa1986be2 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/kubernetes-nodes-list-config.service.ts @@ -1,15 +1,15 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import { DataFunction } from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; -import { ITableColumn } from '../../../../../../core/src/shared/components/list/list-table/table.types'; +import { DataFunction } from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; +import { ITableColumn } from '../../../../../core/src/shared/components/list/list-table/table.types'; import { IListConfig, IListFilter, ListViewTypes, -} from '../../../../../../core/src/shared/components/list/list.component.types'; -import { AppState } from '../../../../../../store/src/app-state'; -import { PaginationEntityState } from '../../../../../../store/src/types/pagination.types'; +} from '../../../../../core/src/shared/components/list/list.component.types'; +import { AppState } from '../../../../../store/src/public-api'; +import { PaginationEntityState } from '../../../../../store/src/types/pagination.types'; import { BaseKubeGuid } from '../../kubernetes-page.types'; import { ConditionType, KubernetesAddressExternal, KubernetesAddressInternal, KubernetesNode } from '../../store/kube.types'; import { defaultHelmKubeListPageSize } from '../kube-helm-list-types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.ts similarity index 89% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.ts index 75d4b752a8..b59c679be9 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-nodes/node-pod-count/node-pod-count.component.ts @@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { TableCellCustom } from '../../../../../../../core/src/shared/components/list/list.types'; +import { TableCellCustom } from '../../../../../../core/src/shared/components/list/list.types'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; import { KubernetesNode } from '../../../store/kube.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.ts similarity index 90% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.ts index 526d4132e9..dcbfd41b47 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-containers/kubernetes-pod-containers.component.ts @@ -1,4 +1,3 @@ -/* tslint:disable:max-line-length */ import { TitleCasePipe } from '@angular/common'; import { Component, Input } from '@angular/core'; import moment from 'moment'; @@ -7,16 +6,14 @@ import { filter, map } from 'rxjs/operators'; import { BooleanIndicatorType, -} from '../../../../../../../core/src/shared/components/boolean-indicator/boolean-indicator.component'; +} from '../../../../../../core/src/shared/components/boolean-indicator/boolean-indicator.component'; import { TableCellBooleanIndicatorComponentConfig, -} from '../../../../../../../core/src/shared/components/list/list-table/table-cell-boolean-indicator/table-cell-boolean-indicator.component'; -import { CardCell } from '../../../../../../../core/src/shared/components/list/list.types'; +} from '../../../../../../core/src/shared/components/list/list-table/table-cell-boolean-indicator/table-cell-boolean-indicator.component'; +import { CardCell } from '../../../../../../core/src/shared/components/list/list.types'; import { kubeEntityCatalog } from '../../../kubernetes-entity-catalog'; import { Container, ContainerState, ContainerStatus, InitContainer, KubernetesPod } from '../../../store/kube.types'; -/* tslint:enable:max-line-length */ - export interface ContainerForTable { isInit: boolean; container: Container | InitContainer; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.ts index 7ba04b0ad1..37fab13a02 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-status/kubernetes-pod-status.component.ts @@ -1,6 +1,6 @@ import { Component, Input } from '@angular/core'; -import { TableCellCustom } from '../../../../../../../core/src/shared/components/list/list.types'; +import { TableCellCustom } from '../../../../../../core/src/shared/components/list/list.types'; import { KubernetesPodExpandedStatusTypes } from '../../../services/kubernetes-expanded-state'; import { KubernetesPod } from '../../../store/kube.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.ts similarity index 80% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.ts index 92dea65c38..f1b28c19a0 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pod-tags/kubernetes-pod-tags.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; -import { AppChip } from '../../../../../../../core/src/shared/components/chips/chips.component'; -import { TableCellCustom } from '../../../../../../../core/src/shared/components/list/list.types'; +import { AppChip } from '../../../../../../core/src/shared/components/chips/chips.component'; +import { TableCellCustom } from '../../../../../../core/src/shared/components/list/list.types'; import { KubeAPIResource, PodLabel } from '../../../store/kube.types'; @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pods-data-source.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pods-data-source.ts similarity index 73% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pods-data-source.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pods-data-source.ts index da0d7f92d2..dd6b22afcb 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pods-data-source.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pods-data-source.ts @@ -1,8 +1,8 @@ import { Store } from '@ngrx/store'; -import { ListDataSource } from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; -import { IListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; -import { AppState } from '../../../../../../store/src/app-state'; +import { ListDataSource } from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; +import { IListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; +import { AppState } from '../../../../../store/src/public-api'; import { kubeEntityCatalog } from '../../kubernetes-entity-catalog'; import { BaseKubeGuid } from '../../kubernetes-page.types'; import { KubernetesPod } from '../../store/kube.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pods-list-config.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pods-list-config.service.ts similarity index 92% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pods-list-config.service.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pods-list-config.service.ts index 0c1c5199e7..6534cf9b8a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-pods/kubernetes-pods-list-config.service.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-pods/kubernetes-pods-list-config.service.ts @@ -8,10 +8,10 @@ import { of } from 'rxjs'; import { TableCellSidePanelComponent, TableCellSidePanelConfig, -} from '../../../../../../core/src/shared/components/list/list-table/table-cell-side-panel/table-cell-side-panel.component'; -import { ITableColumn } from '../../../../../../core/src/shared/components/list/list-table/table.types'; -import { IListConfig, ListViewTypes } from '../../../../../../core/src/shared/components/list/list.component.types'; -import { AppState } from '../../../../../../store/src/app-state'; +} from '../../../../../core/src/shared/components/list/list-table/table-cell-side-panel/table-cell-side-panel.component'; +import { ITableColumn } from '../../../../../core/src/shared/components/list/list-table/table.types'; +import { IListConfig, ListViewTypes } from '../../../../../core/src/shared/components/list/list.component.types'; +import { AppState } from '../../../../../store/src/public-api'; import { BaseKubeGuid } from '../../kubernetes-page.types'; import { KubernetesResourceViewerComponent, diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.ts similarity index 81% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.ts index 2c0cad521b..2d1defd4de 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-service-ports/kubernetes-service-ports.component.ts @@ -1,6 +1,6 @@ import { Component, Input } from '@angular/core'; -import { CardCell } from '../../../../../../core/src/shared/components/list/list.types'; +import { CardCell } from '../../../../../core/src/shared/components/list/list.types'; import { KubeService } from '../../store/kube.types'; @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-services/kubernetes-service-card/kubernetes-service-card.component.html b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-services/kubernetes-service-card/kubernetes-service-card.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-services/kubernetes-service-card/kubernetes-service-card.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-services/kubernetes-service-card/kubernetes-service-card.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-services/kubernetes-service-card/kubernetes-service-card.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-services/kubernetes-service-card/kubernetes-service-card.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-services/kubernetes-service-card/kubernetes-service-card.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-services/kubernetes-service-card/kubernetes-service-card.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-services/kubernetes-service-card/kubernetes-service-card.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-services/kubernetes-service-card/kubernetes-service-card.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-services/kubernetes-service-card/kubernetes-service-card.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-services/kubernetes-service-card/kubernetes-service-card.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-services/kubernetes-service-card/kubernetes-service-card.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-services/kubernetes-service-card/kubernetes-service-card.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-services/kubernetes-service-card/kubernetes-service-card.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-services/kubernetes-service-card/kubernetes-service-card.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-services/kubernetes-service-list-config.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-services/kubernetes-service-list-config.service.ts similarity index 83% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-services/kubernetes-service-list-config.service.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-services/kubernetes-service-list-config.service.ts index e8615512b2..6c861ff29c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-services/kubernetes-service-list-config.service.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-services/kubernetes-service-list-config.service.ts @@ -1,12 +1,12 @@ import { of } from 'rxjs'; -import { ListDataSource } from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; +import { ListDataSource } from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; import { TableCellSidePanelComponent, TableCellSidePanelConfig, -} from '../../../../../../core/src/shared/components/list/list-table/table-cell-side-panel/table-cell-side-panel.component'; -import { ITableColumn } from '../../../../../../core/src/shared/components/list/list-table/table.types'; -import { IListConfig, ListViewTypes } from '../../../../../../core/src/shared/components/list/list.component.types'; +} from '../../../../../core/src/shared/components/list/list-table/table-cell-side-panel/table-cell-side-panel.component'; +import { ITableColumn } from '../../../../../core/src/shared/components/list/list-table/table.types'; +import { IListConfig, ListViewTypes } from '../../../../../core/src/shared/components/list/list.component.types'; import { KubernetesResourceViewerComponent, KubernetesResourceViewerConfig, diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-services/kubernetes-services-data-source.ts b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-services/kubernetes-services-data-source.ts similarity index 65% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-services/kubernetes-services-data-source.ts rename to src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-services/kubernetes-services-data-source.ts index 89f60d414a..e973ca1f75 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/list-types/kubernetes-services/kubernetes-services-data-source.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/list-types/kubernetes-services/kubernetes-services-data-source.ts @@ -1,10 +1,10 @@ import { Store } from '@ngrx/store'; import { OperatorFunction } from 'rxjs'; -import { ListDataSource } from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; -import { IListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; -import { AppState } from '../../../../../../store/src/app-state'; -import { PaginatedAction } from '../../../../../../store/src/types/pagination.types'; +import { ListDataSource } from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; +import { IListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; +import { AppState } from '../../../../../store/src/public-api'; +import { PaginatedAction } from '../../../../../store/src/types/pagination.types'; import { KubeService } from '../../store/kube.types'; export class BaseKubernetesServicesDataSource extends ListDataSource { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/pod-metrics/pod-metrics.component.html b/src/frontend/packages/kubernetes/src/kubernetes/pod-metrics/pod-metrics.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/pod-metrics/pod-metrics.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/pod-metrics/pod-metrics.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/pod-metrics/pod-metrics.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/pod-metrics/pod-metrics.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/pod-metrics/pod-metrics.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/pod-metrics/pod-metrics.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/pod-metrics/pod-metrics.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/pod-metrics/pod-metrics.component.spec.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/pod-metrics/pod-metrics.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/pod-metrics/pod-metrics.component.spec.ts index 5f245aad3a..cbf4e5677f 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/pod-metrics/pod-metrics.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/pod-metrics/pod-metrics.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ActivatedRoute } from '@angular/router'; -import { TabNavService } from '../../../../../core/src/tab-nav.service'; +import { TabNavService } from '../../../../core/src/tab-nav.service'; import { KubernetesBaseTestModules } from '../kubernetes.testing.module'; import { PodMetricsComponent } from './pod-metrics.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/pod-metrics/pod-metrics.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/pod-metrics/pod-metrics.component.ts similarity index 88% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/pod-metrics/pod-metrics.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/pod-metrics/pod-metrics.component.ts index 3fe46c7d38..6d0801f813 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/pod-metrics/pod-metrics.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/pod-metrics/pod-metrics.component.ts @@ -3,17 +3,17 @@ import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { getIdFromRoute } from '../../../../../core/src/core/utils.service'; -import { MetricsConfig } from '../../../../../core/src/shared/components/metrics-chart/metrics-chart.component'; -import { MetricsLineChartConfig } from '../../../../../core/src/shared/components/metrics-chart/metrics-chart.types'; +import { getIdFromRoute } from '../../../../core/src/core/utils.service'; +import { MetricsConfig } from '../../../../core/src/shared/components/metrics-chart/metrics-chart.component'; +import { MetricsLineChartConfig } from '../../../../core/src/shared/components/metrics-chart/metrics-chart.types'; import { ChartDataTypes, getMetricsChartConfigBuilder, -} from '../../../../../core/src/shared/components/metrics-chart/metrics.component.helpers'; -import { IHeaderBreadcrumb } from '../../../../../core/src/shared/components/page-header/page-header.types'; -import { EntityInfo } from '../../../../../store/src/types/api.types'; -import { ChartSeries, IMetricMatrixResult } from '../../../../../store/src/types/base-metric.types'; -import { IMetricApplication } from '../../../../../store/src/types/metric.types'; +} from '../../../../core/src/shared/components/metrics-chart/metrics.component.helpers'; +import { IHeaderBreadcrumb } from '../../../../core/src/shared/components/page-header/page-header.types'; +import { EntityInfo } from '../../../../store/src/types/api.types'; +import { ChartSeries, IMetricMatrixResult } from '../../../../store/src/types/base-metric.types'; +import { IMetricApplication } from '../../../../store/src/types/metric.types'; import { kubeEntityCatalog } from '../kubernetes-entity-catalog'; import { formatAxisCPUTime, formatCPUTime } from '../kubernetes-metrics.helpers'; import { BaseKubeGuid } from '../kubernetes-page.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/analysis-report.types.ts b/src/frontend/packages/kubernetes/src/kubernetes/services/analysis-report.types.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/services/analysis-report.types.ts rename to src/frontend/packages/kubernetes/src/kubernetes/services/analysis-report.types.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-endpoint.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/services/kubernetes-endpoint.service.ts similarity index 92% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-endpoint.service.ts rename to src/frontend/packages/kubernetes/src/kubernetes/services/kubernetes-endpoint.service.ts index 1bf6932297..b33f03a1f1 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-endpoint.service.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/services/kubernetes-endpoint.service.ts @@ -3,13 +3,14 @@ import { Store } from '@ngrx/store'; import { combineLatest, Observable, of } from 'rxjs'; import { filter, first, map, shareReplay, startWith, switchMap } from 'rxjs/operators'; -import { GetAllEndpoints } from '../../../../../store/src/actions/endpoint.actions'; -import { AppState } from '../../../../../store/src/app-state'; -import { EntityService } from '../../../../../store/src/entity-service'; -import { EntityServiceFactory } from '../../../../../store/src/entity-service-factory.service'; -import { PaginationObservables } from '../../../../../store/src/reducers/pagination-reducer/pagination-reducer.types'; -import { EntityInfo } from '../../../../../store/src/types/api.types'; -import { EndpointModel, EndpointUser } from '../../../../../store/src/types/endpoint.types'; +import { GetAllEndpoints } from '../../../../store/src/actions/endpoint.actions'; +import { AppState } from '../../../../store/src/app-state'; +import { EntityService } from '../../../../store/src/entity-service'; +import { EntityServiceFactory } from '../../../../store/src/entity-service-factory.service'; +import { EndpointModel } from '../../../../store/src/public-api'; +import { PaginationObservables } from '../../../../store/src/reducers/pagination-reducer/pagination-reducer.types'; +import { EntityInfo } from '../../../../store/src/types/api.types'; +import { EndpointUser } from '../../../../store/src/types/endpoint.types'; import { kubeEntityCatalog } from '../kubernetes-entity-catalog'; import { BaseKubeGuid } from '../kubernetes-page.types'; import { @@ -126,7 +127,7 @@ export class KubernetesEndpointService { info.versionMismatch = Object.keys(versions).length !== 1; return info; }) - ) + ); } getCaaspNodeData(n: KubernetesNode): CaaspNodeData { @@ -136,13 +137,13 @@ export class KubernetesEndpointService { updates: this.hasBooleanAnnotation(n.metadata.annotations, CAASP_HAS_UPDATES_ANNOTATION), disruptiveUpdates: this.hasBooleanAnnotation(n.metadata.annotations, CAASP_DISRUPTIVE_UPDATES_ANNOTATION), securityUpdates: this.hasBooleanAnnotation(n.metadata.annotations, CAASP_SECURITY_UPDATES_ANNOTATION) - } + }; } } // Check for the specified annotation with a value of 'yes' private hasBooleanAnnotation(annotations: Annotations, annotation: string): boolean { - return annotations[annotation] && annotations[annotation] === 'yes' ? true : false + return annotations[annotation] && annotations[annotation] === 'yes' ? true : false; } getNodeKubeVersions(nodes$: Observable = this.nodes$) { @@ -228,7 +229,7 @@ export class KubernetesEndpointService { this.pods$ = this.getObservable(kubeEntityCatalog.pod.store.getPaginationService(this.kubeGuid)); - this.nodes$ = this.getObservable(kubeEntityCatalog.node.store.getPaginationService(this.kubeGuid)) + this.nodes$ = this.getObservable(kubeEntityCatalog.node.store.getPaginationService(this.kubeGuid)); this.statefulSets$ = this.getObservable(kubeEntityCatalog.statefulSet.store.getPaginationService(this.kubeGuid)); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-expanded-state.ts b/src/frontend/packages/kubernetes/src/kubernetes/services/kubernetes-expanded-state.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-expanded-state.ts rename to src/frontend/packages/kubernetes/src/kubernetes/services/kubernetes-expanded-state.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-namespace.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/services/kubernetes-namespace.service.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-namespace.service.ts rename to src/frontend/packages/kubernetes/src/kubernetes/services/kubernetes-namespace.service.ts index 03f3758a06..d6c62cf663 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-namespace.service.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/services/kubernetes-namespace.service.ts @@ -3,7 +3,7 @@ import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs'; import { filter, first, map, publishReplay } from 'rxjs/operators'; -import { getIdFromRoute } from '../../../../../core/src/core/utils.service'; +import { getIdFromRoute } from '../../../../core/src/core/utils.service'; import { kubeEntityCatalog } from '../kubernetes-entity-catalog'; import { KubernetesNamespace } from '../store/kube.types'; import { KubernetesEndpointService } from './kubernetes-endpoint.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-node.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/services/kubernetes-node.service.ts similarity index 84% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-node.service.ts rename to src/frontend/packages/kubernetes/src/kubernetes/services/kubernetes-node.service.ts index 855173aef9..951a50de18 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes-node.service.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/services/kubernetes-node.service.ts @@ -4,12 +4,12 @@ import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { filter, first, map, publishReplay, refCount } from 'rxjs/operators'; -import { getIdFromRoute } from '../../../../../core/src/core/utils.service'; -import { MetricQueryConfig, MetricsAction } from '../../../../../store/src/actions/metrics.actions'; -import { AppState } from '../../../../../store/src/app-state'; -import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; -import { EntityInfo } from '../../../../../store/src/types/api.types'; -import { MetricQueryType } from '../../../../../store/src/types/metric.types'; +import { getIdFromRoute } from '../../../../core/src/core/utils.service'; +import { MetricQueryConfig, MetricsAction } from '../../../../store/src/actions/metrics.actions'; +import { EntityMonitorFactory } from '../../../../store/src/monitors/entity-monitor.factory.service'; +import { AppState } from '../../../../store/src/public-api'; +import { EntityInfo } from '../../../../store/src/types/api.types'; +import { MetricQueryType } from '../../../../store/src/types/metric.types'; import { kubeEntityCatalog } from '../kubernetes-entity-catalog'; import { KubernetesNode, MetricStatistic } from '../store/kube.types'; import { FetchKubernetesMetricsAction } from '../store/kubernetes.actions'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes.analysis.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/services/kubernetes.analysis.service.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes.analysis.service.ts rename to src/frontend/packages/kubernetes/src/kubernetes/services/kubernetes.analysis.service.ts index a5589d99b6..5c0454d3ad 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes.analysis.service.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/services/kubernetes.analysis.service.ts @@ -4,10 +4,10 @@ import { Store } from '@ngrx/store'; import { combineLatest, Observable } from 'rxjs'; import { filter, first, map, pairwise, startWith, tap } from 'rxjs/operators'; -import { SnackBarService } from '../../../../../core/src/shared/services/snackbar.service'; -import { ResetPaginationOfType } from '../../../../../store/src/actions/pagination.actions'; -import { AppState } from '../../../../../store/src/app-state'; -import { ListActionState, RequestInfoState } from '../../../../../store/src/reducers/api-request-reducer/types'; +import { SnackBarService } from '../../../../core/src/shared/services/snackbar.service'; +import { ResetPaginationOfType } from '../../../../store/src/actions/pagination.actions'; +import { AppState } from '../../../../store/src/app-state'; +import { ListActionState, RequestInfoState } from '../../../../store/src/reducers/api-request-reducer/types'; import { kubeEntityCatalog } from '../kubernetes-entity-catalog'; import { GetAnalysisReports } from '../store/analysis.actions'; import { AnalysisReport } from '../store/kube.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/services/kubernetes.service.ts similarity index 64% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes.service.ts rename to src/frontend/packages/kubernetes/src/kubernetes/services/kubernetes.service.ts index 09c03d154e..3f103e61f1 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubernetes.service.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/services/kubernetes.service.ts @@ -2,10 +2,10 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { map, shareReplay } from 'rxjs/operators'; -import { PaginationMonitor } from '../../../../../store/src/monitors/pagination-monitor'; -import { stratosEntityCatalog } from '../../../../../store/src/stratos-entity-catalog'; -import { APIResource, EntityInfo } from '../../../../../store/src/types/api.types'; -import { EndpointModel } from '../../../../../store/src/types/endpoint.types'; +import { PaginationMonitor } from '../../../../store/src/monitors/pagination-monitor'; +import { EndpointModel } from '../../../../store/src/public-api'; +import { stratosEntityCatalog } from '../../../../store/src/stratos-entity-catalog'; +import { APIResource, EntityInfo } from '../../../../store/src/types/api.types'; import { KUBERNETES_ENDPOINT_TYPE } from '../kubernetes-entity-factory'; @Injectable() @@ -15,7 +15,7 @@ export class KubernetesService { waitForAppEntity$: Observable>; constructor() { - this.kubeEndpointsMonitor = stratosEntityCatalog.endpoint.store.getAll.getPaginationMonitor() + this.kubeEndpointsMonitor = stratosEntityCatalog.endpoint.store.getAll.getPaginationMonitor(); this.kubeEndpoints$ = this.kubeEndpointsMonitor.currentPage$.pipe( map(endpoints => endpoints.filter(e => e.cnsi_type === KUBERNETES_ENDPOINT_TYPE)), diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubescore-report.helper.ts b/src/frontend/packages/kubernetes/src/kubernetes/services/kubescore-report.helper.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/services/kubescore-report.helper.ts rename to src/frontend/packages/kubernetes/src/kubernetes/services/kubescore-report.helper.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/popeye-report.helper.ts b/src/frontend/packages/kubernetes/src/kubernetes/services/popeye-report.helper.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/services/popeye-report.helper.ts rename to src/frontend/packages/kubernetes/src/kubernetes/services/popeye-report.helper.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/services/route.helper.ts b/src/frontend/packages/kubernetes/src/kubernetes/services/route.helper.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/services/route.helper.ts rename to src/frontend/packages/kubernetes/src/kubernetes/services/route.helper.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/action-builders/kube.action-builders.ts b/src/frontend/packages/kubernetes/src/kubernetes/store/action-builders/kube.action-builders.ts similarity index 97% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/store/action-builders/kube.action-builders.ts rename to src/frontend/packages/kubernetes/src/kubernetes/store/action-builders/kube.action-builders.ts index 22d30c7c9b..7d1cf49d2a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/action-builders/kube.action-builders.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/store/action-builders/kube.action-builders.ts @@ -1,6 +1,4 @@ -import { - OrchestratedActionBuilders, -} from '../../../../../../store/src/entity-catalog/action-orchestrator/action-orchestrator'; +import { OrchestratedActionBuilders } from '../../../../../store/src/entity-catalog/action-orchestrator/action-orchestrator'; import { GetHelmReleasePods, GetHelmReleaseServices } from '../../workloads/store/workloads.actions'; import { DeleteAnalysisReport, @@ -42,7 +40,7 @@ export interface KubePodActionBuilders extends OrchestratedActionBuilders { get: ( podName: string, kubeGuid: string, - extraArgs: { namespace: string; } + extraArgs: { namespace: string, } ) => GetKubernetesPod, getMultiple: ( kubeGuid: string, diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/analysis.actions.ts b/src/frontend/packages/kubernetes/src/kubernetes/store/analysis.actions.ts similarity index 94% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/store/analysis.actions.ts rename to src/frontend/packages/kubernetes/src/kubernetes/store/analysis.actions.ts index 5748da68f3..551b3c43d1 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/analysis.actions.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/store/analysis.actions.ts @@ -1,5 +1,5 @@ -import { getActions } from '../../../../../store/src/actions/action.helper'; -import { PaginatedAction } from '../../../../../store/src/types/pagination.types'; +import { getActions } from '../../../../store/src/actions/action.helper'; +import { PaginatedAction } from '../../../../store/src/types/pagination.types'; import { analysisReportEntityType, KUBERNETES_ENDPOINT_TYPE, kubernetesEntityFactory } from '../kubernetes-entity-factory'; import { KubeAction, KubePaginationAction, KubeSingleEntityAction } from './kubernetes.actions'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/analysis.effects.ts b/src/frontend/packages/kubernetes/src/kubernetes/store/analysis.effects.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/store/analysis.effects.ts rename to src/frontend/packages/kubernetes/src/kubernetes/store/analysis.effects.ts index 24ab4ea499..d3f8dbb8a8 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/analysis.effects.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/store/analysis.effects.ts @@ -4,16 +4,10 @@ import { Actions, Effect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; import { catchError, flatMap, mergeMap } from 'rxjs/operators'; -import { environment } from '../../../../../core/src/environments/environment'; -import { AppState } from '../../../../../store/src/app-state'; -import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; -import { ApiRequestTypes } from '../../../../../store/src/reducers/api-request-reducer/request-helpers'; -import { NormalizedResponse } from '../../../../../store/src/types/api.types'; -import { - StartRequestAction, - WrapperRequestActionFailed, - WrapperRequestActionSuccess, -} from '../../../../../store/src/types/request.types'; +import { environment } from '../../../../core/src/environments/environment'; +import { AppState, entityCatalog, NormalizedResponse, WrapperRequestActionSuccess } from '../../../../store/src/public-api'; +import { ApiRequestTypes } from '../../../../store/src/reducers/api-request-reducer/request-helpers'; +import { StartRequestAction, WrapperRequestActionFailed } from '../../../../store/src/types/request.types'; import { KubeScoreReportHelper } from '../services/kubescore-report.helper'; import { PopeyeReportHelper } from '../services/popeye-report.helper'; import { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kube.getIds.ts b/src/frontend/packages/kubernetes/src/kubernetes/store/kube.getIds.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kube.getIds.ts rename to src/frontend/packages/kubernetes/src/kubernetes/store/kube.getIds.ts index 898062b926..256d39ca39 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kube.getIds.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/store/kube.getIds.ts @@ -1,4 +1,4 @@ -import { environment } from '../../../../../core/src/environments/environment'; +import { environment } from '../../../../core/src/environments/environment'; import { BasicKubeAPIResource, KubernetesDeployment, @@ -17,13 +17,13 @@ const debugMissingKubeId = (entity: BasicKubeAPIResource, func: (...args: string console.warn(`Kube entity does not have a kubeId, this is probably a bug: `, entity); } return func(...args); -} +}; -export const getGuidFromKubeNode = (kubeGuid: string, name: string): string => deliminate(name, kubeGuid) +export const getGuidFromKubeNode = (kubeGuid: string, name: string): string => deliminate(name, kubeGuid); export const getGuidFromKubeNodeObj = (entity: KubernetesNode): string => debugMissingKubeId(entity, getGuidFromKubeNode, entity.metadata.kubeId, entity.metadata.name); -export const getGuidFromKubeNamespace = (kubeGuid: string, name: string): string => deliminate(name, kubeGuid) +export const getGuidFromKubeNamespace = (kubeGuid: string, name: string): string => deliminate(name, kubeGuid); export const getGuidFromKubeNamespaceObj = (entity: KubernetesNamespace): string => debugMissingKubeId(entity, getGuidFromKubeNamespace, entity.metadata.kubeId, entity.metadata.name); @@ -45,5 +45,5 @@ export const getGuidFromKubePod = (kubeGuid: string, namespace: string, name: st export const getGuidFromKubePodObj = (entity: KubernetesPod): string => debugMissingKubeId(entity, getGuidFromKubePod, entity.metadata.kubeId, entity.metadata.namespace, entity.metadata.name); -export const getGuidFromKubeDashboard = (kubeGuid: string): string => kubeGuid +export const getGuidFromKubeDashboard = (kubeGuid: string): string => kubeGuid; export const getGuidFromKubeDashboardObj = (entity: KubeDashboardStatus): string => getGuidFromKubeDashboard(entity.kubeGuid); diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kube.types.ts b/src/frontend/packages/kubernetes/src/kubernetes/store/kube.types.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kube.types.ts rename to src/frontend/packages/kubernetes/src/kubernetes/store/kube.types.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kubernetes.actions.ts b/src/frontend/packages/kubernetes/src/kubernetes/store/kubernetes.actions.ts similarity index 97% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kubernetes.actions.ts rename to src/frontend/packages/kubernetes/src/kubernetes/store/kubernetes.actions.ts index b35b07a92b..ebba3d6dca 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kubernetes.actions.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/store/kubernetes.actions.ts @@ -2,10 +2,10 @@ import { SortDirection } from '@angular/material/sort'; import { getActions } from 'frontend/packages/store/src/actions/action.helper'; import { ApiRequestTypes } from 'frontend/packages/store/src/reducers/api-request-reducer/request-helpers'; -import { MetricQueryConfig, MetricsAction, MetricsChartAction } from '../../../../../store/src/actions/metrics.actions'; -import { getPaginationKey } from '../../../../../store/src/actions/pagination.actions'; -import { PaginatedAction, PaginationParam } from '../../../../../store/src/types/pagination.types'; -import { EntityRequestAction } from '../../../../../store/src/types/request.types'; +import { MetricQueryConfig, MetricsAction, MetricsChartAction } from '../../../../store/src/actions/metrics.actions'; +import { getPaginationKey } from '../../../../store/src/actions/pagination.actions'; +import { PaginatedAction, PaginationParam } from '../../../../store/src/types/pagination.types'; +import { EntityRequestAction } from '../../../../store/src/types/request.types'; import { KUBERNETES_ENDPOINT_TYPE, kubernetesDashboardEntityType, @@ -230,7 +230,7 @@ export class GetKubernetesPods implements KubePaginationAction { export class GetKubernetesPodsOnNode extends GetKubernetesPods { constructor(kubeGuid: string, public nodeName: string) { - super(kubeGuid) + super(kubeGuid); this.paginationKey = getPaginationKey(kubernetesPodsEntityType, `node-${nodeName}`, kubeGuid); this.initialParams.fieldSelector = `spec.nodeName=${nodeName}`; } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kubernetes.effects.ts b/src/frontend/packages/kubernetes/src/kubernetes/store/kubernetes.effects.ts similarity index 95% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kubernetes.effects.ts rename to src/frontend/packages/kubernetes/src/kubernetes/store/kubernetes.effects.ts index e839283471..a430bc9bb1 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/store/kubernetes.effects.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/store/kubernetes.effects.ts @@ -8,16 +8,10 @@ import { connectedEndpointsOfTypesSelector } from 'frontend/packages/store/src/s import { of } from 'rxjs'; import { catchError, first, flatMap, map, mergeMap, switchMap } from 'rxjs/operators'; -import { environment } from '../../../../../core/src/environments/environment'; -import { AppState } from '../../../../../store/src/app-state'; -import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; -import { isJetstreamError } from '../../../../../store/src/jetstream'; -import { NormalizedResponse } from '../../../../../store/src/types/api.types'; -import { - StartRequestAction, - WrapperRequestActionFailed, - WrapperRequestActionSuccess, -} from '../../../../../store/src/types/request.types'; +import { environment } from '../../../../core/src/environments/environment'; +import { isJetstreamError } from '../../../../store/src/jetstream'; +import { AppState, entityCatalog, NormalizedResponse, WrapperRequestActionSuccess } from '../../../../store/src/public-api'; +import { StartRequestAction, WrapperRequestActionFailed } from '../../../../store/src/types/request.types'; import { KUBERNETES_ENDPOINT_TYPE, kubernetesDashboardEntityType, @@ -79,8 +73,8 @@ export interface KubeDashboardStatus { running: boolean; pod: { spec: { - containers: KubeDashboardContainer[] - } + containers: KubeDashboardContainer[]; + }; }; version: string; service: { @@ -408,7 +402,7 @@ export class KubernetesEffects { return 'Kubernetes API request error'; } - private createKubeError(err: any): { status: string, message: string } { + private createKubeError(err: any): { status: string, message: string, } { const jetstreamError = isJetstreamError(err); if (jetstreamError) { // Wrapped error diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.html b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.theme.scss b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.theme.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.theme.scss rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.theme.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.html b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.spec.ts similarity index 81% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.spec.ts index d959dc1a83..12fc0e9c0b 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.spec.ts @@ -1,10 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { SidePanelService } from '../../../../../../../core/src/shared/services/side-panel.service'; +import { SharedModule } from '../../../../../../core/src/public-api'; +import { SidePanelService } from '../../../../../../core/src/shared/services/side-panel.service'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; import { KubernetesAnalysisService } from '../../../services/kubernetes.analysis.service'; -import { SharedModule } from './../../../../../../../core/src/shared/shared.module'; import { AnalysisInfoCardComponent } from './analysis-info-card/analysis-info-card.component'; import { KubernetesAnalysisInfoComponent } from './kubernetes-analysis-info.component'; @@ -15,7 +15,7 @@ describe('KubernetesAnalysisInfoComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ KubernetesAnalysisInfoComponent, AnalysisInfoCardComponent ], + declarations: [KubernetesAnalysisInfoComponent, AnalysisInfoCardComponent], imports: [ SharedModule, KubernetesBaseTestModules, @@ -27,7 +27,7 @@ describe('KubernetesAnalysisInfoComponent', () => { KubeBaseGuidMock, ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.ts index 76cce96ccc..13213f6394 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/kubernetes-analysis-info.component.ts @@ -17,7 +17,7 @@ export class KubernetesAnalysisInfoComponent implements PreviewableComponent { analyzers$: Observable; - setProps(props: { [key: string]: any; }) { + setProps(props: { [key: string]: any, }) { this.analyzers$ = props.analyzers$; } } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.html b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.spec.ts similarity index 89% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.spec.ts index ee1e090856..5e94f069a2 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.spec.ts @@ -1,10 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../../../core/src/tab-nav.service'; +import { TabNavService } from '../../../../../../core/src/tab-nav.service'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; import { KubernetesAnalysisService } from '../../../services/kubernetes.analysis.service'; -import { CoreModule } from './../../../../../../../core/src/core/core.module'; +import { CoreModule } from './../../../../../../core/src/core/core.module'; import { AnalysisReportViewerComponent } from './../../../analysis-report-viewer/analysis-report-viewer.component'; import { KubernetesAnalysisReportComponent } from './kubernetes-analysis-report.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.theme.scss b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.theme.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.theme.scss rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.theme.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.html b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.spec.ts similarity index 90% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.spec.ts index 1706cd886f..ea14935d32 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from 'frontend/packages/core/src/tab-nav.service'; -import { MDAppModule } from '../../../../../../core/src/public-api'; +import { MDAppModule } from '../../../../../core/src/public-api'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; import { KubernetesAnalysisService } from '../../services/kubernetes.analysis.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-tab.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-namespaces-tab/kubernetes-namespaces-tab.component.html b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-namespaces-tab/kubernetes-namespaces-tab.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-namespaces-tab/kubernetes-namespaces-tab.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-namespaces-tab/kubernetes-namespaces-tab.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-namespaces-tab/kubernetes-namespaces-tab.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-namespaces-tab/kubernetes-namespaces-tab.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-namespaces-tab/kubernetes-namespaces-tab.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-namespaces-tab/kubernetes-namespaces-tab.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-namespaces-tab/kubernetes-namespaces-tab.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-namespaces-tab/kubernetes-namespaces-tab.component.ts similarity index 86% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-namespaces-tab/kubernetes-namespaces-tab.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-namespaces-tab/kubernetes-namespaces-tab.component.ts index 2611cb9caa..3bbe73f96a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-namespaces-tab/kubernetes-namespaces-tab.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-namespaces-tab/kubernetes-namespaces-tab.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { ListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; +import { ListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; import { KubernetesNamespacesListConfigService, } from '../../list-types/kubernetes-namespaces/kubernetes-namespaces-list-config.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.html b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.ts similarity index 83% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.ts index 07b711cc83..a33344f0d1 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-nodes-tab/kubernetes-nodes-tab.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; -import { ListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; +import { ListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; import { KubernetesNodesListConfigService } from '../../list-types/kubernetes-nodes/kubernetes-nodes-list-config.service'; @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.html b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.ts similarity index 82% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.ts index e4f52d8eb5..3315248a53 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-pods-tab/kubernetes-pods-tab.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; -import { ListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; +import { ListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; import { KubernetesPodsListConfigService } from '../../list-types/kubernetes-pods/kubernetes-pods-list-config.service'; @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.html b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.spec.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.spec.ts index c5ec93f420..05b38cdc1a 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.spec.ts @@ -1,7 +1,7 @@ import { HttpClient, HttpHandler } from '@angular/common/http'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../../core/src/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { KubeBaseGuidMock, KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; import { KubernetesSummaryTabComponent } from './kubernetes-summary.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.theme.scss b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.theme.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.theme.scss rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.theme.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts similarity index 91% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts index b5a69213bb..efe1eeab7c 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts @@ -6,16 +6,15 @@ import { Store } from '@ngrx/store'; import { combineLatest, interval, Observable, Subscription } from 'rxjs'; import { first, map, startWith } from 'rxjs/operators'; -import { safeUnsubscribe } from '../../../../../../core/src/core/utils.service'; +import { safeUnsubscribe } from '../../../../../core/src/core/utils.service'; import { IChartThresholds, ISimpleUsageChartData, -} from '../../../../../../core/src/shared/components/simple-usage-chart/simple-usage-chart.types'; -import { AppState } from '../../../../../../store/src/app-state'; -import { entityCatalog } from '../../../../../../store/src/entity-catalog/entity-catalog'; -import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; -import { getCurrentPageRequestInfo } from '../../../../../../store/src/reducers/pagination-reducer/pagination-reducer.types'; -import { PaginatedAction, PaginationEntityState } from '../../../../../../store/src/types/pagination.types'; +} from '../../../../../core/src/shared/components/simple-usage-chart/simple-usage-chart.types'; +import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; +import { AppState, entityCatalog } from '../../../../../store/src/public-api'; +import { getCurrentPageRequestInfo } from '../../../../../store/src/reducers/pagination-reducer/pagination-reducer.types'; +import { PaginatedAction, PaginationEntityState } from '../../../../../store/src/types/pagination.types'; import { kubeEntityCatalog } from '../../kubernetes-entity-catalog'; import { CaaspNodesData, KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; @@ -119,13 +118,13 @@ export class KubernetesSummaryTabComponent implements OnInit, OnDestroy { const podsObs = kubeEntityCatalog.pod.store.getPaginationService(guid); const pods$ = podsObs.entities$; - this.poll(kubeEntityCatalog.pod.actions.getMultiple(guid), podsObs.pagination$) + this.poll(kubeEntityCatalog.pod.actions.getMultiple(guid), podsObs.pagination$); const nodesObs = kubeEntityCatalog.node.store.getPaginationService(guid); const nodes$ = nodesObs.entities$; - this.poll(kubeEntityCatalog.node.actions.getMultiple(guid), nodesObs.pagination$) + this.poll(kubeEntityCatalog.node.actions.getMultiple(guid), nodesObs.pagination$); const namespacesObs = kubeEntityCatalog.namespace.store.getPaginationService(guid); const namespaces$ = namespacesObs.entities$; - this.poll(kubeEntityCatalog.namespace.actions.getMultiple(guid), namespacesObs.pagination$) + this.poll(kubeEntityCatalog.namespace.actions.getMultiple(guid), namespacesObs.pagination$); this.podCount$ = this.kubeEndpointService.getCountObservable(pods$); this.nodeCount$ = this.kubeEndpointService.getCountObservable(nodes$); @@ -200,7 +199,7 @@ export class KubernetesSummaryTabComponent implements OnInit, OnDestroy { if (!getCurrentPageRequestInfo(pag, { busy: true, error: false, message: '' }).busy) { this.store.dispatch(action); } - }) + }); } ngOnDestroy() { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.html b/src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/chart-values-editor.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/chart-values-editor.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/chart-values-editor.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/chart-values-editor.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/chart-values-editor.component.spec.ts similarity index 80% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/chart-values-editor.component.spec.ts index 8dc9906951..0c222d2b2d 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/chart-values-editor.component.spec.ts @@ -3,8 +3,8 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { createBasicStoreModule } from '@stratosui/store/testing'; -import { ConfirmationDialogService } from '../../../../../../core/src/shared/components/confirmation-dialog.service'; -import { MDAppModule } from './../../../../../../core/src/core/md.module'; +import { MDAppModule } from '../../../../../core/src/public-api'; +import { ConfirmationDialogService } from '../../../../../core/src/shared/components/confirmation-dialog.service'; import { ChartValuesEditorComponent } from './chart-values-editor.component'; describe('ChartValuesEditorComponent', () => { @@ -13,7 +13,7 @@ describe('ChartValuesEditorComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ ChartValuesEditorComponent ], + declarations: [ChartValuesEditorComponent], providers: [ HttpClient, HttpHandler, @@ -26,7 +26,7 @@ describe('ChartValuesEditorComponent', () => { createBasicStoreModule(), ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.theme.scss b/src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/chart-values-editor.component.theme.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.theme.scss rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/chart-values-editor.component.theme.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts similarity index 97% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts index 60b56a6bf5..928a764be7 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/chart-values-editor.component.ts @@ -5,9 +5,9 @@ import * as yaml from 'js-yaml'; import { BehaviorSubject, combineLatest, fromEvent, Observable, of, Subscription } from 'rxjs'; import { catchError, debounceTime, filter, map, startWith, tap } from 'rxjs/operators'; -import { ConfirmationDialogConfig } from '../../../../../../core/src/shared/components/confirmation-dialog.config'; -import { ConfirmationDialogService } from '../../../../../../core/src/shared/components/confirmation-dialog.service'; -import { ThemeService } from './../../../../../../store/src/theme.service'; +import { ConfirmationDialogConfig } from '../../../../../core/src/shared/components/confirmation-dialog.config'; +import { ConfirmationDialogService } from '../../../../../core/src/shared/components/confirmation-dialog.service'; +import { ThemeService } from '../../../../../store/src/theme.service'; import { diffObjects } from './diffvalues'; import { generateJsonSchemaFromObject } from './json-schema-generator'; import { mergeObjects } from './merge'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/diffvalues.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/diffvalues.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/diffvalues.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/diffvalues.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/json-schema-generator.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/json-schema-generator.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/json-schema-generator.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/json-schema-generator.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/merge.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/merge.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/chart-values-editor/merge.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/merge.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.html b/src/frontend/packages/kubernetes/src/kubernetes/workloads/create-release/create-release.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/create-release/create-release.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/workloads/create-release/create-release.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/create-release/create-release.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/create-release/create-release.component.spec.ts similarity index 74% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/create-release/create-release.component.spec.ts index 9de5331552..f4194499a6 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/create-release/create-release.component.spec.ts @@ -1,13 +1,12 @@ -// import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { HttpClient } from '@angular/common/http'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { ConfirmationDialogService } from '../../../../../../core/src/shared/components/confirmation-dialog.service'; -import { TabNavService } from '../../../../../../core/src/tab-nav.service'; -import { EntityMonitorFactory } from '../../../../../../store/src/monitors/entity-monitor.factory.service'; -import { InternalEventMonitorFactory } from '../../../../../../store/src/monitors/internal-event-monitor.factory'; -import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; +import { ConfirmationDialogService } from '../../../../../core/src/shared/components/confirmation-dialog.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; +import { EntityMonitorFactory } from '../../../../../store/src/monitors/entity-monitor.factory.service'; +import { InternalEventMonitorFactory } from '../../../../../store/src/monitors/internal-event-monitor.factory'; +import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; import { MockChartService } from '../../../helm/monocular/shared/services/chart.service.mock'; import { ChartsService } from '../../../helm/monocular/shared/services/charts.service'; import { ConfigService } from '../../../helm/monocular/shared/services/config.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/create-release/create-release.component.ts similarity index 96% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/create-release/create-release.component.ts index 438c58c404..a126fe31f5 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/create-release/create-release.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/create-release/create-release.component.ts @@ -4,13 +4,10 @@ import { ActivatedRoute } from '@angular/router'; import { BehaviorSubject, combineLatest, Observable, of, Subscription } from 'rxjs'; import { distinctUntilChanged, filter, first, map, pairwise, startWith, switchMap } from 'rxjs/operators'; -import { EndpointsService } from '../../../../../../core/src/core/endpoints.service'; -import { safeUnsubscribe } from '../../../../../../core/src/core/utils.service'; -import { - StepOnNextFunction, - StepOnNextResult, -} from '../../../../../../core/src/shared/components/stepper/step/step.component'; -import { RequestInfoState } from '../../../../../../store/src/reducers/api-request-reducer/types'; +import { EndpointsService } from '../../../../../core/src/core/endpoints.service'; +import { safeUnsubscribe } from '../../../../../core/src/core/utils.service'; +import { StepOnNextFunction, StepOnNextResult } from '../../../../../core/src/shared/components/stepper/step/step.component'; +import { RequestInfoState } from '../../../../../store/src/reducers/api-request-reducer/types'; import { helmEntityCatalog } from '../../../helm/helm-entity-catalog'; import { ChartsService } from '../../../helm/monocular/shared/services/charts.service'; import { createMonocularProviders } from '../../../helm/monocular/stratos-monocular-providers.helpers'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.html b/src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.ts index fff1bab700..7090e26af4 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-release-card/helm-release-card.component.ts @@ -1,7 +1,7 @@ import { DatePipe } from '@angular/common'; import { Component, Input } from '@angular/core'; -import { CardCell } from '../../../../../../../core/src/shared/components/list/list.types'; +import { CardCell } from '../../../../../../core/src/shared/components/list/list.types'; import { HelmRelease } from '../../workload.types'; @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-pods-list-config.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-release-pods-list-config.service.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-pods-list-config.service.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-release-pods-list-config.service.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-pods-list-source.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-release-pods-list-source.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-pods-list-source.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-release-pods-list-source.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-services-list-config.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-release-services-list-config.service.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-services-list-config.service.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-release-services-list-config.service.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-services-list-source.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-release-services-list-source.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-release-services-list-source.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-release-services-list-source.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-releases-list-config.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-releases-list-config.service.ts similarity index 98% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-releases-list-config.service.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-releases-list-config.service.ts index 3ab9f96d4d..205c587b60 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-releases-list-config.service.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-releases-list-config.service.ts @@ -14,7 +14,7 @@ import { import { AppState } from 'frontend/packages/store/src/app-state'; import { filter, map } from 'rxjs/operators'; -import { ListView } from '../../../../../../store/src/actions/list.actions'; +import { ListView } from '../../../../../store/src/actions/list.actions'; import { defaultHelmKubeListPageSize } from '../../list-types/kube-helm-list-types'; import { HelmRelease } from '../workload.types'; import { HelmReleaseCardComponent } from './helm-release-card/helm-release-card.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-releases-list-source.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-releases-list-source.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/helm-releases-list-source.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/helm-releases-list-source.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/kube-namespaces-filter-config.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/kube-namespaces-filter-config.service.ts similarity index 97% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/kube-namespaces-filter-config.service.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/kube-namespaces-filter-config.service.ts index 846848fc7a..ca476270ce 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/list-types/kube-namespaces-filter-config.service.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/list-types/kube-namespaces-filter-config.service.ts @@ -17,7 +17,7 @@ import { withLatestFrom, } from 'rxjs/operators'; -import { getCurrentPageRequestInfo } from '../../../../../../store/src/reducers/pagination-reducer/pagination-reducer.types'; +import { getCurrentPageRequestInfo } from '../../../../../store/src/reducers/pagination-reducer/pagination-reducer.types'; import { kubeEntityCatalog } from '../../kubernetes-entity-catalog'; import { KUBERNETES_ENDPOINT_TYPE } from '../../kubernetes-entity-factory'; import { KubernetesNamespace } from '../../store/kube.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-socket-service.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/helm-release-tab-base/helm-release-socket-service.ts similarity index 94% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-socket-service.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/helm-release-tab-base/helm-release-socket-service.ts index 920e07a48d..95f891c894 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-socket-service.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/helm-release-tab-base/helm-release-socket-service.ts @@ -4,10 +4,9 @@ import { Subject, Subscription } from 'rxjs'; import makeWebSocketObservable, { GetWebSocketResponses } from 'rxjs-websockets'; import { catchError, map, share, switchMap } from 'rxjs/operators'; -import { SnackBarService } from '../../../../../../../core/src/shared/services/snackbar.service'; -import { AppState } from '../../../../../../../store/src/app-state'; -import { entityCatalog } from '../../../../../../../store/src/entity-catalog/entity-catalog'; -import { EntityRequestAction, WrapperRequestActionSuccess } from '../../../../../../../store/src/types/request.types'; +import { SnackBarService } from '../../../../../../core/src/shared/services/snackbar.service'; +import { AppState, entityCatalog, WrapperRequestActionSuccess } from '../../../../../../store/src/public-api'; +import { EntityRequestAction } from '../../../../../../store/src/types/request.types'; import { kubeEntityCatalog } from '../../../kubernetes-entity-catalog'; import { KubernetesPodExpandedStatusHelper } from '../../../services/kubernetes-expanded-state'; import { KubernetesPod, KubeService } from '../../../store/kube.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.html b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.spec.ts similarity index 95% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.spec.ts index 521eb3a53e..7dd6fde844 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from 'frontend/packages/core/src/tab-nav.service'; +import { TabNavService } from '../../../../../../core/src/tab-nav.service'; import { HelmReleaseProviders, KubeBaseGuidMock, KubernetesBaseTestModules } from '../../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; import { KubernetesAnalysisService } from '../../../services/kubernetes.analysis.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts similarity index 86% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts index d121deed5d..36b41f47cd 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/helm-release-tab-base/helm-release-tab-base.component.ts @@ -3,9 +3,9 @@ import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { IPageSideNavTab } from '../../../../../../../core/src/features/dashboard/page-side-nav/page-side-nav.component'; -import { SessionService } from '../../../../../../../core/src/shared/services/session.service'; -import { SnackBarService } from '../../../../../../../core/src/shared/services/snackbar.service'; +import { IPageSideNavTab } from '../../../../../../core/src/features/dashboard/page-side-nav/page-side-nav.component'; +import { SessionService } from '../../../../../../core/src/shared/services/session.service'; +import { SnackBarService } from '../../../../../../core/src/shared/services/snackbar.service'; import { KubernetesAnalysisService } from '../../../services/kubernetes.analysis.service'; import { HelmReleaseGuid } from '../../workload.types'; import { HelmReleaseHelperService } from '../tabs/helm-release-helper.service'; @@ -66,11 +66,11 @@ export class HelmReleaseTabBaseComponent implements OnDestroy { { link: 'services', label: 'Services', icon: 'service', iconFont: 'stratos-icons' } ]; - this.socketService.start() + this.socketService.start(); } ngOnDestroy() { - this.socketService.stop() + this.socketService.stop(); this.snackbarService.hide(); } } diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/icon-helper.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/icon-helper.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/icon-helper.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/icon-helper.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.html b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.spec.ts similarity index 95% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.spec.ts index 9d531b7a8e..3ae0b29782 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from 'frontend/packages/core/src/tab-nav.service'; +import { TabNavService } from '../../../../../../../core/src/tab-nav.service'; import { AnalysisReportViewerComponent } from '../../../../analysis-report-viewer/analysis-report-viewer.component'; import { HelmReleaseProviders, KubeBaseGuidMock, KubernetesBaseTestModules } from '../../../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../../../services/kubernetes-endpoint.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-analysis-tab/helm-release-analysis-tab.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-helper.service.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helper.service.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-helper.service.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helpers.scss b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-helpers.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-helpers.scss rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-helpers.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.html b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.ts similarity index 91% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.ts index 08907252bc..797e0bfcf7 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-history-tab/helm-release-history-tab.component.ts @@ -5,8 +5,8 @@ import { map, startWith } from 'rxjs/operators'; import { ITableListDataSource, -} from '../../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; -import { ITableColumn } from '../../../../../../../../core/src/shared/components/list/list-table/table.types'; +} from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; +import { ITableColumn } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; import { HelmReleaseHelperService } from './../helm-release-helper.service'; @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-notes-tab/helm-release-notes-tab.component.html b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-notes-tab/helm-release-notes-tab.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-notes-tab/helm-release-notes-tab.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-notes-tab/helm-release-notes-tab.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-notes-tab/helm-release-notes-tab.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-notes-tab/helm-release-notes-tab.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-notes-tab/helm-release-notes-tab.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-notes-tab/helm-release-notes-tab.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-notes-tab/helm-release-notes-tab.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-notes-tab/helm-release-notes-tab.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-notes-tab/helm-release-notes-tab.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-notes-tab/helm-release-notes-tab.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-notes-tab/helm-release-notes-tab.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-notes-tab/helm-release-notes-tab.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-notes-tab/helm-release-notes-tab.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-notes-tab/helm-release-notes-tab.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.html b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-pods/helm-release-pods-tab.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.html b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.spec.ts similarity index 95% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.spec.ts index dca8ca40f6..6395c37110 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.spec.ts @@ -1,8 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NgxGraphModule } from '@swimlane/ngx-graph'; import { SidePanelService } from 'frontend/packages/core/src/shared/services/side-panel.service'; -import { TabNavService } from 'frontend/packages/core/src/tab-nav.service'; +import { TabNavService } from '../../../../../../../core/src/tab-nav.service'; import { HelmReleaseProviders, KubernetesBaseTestModules } from '../../../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../../../services/kubernetes-endpoint.service'; import { KubernetesAnalysisService } from '../../../../services/kubernetes.analysis.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-resource-graph/helm-release-resource-graph.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.html b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-services/helm-release-services-tab.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss similarity index 96% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss index b2f30b0f4a..9e24c2a39e 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.scss @@ -1,5 +1,5 @@ -@import '../../../../../../../../core/sass/mixins'; +@import '../../../../../../../core/sass/mixins'; @import '../helm-release-helpers'; .resources { diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts similarity index 89% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts index 85a9bcbb98..d1779d79e7 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.spec.ts @@ -1,7 +1,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from 'frontend/packages/core/src/tab-nav.service'; -import { SidePanelService } from '../../../../../../../../core/src/shared/services/side-panel.service'; +import { SidePanelService } from '../../../../../../../core/src/shared/services/side-panel.service'; +import { TabNavService } from '../../../../../../../core/src/tab-nav.service'; import { HelmReleaseProviders, KubeBaseGuidMock } from '../../../../kubernetes.testing.module'; import { KubernetesEndpointService } from '../../../../services/kubernetes-endpoint.service'; import { KubernetesAnalysisService } from '../../../../services/kubernetes.analysis.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.theme.scss b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.theme.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.theme.scss rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.theme.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts similarity index 98% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts index f1c0413fc7..d896843d89 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.ts @@ -10,8 +10,8 @@ import { AppState } from 'frontend/packages/store/src/app-state'; import { combineLatest, Observable, ReplaySubject, Subject } from 'rxjs'; import { distinctUntilChanged, filter, first, map, publishReplay, refCount, startWith } from 'rxjs/operators'; -import { SnackBarService } from '../../../../../../../../core/src/shared/services/snackbar.service'; -import { endpointsEntityRequestDataSelector } from '../../../../../../../../store/src/selectors/endpoint.selectors'; +import { SnackBarService } from '../../../../../../../core/src/shared/services/snackbar.service'; +import { endpointsEntityRequestDataSelector } from '../../../../../../../store/src/selectors/endpoint.selectors'; import { ResourceAlertPreviewComponent, } from '../../../../analysis-report-viewer/resource-alert-preview/resource-alert-preview.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.html b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.spec.ts similarity index 92% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.spec.ts index dd864daa79..f7dc5b580b 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from 'frontend/packages/core/src/tab-nav.service'; +import { TabNavService } from '../../../../../../../core/src/tab-nav.service'; import { HelmReleaseProviders, KubernetesBaseTestModules } from '../../../../kubernetes.testing.module'; import { HelmReleaseValuesTabComponent } from './helm-release-values-tab.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-values-tab/helm-release-values-tab.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.html b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/release/workload-live-reload/workload-live-reload.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/releases-tab/releases-tab.component.html b/src/frontend/packages/kubernetes/src/kubernetes/workloads/releases-tab/releases-tab.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/releases-tab/releases-tab.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/releases-tab/releases-tab.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/releases-tab/releases-tab.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/workloads/releases-tab/releases-tab.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/releases-tab/releases-tab.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/releases-tab/releases-tab.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/releases-tab/releases-tab.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/releases-tab/releases-tab.component.spec.ts similarity index 92% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/releases-tab/releases-tab.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/releases-tab/releases-tab.component.spec.ts index 20f505b6b5..ad908e0608 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/releases-tab/releases-tab.component.spec.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/releases-tab/releases-tab.component.spec.ts @@ -1,7 +1,7 @@ import { DatePipe } from '@angular/common'; import { async, TestBed } from '@angular/core/testing'; -import { TabNavService } from 'frontend/packages/core/src/tab-nav.service'; +import { TabNavService } from '../../../../../core/src/tab-nav.service'; import { KubernetesBaseTestModules } from '../../kubernetes.testing.module'; import { HelmReleaseHelperService } from '../release/tabs/helm-release-helper.service'; import { HelmReleasesTabComponent } from './releases-tab.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/releases-tab/releases-tab.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/releases-tab/releases-tab.component.ts similarity index 92% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/releases-tab/releases-tab.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/releases-tab/releases-tab.component.ts index f86ea01624..45a52b3142 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/releases-tab/releases-tab.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/releases-tab/releases-tab.component.ts @@ -5,7 +5,7 @@ import { endpointOfTypeSelector } from 'frontend/packages/store/src/selectors/en import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { ListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; +import { ListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; import { HELM_ENDPOINT_TYPE } from '../../../helm/helm-entity-factory'; import { HelmReleasesListConfig } from '../list-types/helm-releases-list-config.service'; import { KubernetesNamespacesFilterService } from '../list-types/kube-namespaces-filter-config.service'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workload-action-builders.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/store/workload-action-builders.ts similarity index 94% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workload-action-builders.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/store/workload-action-builders.ts index f85d09243e..35ea3c8c30 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workload-action-builders.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/store/workload-action-builders.ts @@ -1,6 +1,4 @@ -import { - OrchestratedActionBuilders, -} from '../../../../../../store/src/entity-catalog/action-orchestrator/action-orchestrator'; +import { OrchestratedActionBuilders } from '../../../../../store/src/entity-catalog/action-orchestrator/action-orchestrator'; import { HelmUpgradeValues } from '../../../helm/store/helm.types'; import { GetHelmRelease, diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-factory.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/store/workloads-entity-factory.ts similarity index 96% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-factory.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/store/workloads-entity-factory.ts index 25ada89e41..afd43b3e33 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-factory.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/store/workloads-entity-factory.ts @@ -1,4 +1,4 @@ -import { EntitySchema } from '../../../../../../store/src/helpers/entity-schema'; +import { EntitySchema } from '../../../../../store/src/helpers/entity-schema'; import { addKubernetesEntitySchema, KubernetesEntitySchema } from '../../kubernetes-entity-factory'; import { HelmRelease, HelmReleaseGraph, HelmReleaseResources } from '../workload.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-generator.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/store/workloads-entity-generator.ts similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-generator.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/store/workloads-entity-generator.ts index d1695d2322..7bc1162555 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads-entity-generator.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/store/workloads-entity-generator.ts @@ -1,9 +1,9 @@ import { StratosBaseCatalogEntity, StratosCatalogEntity, -} from '../../../../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; -import { StratosEndpointExtensionDefinition } from '../../../../../../store/src/entity-catalog/entity-catalog.types'; -import { IFavoriteMetadata } from '../../../../../../store/src/types/user-favorites.types'; +} from '../../../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; +import { StratosEndpointExtensionDefinition } from '../../../../../store/src/entity-catalog/entity-catalog.types'; +import { IFavoriteMetadata } from '../../../../../store/src/types/user-favorites.types'; import { kubernetesEntityFactory } from '../../kubernetes-entity-factory'; import { HelmRelease, HelmReleaseGraph, HelmReleaseResources } from '../workload.types'; import { workloadsEntityCatalog } from '../workloads-entity-catalog'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.actions.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/store/workloads.actions.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.actions.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/store/workloads.actions.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.effects.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/store/workloads.effects.ts similarity index 94% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.effects.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/store/workloads.effects.ts index 7c235a2766..58d77f01f7 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.effects.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/store/workloads.effects.ts @@ -6,16 +6,18 @@ import { environment } from 'frontend/packages/core/src/environments/environment import { Observable } from 'rxjs'; import { catchError, flatMap, mergeMap } from 'rxjs/operators'; -import { AppState } from '../../../../../../store/src/app-state'; -import { entityCatalog } from '../../../../../../store/src/entity-catalog/entity-catalog'; -import { ApiRequestTypes } from '../../../../../../store/src/reducers/api-request-reducer/request-helpers'; -import { NormalizedResponse } from '../../../../../../store/src/types/api.types'; +import { + AppState, + entityCatalog, + NormalizedResponse, + WrapperRequestActionSuccess, +} from '../../../../../store/src/public-api'; +import { ApiRequestTypes } from '../../../../../store/src/reducers/api-request-reducer/request-helpers'; import { EntityRequestAction, StartRequestAction, WrapperRequestActionFailed, - WrapperRequestActionSuccess, -} from '../../../../../../store/src/types/request.types'; +} from '../../../../../store/src/types/request.types'; import { HelmEffects } from '../../../helm/store/helm.effects'; import { HelmRelease, HelmReleaseHistory } from '../workload.types'; import { workloadsEntityCatalog } from './../workloads-entity-catalog'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.reducers.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/store/workloads.reducers.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.reducers.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/store/workloads.reducers.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.store.module.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/store/workloads.store.module.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/store/workloads.store.module.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/store/workloads.store.module.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-data-source.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/upgrade-release/release-version-data-source.ts similarity index 80% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-data-source.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/upgrade-release/release-version-data-source.ts index 204cc8b66a..6f51797181 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-data-source.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/upgrade-release/release-version-data-source.ts @@ -1,10 +1,10 @@ import { Store } from '@ngrx/store'; import { Observable, of } from 'rxjs'; -import { ListDataSource } from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; -import { RowState } from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; -import { IListConfig } from '../../../../../../core/src/shared/components/list/list.component.types'; -import { PaginationEntityState } from '../../../../../../store/src/types/pagination.types'; +import { ListDataSource } from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; +import { RowState } from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; +import { IListConfig } from '../../../../../core/src/shared/components/list/list.component.types'; +import { PaginationEntityState } from '../../../../../store/src/types/pagination.types'; import { helmEntityCatalog } from '../../../helm/helm-entity-catalog'; import { MonocularVersion } from './../../../helm/store/helm.types'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-list-config.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/upgrade-release/release-version-list-config.ts similarity index 88% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-list-config.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/upgrade-release/release-version-list-config.ts index 0d0f0307c5..5ea00c36ca 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/release-version-list-config.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/upgrade-release/release-version-list-config.ts @@ -3,11 +3,11 @@ import moment from 'moment'; import { BehaviorSubject, of } from 'rxjs'; import { first } from 'rxjs/operators'; -import { ListDataSource } from '../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; +import { ListDataSource } from '../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; import { TableCellRadioComponent, -} from '../../../../../../core/src/shared/components/list/list-table/table-cell-radio/table-cell-radio.component'; -import { ITableColumn } from '../../../../../../core/src/shared/components/list/list-table/table.types'; +} from '../../../../../core/src/shared/components/list/list-table/table-cell-radio/table-cell-radio.component'; +import { ITableColumn } from '../../../../../core/src/shared/components/list/list-table/table.types'; import { defaultPaginationPageSizeOptionsTable, IGlobalListAction, @@ -16,7 +16,7 @@ import { IListMultiFilterConfig, IMultiListAction, ListViewTypes, -} from '../../../../../../core/src/shared/components/list/list.component.types'; +} from '../../../../../core/src/shared/components/list/list.component.types'; import { MonocularVersion } from '../../../helm/store/helm.types'; import { HelmReleaseVersionsDataSource } from './release-version-data-source'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.html b/src/frontend/packages/kubernetes/src/kubernetes/workloads/upgrade-release/upgrade-release.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.html rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/upgrade-release/upgrade-release.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.scss b/src/frontend/packages/kubernetes/src/kubernetes/workloads/upgrade-release/upgrade-release.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.scss rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/upgrade-release/upgrade-release.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/upgrade-release/upgrade-release.component.spec.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.spec.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/upgrade-release/upgrade-release.component.spec.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/upgrade-release/upgrade-release.component.ts similarity index 97% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/upgrade-release/upgrade-release.component.ts index 8d4b789437..5b05e01a51 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/upgrade-release/upgrade-release.component.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/upgrade-release/upgrade-release.component.ts @@ -8,8 +8,8 @@ import { StepComponent, StepOnNextFunction, StepOnNextResult, -} from '../../../../../../core/src/shared/components/stepper/step/step.component'; -import { ActionState } from '../../../../../../store/src/reducers/api-request-reducer/types'; +} from '../../../../../core/src/shared/components/stepper/step/step.component'; +import { ActionState } from '../../../../../store/src/reducers/api-request-reducer/types'; import { ChartsService } from '../../../helm/monocular/shared/services/charts.service'; import { createMonocularProviders } from '../../../helm/monocular/stratos-monocular-providers.helpers'; import { stratosMonocularEndpointGuid } from '../../../helm/monocular/stratos-monocular.helper'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workload.types.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/workload.types.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workload.types.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/workload.types.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads-entity-catalog.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/workloads-entity-catalog.ts similarity index 84% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads-entity-catalog.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/workloads-entity-catalog.ts index 30a8791541..d202d00092 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads-entity-catalog.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/workloads-entity-catalog.ts @@ -1,5 +1,5 @@ -import { StratosCatalogEntity } from '../../../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; -import { IFavoriteMetadata } from '../../../../../store/src/types/user-favorites.types'; +import { StratosCatalogEntity } from '../../../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity'; +import { IFavoriteMetadata } from '../../../../store/src/types/user-favorites.types'; import { WorkloadGraphBuilders, WorkloadReleaseBuilders, diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/workloads.module.ts similarity index 95% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/workloads.module.ts index 341c83d740..1c96bf2606 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.module.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/workloads.module.ts @@ -4,8 +4,8 @@ import { MaterialDesignFrameworkModule } from '@cfstratos/ajsf-material'; import { NgxGraphModule } from '@swimlane/ngx-graph'; import { MonacoEditorModule, NgxMonacoEditorConfig } from 'ngx-monaco-editor'; -import { CoreModule } from '../../../../../core/src/core/core.module'; -import { SharedModule } from '../../../../../core/src/shared/shared.module'; +import { CoreModule } from '../../../../core/src/core/core.module'; +import { SharedModule } from '../../../../core/src/shared/shared.module'; import { KubernetesModule } from '../kubernetes.module'; import { ChartValuesEditorComponent } from './chart-values-editor/chart-values-editor.component'; import { CreateReleaseComponent } from './create-release/create-release.component'; diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.routing.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/workloads.routing.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.routing.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/workloads.routing.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.testing.module.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/workloads.testing.module.ts similarity index 66% rename from src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.testing.module.ts rename to src/frontend/packages/kubernetes/src/kubernetes/workloads/workloads.testing.module.ts index 5f6b193777..5d1b9d4b87 100644 --- a/src/frontend/packages/suse-extensions/src/custom/kubernetes/workloads/workloads.testing.module.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/workloads.testing.module.ts @@ -1,15 +1,18 @@ import { HttpClientModule } from '@angular/common/http'; import { NgModule } from '@angular/core'; -import { CoreModule } from '@angular/flex-layout'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { SharedModule } from '../../../../../core/src/public-api'; -import { AppTestModule } from '../../../../../core/test-framework/core-test.helper'; -import { CATALOGUE_ENTITIES, EntityCatalogFeatureModule } from '../../../../../store/src/entity-catalog.module'; -import { entityCatalog, TestEntityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog'; -import { generateStratosEntities } from '../../../../../store/src/stratos-entity-generator'; -import { createBasicStoreModule } from '../../../../../store/testing/public-api'; +import { CoreModule, SharedModule } from '../../../../core/src/public-api'; +import { AppTestModule } from '../../../../core/test-framework/core-test.helper'; +import { + CATALOGUE_ENTITIES, + entityCatalog, + EntityCatalogFeatureModule, + TestEntityCatalog, +} from '../../../../store/src/public-api'; +import { generateStratosEntities } from '../../../../store/src/stratos-entity-generator'; +import { createBasicStoreModule } from '../../../../store/testing/public-api'; import { generateHelmEntities } from '../../helm/helm-entity-generator'; import { HelmTestingModule } from '../../helm/helm-testing.module'; import { generateKubernetesEntities } from '../kubernetes-entity-generator'; @@ -44,4 +47,4 @@ export const WorkloadsBaseTestingModule = [ SharedModule, HelmTestingModule, WorkloadsTestingModule -] +]; diff --git a/src/frontend/packages/kubernetes/src/public-api.ts b/src/frontend/packages/kubernetes/src/public-api.ts new file mode 100644 index 0000000000..4da9d3ad7b --- /dev/null +++ b/src/frontend/packages/kubernetes/src/public-api.ts @@ -0,0 +1,4 @@ +// Custom Extensions + +export * from './kube-package.module'; +export * from './kube-package-routing.module'; \ No newline at end of file diff --git a/src/frontend/packages/kubernetes/src/test.ts b/src/frontend/packages/kubernetes/src/test.ts new file mode 100644 index 0000000000..f205020844 --- /dev/null +++ b/src/frontend/packages/kubernetes/src/test.ts @@ -0,0 +1,36 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files +import 'core-js/es/reflect'; +import 'zone.js/dist/zone'; +import 'zone.js/dist/zone-testing'; + +import { APP_BASE_HREF } from '@angular/common'; +import { getTestBed } from '@angular/core/testing'; +import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; + + +declare const require: any; + +// First, initialize the Angular testing environment. +const testBed = getTestBed(); +testBed.initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); + +beforeEach(() => { + testBed.configureTestingModule({ + providers: [{ provide: APP_BASE_HREF, useValue: '/' }] + }); +}); + +/** + * Bump up the Jasmine timeout from 5 seconds + */ +beforeAll(() => { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; +}); + +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); diff --git a/src/frontend/packages/kubernetes/tsconfig.spec.json b/src/frontend/packages/kubernetes/tsconfig.spec.json new file mode 100644 index 0000000000..cb4a7be918 --- /dev/null +++ b/src/frontend/packages/kubernetes/tsconfig.spec.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../tsconfig.spec.json", + "compilerOptions": { + "outDir": "../../../../out-tsc/spec", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "src/test.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/src/frontend/packages/kubernetes/tslint.json b/src/frontend/packages/kubernetes/tslint.json new file mode 100644 index 0000000000..9da788b6cb --- /dev/null +++ b/src/frontend/packages/kubernetes/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": "../../../../tslint.json" +} diff --git a/src/frontend/packages/suse-extensions/package.json b/src/frontend/packages/suse-extensions/package.json index f41a5ce1bb..346c719420 100644 --- a/src/frontend/packages/suse-extensions/package.json +++ b/src/frontend/packages/suse-extensions/package.json @@ -7,7 +7,6 @@ }, "stratos": { "module": "SuseModule", - "routingModule": "SuseRoutingModule", "theming": "sass/_all-theme#apply-theme-suse-extensions", "assets": { "assets": "core/assets" diff --git a/src/frontend/packages/suse-extensions/sass/_all-theme.scss b/src/frontend/packages/suse-extensions/sass/_all-theme.scss index 3f1be2c34b..2c1f83a02a 100644 --- a/src/frontend/packages/suse-extensions/sass/_all-theme.scss +++ b/src/frontend/packages/suse-extensions/sass/_all-theme.scss @@ -1,25 +1,11 @@ -// Theming for the copmponents in the Kubernetes package +// Theming for the copmponents in the SUSE Extensions package -@import '../src/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.theme'; -@import '../src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-report/kubernetes-analysis-report.component.theme'; -@import '../src/custom/kubernetes/tabs/kubernetes-analysis-tab/kubernetes-analysis-info/analysis-info-card/analysis-info-card.component.theme'; -@import '../src/custom/helm/list-types/monocular-chart-card/monocular-chart-card.component.theme'; -@import '../src/custom/kubernetes/workloads/release/tabs/helm-release-summary-tab/helm-release-summary-tab.component.theme'; -@import '../src/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.theme'; -@import '../src/custom/kubernetes/workloads/chart-values-editor/chart-values-editor.component.theme'; -@import '../src/custom/suse-login/suse-login.component.theme'; +@import '../src/suse-login/suse-login.component.theme'; @mixin apply-theme-suse-extensions($stratos-theme) { $theme: map-get($stratos-theme, theme); $app-theme: map-get($stratos-theme, app-theme); - @include kube-summary-theme($theme, $app-theme); - @include kube-analysis-report-theme($theme, $app-theme); - @include kube-analysis-card-theme($theme, $app-theme); - @include monocular-chart-card($theme, $app-theme); - @include helm-release-summary-tab-theme($theme, $app-theme); - @include kube-node-link-theme($theme, $app-theme); - @include app-chart-values-editor-theme($theme, $app-theme); @include suse-login-page-theme($theme, $app-theme); } diff --git a/src/frontend/packages/suse-extensions/src/custom/demo/demo-helper/demo-helper.component.html b/src/frontend/packages/suse-extensions/src/demo/demo-helper/demo-helper.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/demo/demo-helper/demo-helper.component.html rename to src/frontend/packages/suse-extensions/src/demo/demo-helper/demo-helper.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/demo/demo-helper/demo-helper.component.scss b/src/frontend/packages/suse-extensions/src/demo/demo-helper/demo-helper.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/demo/demo-helper/demo-helper.component.scss rename to src/frontend/packages/suse-extensions/src/demo/demo-helper/demo-helper.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/demo/demo-helper/demo-helper.component.spec.ts b/src/frontend/packages/suse-extensions/src/demo/demo-helper/demo-helper.component.spec.ts similarity index 84% rename from src/frontend/packages/suse-extensions/src/custom/demo/demo-helper/demo-helper.component.spec.ts rename to src/frontend/packages/suse-extensions/src/demo/demo-helper/demo-helper.component.spec.ts index b0c270cf94..e1ff7ca609 100644 --- a/src/frontend/packages/suse-extensions/src/custom/demo/demo-helper/demo-helper.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/demo/demo-helper/demo-helper.component.spec.ts @@ -1,8 +1,8 @@ import { HttpClientModule } from '@angular/common/http'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { TabNavService } from '../../../../../core/src/tab-nav.service'; -import { BaseTestModules } from '../../../../../core/test-framework/core-test.helper'; +import { TabNavService } from '../../../../core/src/tab-nav.service'; +import { BaseTestModules } from '../../../../core/test-framework/core-test.helper'; import { DemoHelperComponent } from './demo-helper.component'; describe('DemoHelperComponent', () => { diff --git a/src/frontend/packages/suse-extensions/src/custom/demo/demo-helper/demo-helper.component.ts b/src/frontend/packages/suse-extensions/src/demo/demo-helper/demo-helper.component.ts similarity index 83% rename from src/frontend/packages/suse-extensions/src/custom/demo/demo-helper/demo-helper.component.ts rename to src/frontend/packages/suse-extensions/src/demo/demo-helper/demo-helper.component.ts index 044d47f2c4..2a18d570da 100644 --- a/src/frontend/packages/suse-extensions/src/custom/demo/demo-helper/demo-helper.component.ts +++ b/src/frontend/packages/suse-extensions/src/demo/demo-helper/demo-helper.component.ts @@ -3,8 +3,8 @@ import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import { map } from 'rxjs/operators'; -import { StratosAction, StratosActionType } from '../../../../../core/src/core/extension/extension-service'; -import { AppState } from '../../../../../store/src/app-state'; +import { StratosAction, StratosActionType } from '../../../../core/src/core/extension/extension-service'; +import { AppState } from '../../../../store/src/app-state'; @StratosAction({ diff --git a/src/frontend/packages/suse-extensions/src/public-api.ts b/src/frontend/packages/suse-extensions/src/public-api.ts index 82ee7d4da7..715977a55c 100644 --- a/src/frontend/packages/suse-extensions/src/public-api.ts +++ b/src/frontend/packages/suse-extensions/src/public-api.ts @@ -1,4 +1,3 @@ // Custom Extensions -export * from './custom/suse.module' -export * from './custom/suse-routing.module' \ No newline at end of file +export * from './suse.module'; \ No newline at end of file diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-about-info/suse-about-info.component.html b/src/frontend/packages/suse-extensions/src/suse-about-info/suse-about-info.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/suse-about-info/suse-about-info.component.html rename to src/frontend/packages/suse-extensions/src/suse-about-info/suse-about-info.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-about-info/suse-about-info.component.scss b/src/frontend/packages/suse-extensions/src/suse-about-info/suse-about-info.component.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/suse-about-info/suse-about-info.component.scss rename to src/frontend/packages/suse-extensions/src/suse-about-info/suse-about-info.component.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-about-info/suse-about-info.component.ts b/src/frontend/packages/suse-extensions/src/suse-about-info/suse-about-info.component.ts similarity index 79% rename from src/frontend/packages/suse-extensions/src/custom/suse-about-info/suse-about-info.component.ts rename to src/frontend/packages/suse-extensions/src/suse-about-info/suse-about-info.component.ts index b67379c282..0147da6855 100644 --- a/src/frontend/packages/suse-extensions/src/custom/suse-about-info/suse-about-info.component.ts +++ b/src/frontend/packages/suse-extensions/src/suse-about-info/suse-about-info.component.ts @@ -3,9 +3,9 @@ import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { filter, map } from 'rxjs/operators'; -import { AppState } from '../../../../store/src/app-state'; -import { AuthState } from '../../../../store/src/reducers/auth.reducer'; -import { SessionData } from '../../../../store/src/types/auth.types'; +import { AppState } from '../../../store/src/app-state'; +import { AuthState } from '../../../store/src/reducers/auth.reducer'; +import { SessionData } from '../../../store/src/types/auth.types'; @Component({ selector: 'app-suse-about-info', diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.html b/src/frontend/packages/suse-extensions/src/suse-login/suse-login.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.html rename to src/frontend/packages/suse-extensions/src/suse-login/suse-login.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss b/src/frontend/packages/suse-extensions/src/suse-login/suse-login.component.scss similarity index 98% rename from src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss rename to src/frontend/packages/suse-extensions/src/suse-login/suse-login.component.scss index 0bab1c84ab..095d223561 100644 --- a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.scss +++ b/src/frontend/packages/suse-extensions/src/suse-login/suse-login.component.scss @@ -1,4 +1,4 @@ -@import '../../../../suse-theme/sass/colors'; +@import '../../../suse-theme/sass/colors.scss'; app-suse-login { background: url(/core/assets/custom/login/sign-in-bg.svg) #7AD4AA no-repeat center fixed; diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.spec.ts b/src/frontend/packages/suse-extensions/src/suse-login/suse-login.component.spec.ts similarity index 78% rename from src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.spec.ts rename to src/frontend/packages/suse-extensions/src/suse-login/suse-login.component.spec.ts index 2955b20df0..67dd3a6277 100644 --- a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.spec.ts +++ b/src/frontend/packages/suse-extensions/src/suse-login/suse-login.component.spec.ts @@ -4,9 +4,9 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; import { StoreModule } from '@ngrx/store'; -import { CoreModule } from '../../../../core/src/core/core.module'; -import { appReducers } from '../../../../store/src/reducers.module'; -import { SharedModule } from './../../../../core/src/shared/shared.module'; +import { CoreModule } from '../../../core/src/core/core.module'; +import { appReducers } from '../../../store/src/reducers.module'; +import { SharedModule } from './../../../core/src/shared/shared.module'; import { SuseLoginComponent } from './suse-login.component'; describe('SuseLoginComponent', () => { @@ -15,7 +15,7 @@ describe('SuseLoginComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ SuseLoginComponent ], + declarations: [SuseLoginComponent], imports: [ CommonModule, CoreModule, @@ -25,7 +25,7 @@ describe('SuseLoginComponent', () => { StoreModule.forRoot(appReducers) ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.theme.scss b/src/frontend/packages/suse-extensions/src/suse-login/suse-login.component.theme.scss similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.theme.scss rename to src/frontend/packages/suse-extensions/src/suse-login/suse-login.component.theme.scss diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.ts b/src/frontend/packages/suse-extensions/src/suse-login/suse-login.component.ts similarity index 66% rename from src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.ts rename to src/frontend/packages/suse-extensions/src/suse-login/suse-login.component.ts index 1376343844..4503f066bd 100644 --- a/src/frontend/packages/suse-extensions/src/custom/suse-login/suse-login.component.ts +++ b/src/frontend/packages/suse-extensions/src/suse-login/suse-login.component.ts @@ -1,10 +1,10 @@ import { Component, ViewEncapsulation } from '@angular/core'; import { Store } from '@ngrx/store'; -import { CustomizationService, CustomizationsMetadata } from '../../../../core/src/core/customizations.types'; -import { StratosLoginComponent } from '../../../../core/src/core/extension/extension-service'; -import { LoginPageComponent } from '../../../../core/src/features/login/login-page/login-page.component'; -import { AppState } from '../../../../store/src/app-state'; +import { CustomizationService, CustomizationsMetadata } from '../../../core/src/core/customizations.types'; +import { StratosLoginComponent } from '../../../core/src/core/extension/extension-service'; +import { LoginPageComponent } from '../../../core/src/features/login/login-page/login-page.component'; +import { AppState } from '../../../store/src/app-state'; @StratosLoginComponent() @Component({ diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-welcome/suse-welcome.component.html b/src/frontend/packages/suse-extensions/src/suse-welcome/suse-welcome.component.html similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/suse-welcome/suse-welcome.component.html rename to src/frontend/packages/suse-extensions/src/suse-welcome/suse-welcome.component.html diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-welcome/suse-welcome.component.scss b/src/frontend/packages/suse-extensions/src/suse-welcome/suse-welcome.component.scss similarity index 93% rename from src/frontend/packages/suse-extensions/src/custom/suse-welcome/suse-welcome.component.scss rename to src/frontend/packages/suse-extensions/src/suse-welcome/suse-welcome.component.scss index c0e5cadd87..e9a131dffe 100644 --- a/src/frontend/packages/suse-extensions/src/custom/suse-welcome/suse-welcome.component.scss +++ b/src/frontend/packages/suse-extensions/src/suse-welcome/suse-welcome.component.scss @@ -1,4 +1,4 @@ -@import '../../../../suse-theme/sass/colors'; +@import '../../../suse-theme/sass/colors'; .no-endpoints-message { color: $suse-button-gray; diff --git a/src/frontend/packages/suse-extensions/src/custom/suse-welcome/suse-welcome.component.ts b/src/frontend/packages/suse-extensions/src/suse-welcome/suse-welcome.component.ts similarity index 100% rename from src/frontend/packages/suse-extensions/src/custom/suse-welcome/suse-welcome.component.ts rename to src/frontend/packages/suse-extensions/src/suse-welcome/suse-welcome.component.ts diff --git a/src/frontend/packages/suse-extensions/src/custom/suse.module.ts b/src/frontend/packages/suse-extensions/src/suse.module.ts similarity index 68% rename from src/frontend/packages/suse-extensions/src/custom/suse.module.ts rename to src/frontend/packages/suse-extensions/src/suse.module.ts index d147814f58..1f7f78c047 100644 --- a/src/frontend/packages/suse-extensions/src/custom/suse.module.ts +++ b/src/frontend/packages/suse-extensions/src/suse.module.ts @@ -1,13 +1,14 @@ import { NgModule } from '@angular/core'; -import { CoreModule } from '../../../core/src/core/core.module'; -import { CustomizationService, CustomizationsMetadata } from '../../../core/src/core/customizations.types'; -import { MDAppModule } from '../../../core/src/core/md.module'; -import { ExtensionService } from '../../../core/src/public-api'; -import { SharedModule } from '../../../core/src/shared/shared.module'; +import { + CoreModule, + CustomizationService, + CustomizationsMetadata, + ExtensionService, + MDAppModule, + SharedModule, +} from '../../core/src/public-api'; import { DemoHelperComponent } from './demo/demo-helper/demo-helper.component'; -import { HelmSetupModule } from './helm/helm.setup.module'; -import { KubernetesSetupModule } from './kubernetes/kubernetes.setup.module'; import { SuseAboutInfoComponent } from './suse-about-info/suse-about-info.component'; import { SuseLoginComponent } from './suse-login/suse-login.component'; import { SuseWelcomeComponent } from './suse-welcome/suse-welcome.component'; @@ -25,8 +26,6 @@ const SuseCustomizations: CustomizationsMetadata = { CoreModule, SharedModule, MDAppModule, - KubernetesSetupModule, - HelmSetupModule, ExtensionService.declare([ SuseLoginComponent, ]) diff --git a/src/jetstream/go.mod b/src/jetstream/go.mod index 4ca6a15bdc..6c3aded846 100644 --- a/src/jetstream/go.mod +++ b/src/jetstream/go.mod @@ -45,6 +45,7 @@ require ( github.com/gorilla/sessions v1.1.3 github.com/gorilla/websocket v1.4.2 github.com/govau/cf-common v0.0.7 + github.com/helm/monocular v1.10.0 // indirect github.com/jessevdk/go-flags v1.4.0 // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/kat-co/vala v0.0.0-20170210184112-42e1d8b61f12 diff --git a/src/jetstream/go.sum b/src/jetstream/go.sum index 9488234375..6db73bcdb5 100644 --- a/src/jetstream/go.sum +++ b/src/jetstream/go.sum @@ -24,6 +24,7 @@ github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxB github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -34,6 +35,7 @@ github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e h1:eb0Pzkt15Bm github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.3.1/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= @@ -137,6 +139,7 @@ github.com/deislabs/oras v0.7.0 h1:RnDoFd3tQYODMiUqxgQ8JxlrlWL0/VMKIKRD01MmNYk= github.com/deislabs/oras v0.7.0/go.mod h1:sqMKPG3tMyIX9xwXUBRLhZ24o+uT4y6jgBD2RzUTKDM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/disintegration/imaging v1.5.0/go.mod h1:9B/deIUIrliYkyMTuXJd6OUFLcrZ2tf+3Qlwnaf/CjU= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/docker/cli v0.0.0-20190506213505-d88565df0c2d h1:qdD+BtyCE1XXpDyhvn0yZVcZOLILdj9Cw4pKu0kQbPQ= @@ -186,6 +189,7 @@ github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2H github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/globalsign/mgo v0.0.0-20180615134936-113d3961e731/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= @@ -297,6 +301,7 @@ github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORR github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= @@ -326,6 +331,7 @@ github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG github.com/helm/monocular v1.4.0 h1:g0sOpuMe+9u+aPfd9ZO8mWV+c8W0dfGyBG9Wl23nwec= github.com/helm/monocular v1.4.0/go.mod h1:PpkCN0v4zVVigsIHnsQdJytKFmaUkwfhxB7z33a9/gE= github.com/helm/monocular v1.10.0 h1:/tkbVH0+7GR2C4W2ODJGiVGXyHmYCa3vaBb5S+pz6bE= +github.com/helm/monocular v1.10.0/go.mod h1:2pJcZSWeUPTtw1QSje6c/qCrSuSUujoNPgUoFc8ySMQ= github.com/heptio/authenticator v0.3.0/go.mod h1:Q86X8hc61JXhE5XxYLKmrSRWby/Oe8IIYZIBgmGVkTA= github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40 h1:GT4RsKmHh1uZyhmTkWJTDALRjSHYQp6FRKrotf0zhAs= github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40/go.mod h1:NtmN9h8vrTveVQRLHcX2HQ5wIPBDCsZ351TGbZWgg38= @@ -339,6 +345,7 @@ github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jinzhu/copier v0.0.0-20180308034124-7e38e58719c3/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= @@ -358,6 +365,7 @@ github.com/kat-co/vala v0.0.0-20170210184112-42e1d8b61f12/go.mod h1:u9MdXq/QageO github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -367,6 +375,7 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kubeapps/common v0.0.0-20190307100129-fcd6537ca4e3/go.mod h1:TsgmjeDpbftqhwPKInJ3v+l+xbHs4goiB6DFb2WqY9c= github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a h1:VqeX/fehAB6FtBox0TVYcjOMXGE56INQIfbXegditX4= github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a/go.mod h1:TsgmjeDpbftqhwPKInJ3v+l+xbHs4goiB6DFb2WqY9c= github.com/kubernetes-sigs/aws-iam-authenticator v0.3.1-0.20190111160901-390d9087a4bc h1:Ttr4Z3ZrMv4rAXn10UAqOC8ACx+F1omvcyV1a3hRArE= @@ -467,6 +476,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/poy/eachers v0.0.0-20181020210610-23942921fe77/go.mod h1:x1vqpbcMW9T/KRcQ4b48diSiSVtYgvwQ5xzDByEg4WE= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.0.0-20181001174001-0a8115f42e03/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -476,11 +486,13 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1: github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/procfs v0.0.0-20180920065004-418d78d0b9a7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190129233650-316cf8ccfec5/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -497,6 +509,7 @@ github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0 github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= @@ -512,6 +525,7 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -531,6 +545,7 @@ github.com/tedsuo/rata v1.0.0/go.mod h1:X47ELzhOoLbfFIY0Cql9P6yo3Cdwf2CMX3FVZxRz github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/unrolled/render v0.0.0-20180914162206-b9786414de4d/go.mod h1:tu82oB5W2ykJRVioYsB+IQKcft7ryBr7w12qMBUPyXg= github.com/unrolled/render v1.0.1 h1:VDDnQQVfBMsOsp3VaCJszSO0nkBIVEYoPWeRThk9spY= github.com/unrolled/render v1.0.1/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0obPf6NBdM= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= @@ -582,6 +597,7 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/image v0.0.0-20180926015637-991ec62608f3/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -627,6 +643,7 @@ golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -716,6 +733,7 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/helm v2.13.1+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= k8s.io/helm v2.16.1+incompatible h1:L+k810plJlaGWEw1EszeT4deK8XVaKxac1oGcuB+WDc= k8s.io/helm v2.16.1+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= k8s.io/helm v2.16.10+incompatible h1:eFksERw3joHEL62TrcDX8I5fgEQJvit4qxxPXAkYTyQ= diff --git a/src/tsconfig.json b/src/tsconfig.json index a086487d1f..1027a40bae 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -13,7 +13,7 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, - "esModuleInterop": true, + "esModuleInterop": true, "importHelpers": true, "target": "es2015", "module": "esnext", @@ -39,6 +39,7 @@ "@stratosui/cf-autoscaler": ["frontend/packages/cf-autoscaler/src/public_api.ts"], "@example/extensions": ["frontend/packages/example-extensions/src/public-api.ts"], "@susestratos/extensions": ["frontend/packages/suse-extensions/src/public-api.ts"], + "@stratosui/kubernetes": ["frontend/packages/kubernetes/src/public-api.ts"], } } } From 10d58ec9bb5717b2c6107f56e440d5935ee9d45a Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Fri, 2 Oct 2020 14:29:23 +0100 Subject: [PATCH 643/648] Don't run doc actions on forks (#520) * Update internal versions not to use commit IDs * Only run doc actions on main repository --- .github/workflows/documentation-versioning.yml | 1 + .github/workflows/documentation.yml | 2 +- website/internal-versions.json | 3 +-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/documentation-versioning.yml b/.github/workflows/documentation-versioning.yml index 76eab9369b..e0b9b7b4cc 100644 --- a/.github/workflows/documentation-versioning.yml +++ b/.github/workflows/documentation-versioning.yml @@ -6,6 +6,7 @@ on: jobs: update-docs-internal-versions: + if: github.repository == ‘cloudfoundry/stratos’ runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index a705479e7a..32bb02f3fb 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -8,7 +8,7 @@ on: jobs: build-docs: - if: github.event_name != 'push' + if: github.event_name != 'push' && github.repository == ‘cloudfoundry/stratos’ runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 diff --git a/website/internal-versions.json b/website/internal-versions.json index 2635ffaea2..3d39ca9451 100644 --- a/website/internal-versions.json +++ b/website/internal-versions.json @@ -1,4 +1,3 @@ [ - "4.0.1:372682177452d8d:true", - "4.0.0:372682177452d8d:true" + "4.1.0:4.1.0:true" ] From bac6d1c2fe735649e0b484a51c575be013859c41 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Mon, 5 Oct 2020 09:45:00 +0100 Subject: [PATCH 644/648] Fix: Workload shows upgrade when chart has been removed (#515) --- .../workloads/release/tabs/helm-release-helper.service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-helper.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-helper.service.ts index 93dffcc4e4..e1bcedc06b 100644 --- a/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-helper.service.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-helper.service.ts @@ -238,7 +238,9 @@ export class HelmReleaseHelperService { // No newer release, so return the release itself if that is what was requested and we can find the chart // NOTE: If the helm repository is removed that we installed from, we won't be able to find the chart if (returnLatest) { - const releaseChart = charts.find(c => c.relationships.latestChartVersion.data.version === release.chart.metadata.version); + // Need to check that the chart is probably the same + const releaseChart = charts.find(c => this.isProbablySameChart(c.attributes, release.chart.metadata) && + c.relationships.latestChartVersion.data.version === release.chart.metadata.version); if (releaseChart) { return { release, From 8004c2112eaa928714d8fcecd1b23703da9b1852 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Mon, 5 Oct 2020 11:25:30 +0100 Subject: [PATCH 645/648] Fix: YAML editor does not load when installing Stratos Helm Chart (#517) --- .../src/kubernetes/workloads/chart-values-editor/merge.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/merge.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/merge.ts index 39ef4cf042..a1b60d139b 100644 --- a/src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/merge.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/chart-values-editor/merge.ts @@ -13,6 +13,10 @@ export function mergeObjects(src: any, ...dest: any): any { // merge from dest into src function doMergeObjects(src: any, dest: any) { // Go through the keys of dest an update them in src + if (!dest) { + return + } + Object.keys(dest).forEach(key => { if (typeof(dest[key]) === 'object' && !Array.isArray(dest)) { if (!src[key]) { From f511c5fffecc986f8565db9287cdd5c02f362553 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Mon, 5 Oct 2020 11:26:40 +0100 Subject: [PATCH 646/648] First round of improvements to Helm Chart schema (#518) * First round of improvements to Helm Chart schema * Fix lint issue with erroneous comma --- deploy/kubernetes/console/values.schema.json | 60 +++++++++----------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/deploy/kubernetes/console/values.schema.json b/deploy/kubernetes/console/values.schema.json index 34651e77ae..ba85b82386 100644 --- a/deploy/kubernetes/console/values.schema.json +++ b/deploy/kubernetes/console/values.schema.json @@ -2,29 +2,24 @@ "$schema": "http://json-schema.org/schema#", "type": "object", "properties": { - "autoCleanup": { - "type": "boolean" - }, - "configInit": { - "type": "object", - "properties": { - "nodeSelector": { - "type": "object" - } - } - }, "console": { "type": "object", "properties": { + "techPreview": { + "type": "boolean", + "description": "Enable/disable technology preview features" + }, "apiKeysEnabled": { "type": "string", - "enum": ["disabled", "admin_only", "all_users"] + "enum": ["disabled", "admin_only", "all_users"], + "description": "Enable API keys for admins, all users or nobody" }, "autoRegisterCF": { "type": ["string", "null"] }, "backendLogLevel": { - "type": "string" + "type": "string", + "enum": ["debug", "info", "warn", "error"] }, "cookieDomain": { "type": ["string", "null"] @@ -55,7 +50,13 @@ }, "service": { "type": "object", + "description": "Configure the properties of the external service", "properties": { + "type": { + "type": "string", + "description": "Service type", + "enum": ["ClusterIP", "NodePort", "LoadBalancer", "ExternalName"] + }, "annotations": { "type": "object" }, @@ -118,9 +119,6 @@ }, "servicePort": { "type": "integer" - }, - "type": { - "type": "string" } } }, @@ -145,9 +143,6 @@ "statefulSetExtraLabels": { "type": "object" }, - "techPreview": { - "type": "boolean" - }, "templatesConfigMapName": { "type": ["string", "null"] }, @@ -170,8 +165,10 @@ } } }, - "consoleVersion": { - "type": "string" + "imagePullPolicy": { + "type": "string", + "enum": ["Always", "IfNotPresent", "Never"], + "description": "Sets the Image Pull Policy" }, "dockerRegistrySecret": { "type": "string" @@ -211,9 +208,6 @@ } } }, - "imagePullPolicy": { - "type": "string" - }, "images": { "type": "object", "properties": { @@ -338,14 +332,6 @@ } } }, - "services": { - "type": "object", - "properties": { - "loadbalanced": { - "type": "boolean" - } - } - }, "uaa": { "type": "object", "properties": { @@ -366,8 +352,16 @@ } } }, - "useLb": { + "autoCleanup": { "type": "boolean" + }, + "configInit": { + "type": "object", + "properties": { + "nodeSelector": { + "type": "object" + } + } } } } From a9d8639e21147876380a7a4d86e6143c19fe8f53 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Mon, 5 Oct 2020 11:27:20 +0100 Subject: [PATCH 647/648] Helm chat and CI changes for upstreaming (#519) --- deploy/ci/console-dev-releases.yml | 43 ++++++++++++++++--- deploy/kubernetes/build.sh | 8 ++++ .../console/templates/deployment.yaml | 8 ++++ deploy/kubernetes/custom/__stratos.tpl | 13 ------ deploy/kubernetes/custom/custom-build.sh | 23 ---------- deploy/kubernetes/custom/customize-helm.sh | 3 +- 6 files changed, 54 insertions(+), 44 deletions(-) delete mode 100644 deploy/kubernetes/custom/__stratos.tpl delete mode 100644 deploy/kubernetes/custom/custom-build.sh diff --git a/deploy/ci/console-dev-releases.yml b/deploy/ci/console-dev-releases.yml index efcb887ad2..e4af7f2fff 100644 --- a/deploy/ci/console-dev-releases.yml +++ b/deploy/ci/console-dev-releases.yml @@ -1,3 +1,4 @@ +# This pipeline creates an Alpha, Beta or RC release --- resource_types: - name: docker-image @@ -46,7 +47,19 @@ resources: username: ((docker-username)) password: ((docker-password)) repository: ((docker-repository))/stratos-console - +- name: kube-terminal-image + type: docker-image + source: + username: ((docker-username)) + password: ((docker-password)) + repository: ((docker-repository))/stratos-kube-terminal +- name: analyzers-image + type: docker-image + source: + username: ((docker-username)) + password: ((docker-password)) + repository: ((docker-repository))/stratos-analyzers + # Artifacts - name: image-tag type: s3 @@ -129,7 +142,16 @@ jobs: tag: image-tag/v2-alpha-tag patch_base_reg: ((patch-base-reg)) patch_base_tag: ((patch-base-tag)) - - do: + - do: + - put: ui-image + params: + dockerfile: stratos/deploy/Dockerfile.ui + build: stratos/ + target_name: prod-build + tag: image-tag/v2-alpha-tag + build_args_file: image-tag/ui-build-args + patch_base_reg: ((patch-base-reg)) + patch_base_tag: ((patch-base-tag)) - put: config-init-image params: dockerfile: stratos/deploy/Dockerfile.init @@ -137,15 +159,22 @@ jobs: tag: image-tag/v2-alpha-tag patch_base_reg: ((patch-base-reg)) patch_base_tag: ((patch-base-tag)) - - put: ui-image + - do: + - put: analyzers-image params: - dockerfile: stratos/deploy/Dockerfile.ui - build: stratos/ - target_name: prod-build + dockerfile: stratos/src/jetstream/plugins/analysis/container/Dockerfile + build: stratos/src/jetstream/plugins/analysis/container/ tag: image-tag/v2-alpha-tag - build_args_file: image-tag/ui-build-args patch_base_reg: ((patch-base-reg)) patch_base_tag: ((patch-base-tag)) + - put: kube-terminal-image + params: + dockerfile: stratos/deploy/containers/kube-terminal/Dockerfile.kubeterminal + build: stratos/deploy/containers/kube-terminal + tag: image-tag/v2-alpha-tag + patch_base_reg: ((patch-base-reg)) + patch_base_tag: ((patch-base-tag)) + - name: create-chart plan: - get: stratos diff --git a/deploy/kubernetes/build.sh b/deploy/kubernetes/build.sh index 4dc645a607..41ae1ac65a 100755 --- a/deploy/kubernetes/build.sh +++ b/deploy/kubernetes/build.sh @@ -214,6 +214,14 @@ if [ "${CHART_ONLY}" == "false" ]; then log "-- Building/publishing the runtime container image for the Console web server (frontend)" patchAndPushImage stratos-console deploy/Dockerfile.ui "${STRATOS_PATH}" prod-build + # Build and push an image for the Kubernetes Terminal + log "-- Building/publishing Kubernetes Terminal" + patchAndPushImage stratos-kube-terminal Dockerfile.kubeterminal "${STRATOS_PATH}/deploy/containers/kube-terminal" + + # Analzyers container + log "-- Building/publishing Stratos Analyzers" + patchAndPushImage stratos-analyzers Dockerfile "${STRATOS_PATH}/src/jetstream/plugins/analysis/container" + # Build any custom images added by a fork if [ "${HAS_CUSTOM_BUILD}" == "true" ]; then custom_image_build diff --git a/deploy/kubernetes/console/templates/deployment.yaml b/deploy/kubernetes/console/templates/deployment.yaml index 919098add4..f5c61e853f 100644 --- a/deploy/kubernetes/console/templates/deployment.yaml +++ b/deploy/kubernetes/console/templates/deployment.yaml @@ -294,6 +294,14 @@ spec: - name: UI_LIST_ALLOW_LOAD_MAXED value: {{ default "false" .Values.console.ui.listAllowLoadMaxed | quote }} {{- end }} + - name: ANALYSIS_SERVICES_API + value: "http://{{ .Release.Name }}-analyzers:8090" + - name: STRATOS_KUBERNETES_NAMESPACE + value: "{{ .Release.Namespace }}" + - name: STRATOS_KUBERNETES_TERMINAL_IMAGE + value: "{{.Values.kube.registry.hostname}}/{{.Values.kube.organization}}/stratos-kube-terminal:{{.Values.consoleVersion}}" + - name: STRATOS_KUBERNETES_DASHBOARD_IMAGE + value: "{{.Values.console.kubeDashboardImage}}" {{- include "stratosJetstreamEnv" . | indent 8 }} readinessProbe: httpGet: diff --git a/deploy/kubernetes/custom/__stratos.tpl b/deploy/kubernetes/custom/__stratos.tpl deleted file mode 100644 index 653bade6f6..0000000000 --- a/deploy/kubernetes/custom/__stratos.tpl +++ /dev/null @@ -1,13 +0,0 @@ -# Customizations for the Helm Chart - -# Extra env vars for the Jetstream Pod in deployment.yaml -{{- define "stratosJetstreamEnv" }} -- name: ANALYSIS_SERVICES_API - value: "http://{{ .Release.Name }}-analyzers:8090" -- name: STRATOS_KUBERNETES_NAMESPACE - value: "{{ .Release.Namespace }}" -- name: STRATOS_KUBERNETES_TERMINAL_IMAGE - value: "{{.Values.kube.registry.hostname}}/{{.Values.kube.organization}}/stratos-kube-terminal:{{.Values.consoleVersion}}" -- name: STRATOS_KUBERNETES_DASHBOARD_IMAGE - value: "{{.Values.console.kubeDashboardImage}}" -{{- end }} \ No newline at end of file diff --git a/deploy/kubernetes/custom/custom-build.sh b/deploy/kubernetes/custom/custom-build.sh deleted file mode 100644 index 8b413607cf..0000000000 --- a/deploy/kubernetes/custom/custom-build.sh +++ /dev/null @@ -1,23 +0,0 @@ -# This file is included in the build script - -printf "${BOLD}${YELLOW}" -echo -echo "===========================================================================" -echo "SUSE Helm Chart Customization script loaded <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" -echo "===========================================================================" -echo -printf "${RESET}" - -# Script must provide a function named 'custom_image_build' - -function custom_image_build() { - - # Build and push an image for the Kubernetes Terminal - log "-- Building/publishing Kubernetes Terminal" - patchAndPushImage stratos-kube-terminal Dockerfile.kubeterminal "${STRATOS_PATH}/deploy/containers/kube-terminal" - - # Analzyers container - log "-- Building/publishing Stratos Analyzers" - patchAndPushImage stratos-analyzers Dockerfile "${STRATOS_PATH}/src/jetstream/plugins/analysis/container" - -} \ No newline at end of file diff --git a/deploy/kubernetes/custom/customize-helm.sh b/deploy/kubernetes/custom/customize-helm.sh index 674ec4f99a..b5674acf8a 100755 --- a/deploy/kubernetes/custom/customize-helm.sh +++ b/deploy/kubernetes/custom/customize-helm.sh @@ -15,10 +15,11 @@ echo "Customizations folder: ${DIR}" echo "Chart folder : ${CHART_PATH}" echo "" +# Kubernetes has been upstreamed, so no customizations at this time # =========================================================================================== # Copy our customization helper over the default, empty one # =========================================================================================== -cp "${DIR}/__stratos.tpl" "${CHART_PATH}/templates/__stratos.tpl" +#cp "${DIR}/__stratos.tpl" "${CHART_PATH}/templates/__stratos.tpl" # =========================================================================================== # Chart.yaml changes From 41737cac0952e1c2650473214ab003bf1be7a0e6 Mon Sep 17 00:00:00 2001 From: Neil MacDougall Date: Mon, 5 Oct 2020 11:39:00 +0100 Subject: [PATCH 648/648] Fix upgrade not showing when an RC is deployed (#513) * Fix upgrade not showing when an RC is deployed * Fix lint * Lint fix * Move test file * Update helm-release-helper.service.spec.ts * Update helm-release-helper.service.ts --- .../tabs/helm-release-helper.service.spec.ts | 28 +++++++++++++++++++ .../tabs/helm-release-helper.service.ts | 25 +++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-helper.service.spec.ts diff --git a/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-helper.service.spec.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-helper.service.spec.ts new file mode 100644 index 0000000000..94a111d642 --- /dev/null +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-helper.service.spec.ts @@ -0,0 +1,28 @@ +import { Version } from './helm-release-helper.service'; + +describe('HelmReleaseHelperService', () => { + + describe('Version', () => { + + const v10 = new Version('1.0.0'); + const v11 = new Version('1.1.0'); + const v11rc1 = new Version('1.0.0-rc.1'); + const v11rc2 = new Version('1.0.0-rc.2'); + const v201 = new Version('2.0.1'); + const v101 = new Version('1.0.1'); + + it('version comparisons', () => { + expect(v11.isNewer(v10)).toBe(true); + expect(v11rc1.isNewer(v11)).toBe(false); + expect(v11rc2.isNewer(v11rc1)).toBe(true); + expect(v201.isNewer(v11)).toBe(true); + expect(v201.isNewer(v11rc1)).toBe(true); + expect(v10.isNewer(v11)).toBe(false); + expect(v101.isNewer(v11)).toBe(false); + expect(v101.isNewer(v10)).toBe(true); + + expect(v11rc1.isNewer(v10)).toBe(false); + + }); + }); +}); diff --git a/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-helper.service.ts b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-helper.service.ts index e1bcedc06b..5b9c818e19 100644 --- a/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-helper.service.ts +++ b/src/frontend/packages/kubernetes/src/kubernetes/workloads/release/tabs/helm-release-helper.service.ts @@ -19,18 +19,26 @@ import { import { workloadsEntityCatalog } from '../../workloads-entity-catalog'; // Simple class to represent MAJOR.MINOR.REVISION version -class Version { +export class Version { public major: number; public minor: number; public revision: number; + public prerelease: string; + public valid: boolean; constructor(v: string) { this.valid = false; if (typeof v === 'string') { - const parts = v.split('.'); + let version = v; + const pre = v.split('-'); + if (pre.length > 1) { + version = pre[0]; + this.prerelease = pre[1]; + } + const parts = version.split('.'); if (parts.length === 3) { this.major = parseInt(parts[0], 10); this.minor = parseInt(parts[1], 10); @@ -55,6 +63,19 @@ class Version { return true; } if (this.minor === other.minor) { + if (this.revision === other.revision) { + // Same version numbers + if (this.prerelease && !other.prerelease) { + return false; + } + if(!this.prerelease && other.prerelease) { + return true; + } + if (this.prerelease && other.prerelease) { + return this.prerelease > other.prerelease; + } + return false; + } return this.revision > other.revision; } }