From 457685063fd4b960441e482cc8b88fa8c972a7d2 Mon Sep 17 00:00:00 2001 From: Nicholas Soteropoulos Date: Mon, 14 Sep 2020 17:19:10 -0400 Subject: Fix mod ui build issues Change-Id: I4d002645240852a3a5f1964d9ffa2cac11c45b47 Signed-off-by: Nicholas Soteropoulos Issue-ID: DCAEGEN2-2317 Signed-off-by: Nicholas Soteropoulos --- mod2/ui/src/app/app-routing.module.ts | 55 ++ mod2/ui/src/app/app.component.css | 173 ++++++ mod2/ui/src/app/app.component.html | 136 +++++ mod2/ui/src/app/app.component.spec.ts | 90 +++ mod2/ui/src/app/app.component.ts | 218 ++++++++ mod2/ui/src/app/app.module.ts | 172 ++++++ .../ui/src/app/blueprints/blueprints.component.css | 142 +++++ .../src/app/blueprints/blueprints.component.html | 283 ++++++++++ .../app/blueprints/blueprints.component.spec.ts | 153 ++++++ mod2/ui/src/app/blueprints/blueprints.component.ts | 602 +++++++++++++++++++++ .../app/comp-spec-add/comp-spec-add.component.css | 43 ++ .../app/comp-spec-add/comp-spec-add.component.html | 64 +++ .../comp-spec-add/comp-spec-add.component.spec.ts | 77 +++ .../app/comp-spec-add/comp-spec-add.component.ts | 180 ++++++ .../comp-spec-validation.component.css | 73 +++ .../comp-spec-validation.component.html | 108 ++++ .../comp-spec-validation.component.spec.ts | 132 +++++ .../comp-spec-validation.component.ts | 145 +++++ .../ui/src/app/comp-specs/comp-specs.component.css | 121 +++++ .../src/app/comp-specs/comp-specs.component.html | 189 +++++++ .../app/comp-specs/comp-specs.component.spec.ts | 133 +++++ mod2/ui/src/app/comp-specs/comp-specs.component.ts | 211 ++++++++ mod2/ui/src/app/guards/auth.guard.spec.ts | 44 ++ mod2/ui/src/app/guards/auth.guard.ts | 62 +++ mod2/ui/src/app/guards/login.guard.spec.ts | 41 ++ mod2/ui/src/app/guards/login.guard.ts | 52 ++ mod2/ui/src/app/guards/role.guard.spec.ts | 44 ++ mod2/ui/src/app/guards/role.guard.ts | 61 +++ mod2/ui/src/app/home/home.component.css | 50 ++ mod2/ui/src/app/home/home.component.html | 93 ++++ mod2/ui/src/app/home/home.component.ts | 64 +++ mod2/ui/src/app/login/login.component.css | 25 + mod2/ui/src/app/login/login.component.html | 58 ++ mod2/ui/src/app/login/login.component.spec.ts | 59 ++ mod2/ui/src/app/login/login.component.ts | 65 +++ mod2/ui/src/app/material-elevation.directive.ts | 64 +++ .../app/microservices/microservices.component.css | 121 +++++ .../app/microservices/microservices.component.html | 185 +++++++ .../microservices/microservices.component.spec.ts | 134 +++++ .../app/microservices/microservices.component.ts | 311 +++++++++++ mod2/ui/src/app/models/AuthResponse.ts | 28 + mod2/ui/src/app/models/Authority.enum.ts | 22 + mod2/ui/src/app/models/User.ts | 25 + .../app/ms-add-change/ms-add-change.component.css | 57 ++ .../app/ms-add-change/ms-add-change.component.html | 109 ++++ .../ms-add-change/ms-add-change.component.spec.ts | 67 +++ .../app/ms-add-change/ms-add-change.component.ts | 180 ++++++ .../ms-instance-add/ms-instance-add.component.css | 48 ++ .../ms-instance-add/ms-instance-add.component.html | 93 ++++ .../ms-instance-add.component.spec.ts | 71 +++ .../ms-instance-add/ms-instance-add.component.ts | 189 +++++++ .../src/app/msInstances/msInstances.component.css | 148 +++++ .../src/app/msInstances/msInstances.component.html | 221 ++++++++ .../app/msInstances/msInstances.component.spec.ts | 127 +++++ .../src/app/msInstances/msInstances.component.ts | 511 +++++++++++++++++ .../onboarding-tools.component.css | 27 + .../onboarding-tools.component.html | 23 + .../onboarding-tools.component.spec.ts | 48 ++ .../onboarding-tools/onboarding-tools.component.ts | 52 ++ mod2/ui/src/app/register/register.component.css | 18 + mod2/ui/src/app/register/register.component.html | 62 +++ .../ui/src/app/register/register.component.spec.ts | 61 +++ mod2/ui/src/app/register/register.component.ts | 99 ++++ .../reset-password/reset-password.component.css | 18 + .../reset-password/reset-password.component.html | 64 +++ .../reset-password.component.spec.ts | 58 ++ .../app/reset-password/reset-password.component.ts | 78 +++ mod2/ui/src/app/services/auth.service.spec.ts | 43 ++ mod2/ui/src/app/services/auth.service.ts | 95 ++++ .../app/services/base-microservice.service.spec.ts | 42 ++ .../src/app/services/base-microservice.service.ts | 35 ++ mod2/ui/src/app/services/breadcrumb.service.ts | 73 +++ .../src/app/services/comp-spec-add.service.spec.ts | 42 ++ mod2/ui/src/app/services/comp-spec-add.service.ts | 45 ++ .../services/comp-specs-service.service.spec.ts | 43 ++ .../src/app/services/comp-specs-service.service.ts | 43 ++ .../services/deployment-artifact.service.spec.ts | 43 ++ .../app/services/deployment-artifact.service.ts | 71 +++ mod2/ui/src/app/services/download.service.spec.ts | 41 ++ mod2/ui/src/app/services/download.service.ts | 85 +++ .../app/services/global-filters.service.spec.ts | 30 + mod2/ui/src/app/services/global-filters.service.ts | 55 ++ .../app/services/jwt-interceptor.service.spec.ts | 30 + .../ui/src/app/services/jwt-interceptor.service.ts | 40 ++ .../services/microservice-instance.service.spec.ts | 41 ++ .../app/services/microservice-instance.service.ts | 46 ++ mod2/ui/src/app/services/ms-add.service.spec.ts | 42 ++ mod2/ui/src/app/services/ms-add.service.ts | 52 ++ .../app/services/spec-validation.service.spec.ts | 41 ++ .../ui/src/app/services/spec-validation.service.ts | 53 ++ mod2/ui/src/app/services/user.service.spec.ts | 44 ++ mod2/ui/src/app/services/user.service.ts | 53 ++ mod2/ui/src/app/shared/shared-module.ts | 61 +++ .../user-management/user-management.component.css | 54 ++ .../user-management/user-management.component.html | 89 +++ .../user-management.component.spec.ts | 63 +++ .../user-management/user-management.component.ts | 149 +++++ mod2/ui/src/assets/.gitkeep | 0 mod2/ui/src/assets/env.js | 17 + mod2/ui/src/environments/environment.prod.ts | 22 + mod2/ui/src/environments/environment.ts | 35 ++ mod2/ui/src/favicon.ico | Bin 0 -> 5430 bytes mod2/ui/src/index.html | 36 ++ mod2/ui/src/main.ts | 31 ++ mod2/ui/src/polyfills.ts | 81 +++ mod2/ui/src/styles.css | 33 ++ mod2/ui/src/test.ts | 38 ++ 107 files changed, 9474 insertions(+) create mode 100644 mod2/ui/src/app/app-routing.module.ts create mode 100644 mod2/ui/src/app/app.component.css create mode 100644 mod2/ui/src/app/app.component.html create mode 100644 mod2/ui/src/app/app.component.spec.ts create mode 100644 mod2/ui/src/app/app.component.ts create mode 100644 mod2/ui/src/app/app.module.ts create mode 100644 mod2/ui/src/app/blueprints/blueprints.component.css create mode 100644 mod2/ui/src/app/blueprints/blueprints.component.html create mode 100644 mod2/ui/src/app/blueprints/blueprints.component.spec.ts create mode 100644 mod2/ui/src/app/blueprints/blueprints.component.ts create mode 100644 mod2/ui/src/app/comp-spec-add/comp-spec-add.component.css create mode 100644 mod2/ui/src/app/comp-spec-add/comp-spec-add.component.html create mode 100644 mod2/ui/src/app/comp-spec-add/comp-spec-add.component.spec.ts create mode 100644 mod2/ui/src/app/comp-spec-add/comp-spec-add.component.ts create mode 100644 mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.css create mode 100644 mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.html create mode 100644 mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.spec.ts create mode 100644 mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.ts create mode 100644 mod2/ui/src/app/comp-specs/comp-specs.component.css create mode 100644 mod2/ui/src/app/comp-specs/comp-specs.component.html create mode 100644 mod2/ui/src/app/comp-specs/comp-specs.component.spec.ts create mode 100644 mod2/ui/src/app/comp-specs/comp-specs.component.ts create mode 100644 mod2/ui/src/app/guards/auth.guard.spec.ts create mode 100644 mod2/ui/src/app/guards/auth.guard.ts create mode 100644 mod2/ui/src/app/guards/login.guard.spec.ts create mode 100644 mod2/ui/src/app/guards/login.guard.ts create mode 100644 mod2/ui/src/app/guards/role.guard.spec.ts create mode 100644 mod2/ui/src/app/guards/role.guard.ts create mode 100644 mod2/ui/src/app/home/home.component.css create mode 100644 mod2/ui/src/app/home/home.component.html create mode 100644 mod2/ui/src/app/home/home.component.ts create mode 100644 mod2/ui/src/app/login/login.component.css create mode 100644 mod2/ui/src/app/login/login.component.html create mode 100644 mod2/ui/src/app/login/login.component.spec.ts create mode 100644 mod2/ui/src/app/login/login.component.ts create mode 100644 mod2/ui/src/app/material-elevation.directive.ts create mode 100644 mod2/ui/src/app/microservices/microservices.component.css create mode 100644 mod2/ui/src/app/microservices/microservices.component.html create mode 100644 mod2/ui/src/app/microservices/microservices.component.spec.ts create mode 100644 mod2/ui/src/app/microservices/microservices.component.ts create mode 100644 mod2/ui/src/app/models/AuthResponse.ts create mode 100644 mod2/ui/src/app/models/Authority.enum.ts create mode 100644 mod2/ui/src/app/models/User.ts create mode 100644 mod2/ui/src/app/ms-add-change/ms-add-change.component.css create mode 100644 mod2/ui/src/app/ms-add-change/ms-add-change.component.html create mode 100644 mod2/ui/src/app/ms-add-change/ms-add-change.component.spec.ts create mode 100644 mod2/ui/src/app/ms-add-change/ms-add-change.component.ts create mode 100644 mod2/ui/src/app/ms-instance-add/ms-instance-add.component.css create mode 100644 mod2/ui/src/app/ms-instance-add/ms-instance-add.component.html create mode 100644 mod2/ui/src/app/ms-instance-add/ms-instance-add.component.spec.ts create mode 100644 mod2/ui/src/app/ms-instance-add/ms-instance-add.component.ts create mode 100644 mod2/ui/src/app/msInstances/msInstances.component.css create mode 100644 mod2/ui/src/app/msInstances/msInstances.component.html create mode 100644 mod2/ui/src/app/msInstances/msInstances.component.spec.ts create mode 100644 mod2/ui/src/app/msInstances/msInstances.component.ts create mode 100644 mod2/ui/src/app/onboarding-tools/onboarding-tools.component.css create mode 100644 mod2/ui/src/app/onboarding-tools/onboarding-tools.component.html create mode 100644 mod2/ui/src/app/onboarding-tools/onboarding-tools.component.spec.ts create mode 100644 mod2/ui/src/app/onboarding-tools/onboarding-tools.component.ts create mode 100644 mod2/ui/src/app/register/register.component.css create mode 100644 mod2/ui/src/app/register/register.component.html create mode 100644 mod2/ui/src/app/register/register.component.spec.ts create mode 100644 mod2/ui/src/app/register/register.component.ts create mode 100644 mod2/ui/src/app/reset-password/reset-password.component.css create mode 100644 mod2/ui/src/app/reset-password/reset-password.component.html create mode 100644 mod2/ui/src/app/reset-password/reset-password.component.spec.ts create mode 100644 mod2/ui/src/app/reset-password/reset-password.component.ts create mode 100644 mod2/ui/src/app/services/auth.service.spec.ts create mode 100644 mod2/ui/src/app/services/auth.service.ts create mode 100644 mod2/ui/src/app/services/base-microservice.service.spec.ts create mode 100644 mod2/ui/src/app/services/base-microservice.service.ts create mode 100644 mod2/ui/src/app/services/breadcrumb.service.ts create mode 100644 mod2/ui/src/app/services/comp-spec-add.service.spec.ts create mode 100644 mod2/ui/src/app/services/comp-spec-add.service.ts create mode 100644 mod2/ui/src/app/services/comp-specs-service.service.spec.ts create mode 100644 mod2/ui/src/app/services/comp-specs-service.service.ts create mode 100644 mod2/ui/src/app/services/deployment-artifact.service.spec.ts create mode 100644 mod2/ui/src/app/services/deployment-artifact.service.ts create mode 100644 mod2/ui/src/app/services/download.service.spec.ts create mode 100644 mod2/ui/src/app/services/download.service.ts create mode 100644 mod2/ui/src/app/services/global-filters.service.spec.ts create mode 100644 mod2/ui/src/app/services/global-filters.service.ts create mode 100644 mod2/ui/src/app/services/jwt-interceptor.service.spec.ts create mode 100644 mod2/ui/src/app/services/jwt-interceptor.service.ts create mode 100644 mod2/ui/src/app/services/microservice-instance.service.spec.ts create mode 100644 mod2/ui/src/app/services/microservice-instance.service.ts create mode 100644 mod2/ui/src/app/services/ms-add.service.spec.ts create mode 100644 mod2/ui/src/app/services/ms-add.service.ts create mode 100644 mod2/ui/src/app/services/spec-validation.service.spec.ts create mode 100644 mod2/ui/src/app/services/spec-validation.service.ts create mode 100644 mod2/ui/src/app/services/user.service.spec.ts create mode 100644 mod2/ui/src/app/services/user.service.ts create mode 100644 mod2/ui/src/app/shared/shared-module.ts create mode 100644 mod2/ui/src/app/user-management/user-management.component.css create mode 100644 mod2/ui/src/app/user-management/user-management.component.html create mode 100644 mod2/ui/src/app/user-management/user-management.component.spec.ts create mode 100644 mod2/ui/src/app/user-management/user-management.component.ts create mode 100644 mod2/ui/src/assets/.gitkeep create mode 100644 mod2/ui/src/assets/env.js create mode 100644 mod2/ui/src/environments/environment.prod.ts create mode 100644 mod2/ui/src/environments/environment.ts create mode 100644 mod2/ui/src/favicon.ico create mode 100644 mod2/ui/src/index.html create mode 100644 mod2/ui/src/main.ts create mode 100644 mod2/ui/src/polyfills.ts create mode 100644 mod2/ui/src/styles.css create mode 100644 mod2/ui/src/test.ts (limited to 'mod2/ui/src') diff --git a/mod2/ui/src/app/app-routing.module.ts b/mod2/ui/src/app/app-routing.module.ts new file mode 100644 index 0000000..0ffe503 --- /dev/null +++ b/mod2/ui/src/app/app-routing.module.ts @@ -0,0 +1,55 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { NgModule } from '@angular/core'; +import { Routes, RouterModule, NavigationStart, NavigationEnd, Route} from '@angular/router'; +import { HomeComponent } from './home/home.component'; +import { OnboardingToolsComponent } from './onboarding-tools/onboarding-tools.component'; +import { CompSpecsComponent } from './comp-specs/comp-specs.component'; +import { MsInstancesComponent } from './msInstances/msInstances.component'; +import { LoginComponent } from './login/login.component'; +import { RegisterComponent } from './register/register.component'; +import { ResetPasswordComponent } from './reset-password/reset-password.component'; +import { AuthGuard } from './guards/auth.guard'; +import { RoleGuard } from './guards/role.guard'; +import { UserManagementComponent } from './user-management/user-management.component'; +import { LoginGuard } from './guards/login.guard'; +import { MicroservicesComponent } from './microservices/microservices.component'; +import { BlueprintsComponent } from './blueprints/blueprints.component'; +import { CompSpecValidationComponent } from './comp-spec-validation/comp-spec-validation.component'; + +const routes: Routes = [ + { path: '', component: HomeComponent, canActivate:[LoginGuard]}, + { path: 'home', component: HomeComponent, canActivate:[AuthGuard] }, + { path: 'login', component: LoginComponent, canActivate:[LoginGuard]}, + { path: 'register', component: RegisterComponent, canActivate:[AuthGuard, RoleGuard] }, + { path: 'reset-password', component: ResetPasswordComponent, canActivate:[AuthGuard]}, + { path: 'users', component: UserManagementComponent, canActivate:[AuthGuard, RoleGuard]}, + { path: 'OnboardingTools', component: OnboardingToolsComponent, canActivate:[AuthGuard] }, + { path: 'CompSpecs', component: CompSpecsComponent, canActivate:[AuthGuard]}, + { path: 'ms-instances', component: MsInstancesComponent, canActivate:[AuthGuard] }, + { path: 'base-microservices', component: MicroservicesComponent, canActivate: [AuthGuard] }, + { path: 'blueprints', component: BlueprintsComponent, canActivate: [AuthGuard] }, + { path: 'spec-validator', component: CompSpecValidationComponent, canActivate: [AuthGuard] } +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +export class AppRoutingModule { } diff --git a/mod2/ui/src/app/app.component.css b/mod2/ui/src/app/app.component.css new file mode 100644 index 0000000..0b9779a --- /dev/null +++ b/mod2/ui/src/app/app.component.css @@ -0,0 +1,173 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +.sidenav-container { + height: 100%; +} + +.sidenav { + width: 190px; + background: linear-gradient(to bottom, #1D2329 0%, #454B52 8%) +} + +.sidenav .mat-toolbar { + background: inherit; + color:rgb(225, 241, 247); +} + +.subMenuItem{ + color: white; + font-size: 14px; + font-weight: 400; + height: 6vh !important; + min-height: 35px !important; +} + +.mat-toolbar.mat-primary { + display: block; + position: sticky; + top: 0; + z-index: 1; + height: 33px; + position: fixed; + border-radius: 0%; + background: linear-gradient(to bottom, #1D2329 0%, #454B52 135%) +} + +.nav { + font-size: 16px; + font-weight: 500; + background-color: #454B52; + color:white; + height: 7vh !important; + min-height: 40px !important; +} + +*:focus { + outline: none !important; + border: 0 !important; +} + +.is-active{ + background-color: #EA712F !important; +} + +label { + cursor: pointer; +} + +.input{ + padding-top: 20px; +} + +.inputLabel { + font-weight: 600; + margin-left: 25px; + width: 160px; +} + +.inputFieldSm { + width: 200px; + height: 35px; + padding-left: 8px; +} +.inputFieldMed { + width: 300px; + height: 35px; + padding-left: 6px; +} +.inputFieldLg { + width: 400px; + height: 35px; + padding-left: 6px; +} + +/* B R E A D C R U M B S . . . . . . . . */ +.breadcrumb { + list-style: none; + overflow: hidden; + font: 12px Helvetica, Arial, Sans-Serif; + background: transparent; + margin-left: 5px; + margin-top: -3px; + padding: 0; +} +.breadcrumb li { + float: left; +} +.breadcrumb li a { + color: #1D2329; + font-weight: 700; + text-decoration: none; + padding: 6px 0 6px 45px; + background: brown; /* fallback color */ + background: hsla(21,82%,55%,1); + position: relative; + display: block; + float: left; +} +.breadcrumb li a:after { + content: " "; + display: block; + width: 0; + height: 0; + border-top: 50px solid transparent; /* Go big on the size, and let overflow hide */ + border-bottom: 50px solid transparent; + border-left: 30px solid hsla(21,82%,55%,1); + position: absolute; + top: 50%; + margin-top: -50px; + left: 100%; + z-index: 2; +} +.breadcrumb li a:before { + content: " "; + display: block; + width: 0; + height: 0; + border-top: 50px solid transparent; /* Go big on the size, and let overflow hide */ + border-bottom: 50px solid transparent; + border-left: 30px solid #b9bec4; + position: absolute; + top: 50%; + margin-top: -50px; + margin-left: 3px; + left: 100%; + z-index: 1; +} +.breadcrumb li:first-child a { + padding-left: 14px; +} +.breadcrumb li:nth-child(2) a { background: hsla(30,100%,50%,.95); } +.breadcrumb li:nth-child(2) a:after { border-left-color: hsla(30,100%,50%,.95); } +.breadcrumb li:nth-child(3) a { background: hsla(38,100%,50%,.90); } +.breadcrumb li:nth-child(3) a:after { border-left-color: hsla(38,100%,50%,.90); } +.breadcrumb li:nth-child(4) a { background: hsla(45,100%,50%,.85); } +.breadcrumb li:nth-child(4) a:after { border-left-color: hsla(45,100%,50%,.85); } +.breadcrumb li:nth-child(5) a { background: hsla(52,100%,50%,.80); } +.breadcrumb li:nth-child(5) a:after { border-left-color: hsla(52,100%,50%,.80); } +.breadcrumb li:last-child a { + background: transparent !important; + color: black; + pointer-events: none; + cursor: default; + margin-left: -10px; +} +.breadcrumb li:last-child a:after { border: 0; } +.breadcrumb li a:hover { background: rgba(102, 102, 102, 0.993); color: #e9fffd; cursor: pointer} +.breadcrumb li a:hover:after { border-left-color: hsla(0,0%,40%,1); color: #bbc9d8 !important; cursor: pointer} diff --git a/mod2/ui/src/app/app.component.html b/mod2/ui/src/app/app.component.html new file mode 100644 index 0000000..e214dc1 --- /dev/null +++ b/mod2/ui/src/app/app.component.html @@ -0,0 +1,136 @@ + + + + + + +
+ + + + + +
+ + + + + + +
+ + +
+
+ + +
+
+ + + + + MOD + + + + + +
+ +
+
+
+ + + + MOD + + + + + +

Your login has expired. Please log in again.

+ + + +
+ + + + + Reset User Password + +
+ +
+
+ + + + +
+
+ password should be more than 5 characters + +
+
+ + + + +
+
+ {{resetPasswordForm.errors.errMsg}} + +
+   + +
+
+ +
\ No newline at end of file diff --git a/mod2/ui/src/app/app.component.spec.ts b/mod2/ui/src/app/app.component.spec.ts new file mode 100644 index 0000000..d6eb43c --- /dev/null +++ b/mod2/ui/src/app/app.component.spec.ts @@ -0,0 +1,90 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AppComponent } from './app.component'; +import { Ng4LoadingSpinnerModule } from 'ng4-loading-spinner'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatCardModule } from '@angular/material/card'; +import { MatTreeModule } from '@angular/material/tree'; +import { DialogModule } from 'primeng/dialog'; +import { MatListModule } from '@angular/material/list'; +import { LoginComponent } from './login/login.component'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { CardModule } from 'primeng/card'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { JwtHelperService, JwtModule, JWT_OPTIONS } from '@auth0/angular-jwt'; + +describe('AppComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + Ng4LoadingSpinnerModule, + MatSidenavModule, + MatToolbarModule, + MatCardModule, + MatTreeModule, + DialogModule, + MatListModule, + FormsModule, + ReactiveFormsModule, + CardModule, + HttpClientTestingModule, + JwtModule, + ], + declarations: [ + AppComponent, + LoginComponent + ], providers: [ + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }).compileComponents(); + })); + + it('should create the app', () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title 'mod-fe'`, () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app.title).toEqual('mod-fe'); + }); + + it(`should have menu item variables as 'false'`, () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app.showUtilitiesMenu).toEqual(false); + expect(app.utilitiesMenuIconRight).toEqual(true); + }); + + it(`should change utilites menu arrow`, () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + + app.utilitiesMenu() + expect(app.showUtilitiesMenu).toEqual(true); + expect(app.utilitiesMenuIconRight).toEqual(false); + }); +}); \ No newline at end of file diff --git a/mod2/ui/src/app/app.component.ts b/mod2/ui/src/app/app.component.ts new file mode 100644 index 0000000..87d29b4 --- /dev/null +++ b/mod2/ui/src/app/app.component.ts @@ -0,0 +1,218 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Component, HostBinding, OnInit } from '@angular/core'; +import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; +import { Observable } from 'rxjs'; +import { map, shareReplay } from 'rxjs/operators'; +import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree'; +import { FlatTreeControl } from '@angular/cdk/tree'; +import { Event, Router, RouterOutlet, NavigationStart, NavigationEnd, RouterEvent } from '@angular/router'; +import { AuthService } from './services/auth.service'; +import { BreadcrumbService } from './services/breadcrumb.service'; +import { environment } from '../environments/environment'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { UserService } from './services/user.service'; +import { User } from './models/User'; +import { Authority } from './models/Authority.enum'; + +interface TreeNode { + name: string; + children?: TreeNode[]; +} + +const TREE_DATA: TreeNode[] = [ + { + name: 'Microservices', + children: [ + { name: 'Microservices' }, + { name: 'MS Instances' }, + { name: 'Blueprints' }, + { name: 'MOD APIs' } + ] + } +]; + +interface ExampleFlatNode { + expandable: boolean; + name: string; + level: number; +} + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'] +}) +export class AppComponent implements OnInit{ + + @HostBinding('@.disabled') + public animationsDisabled = true; + activeNode: any; + resetPasswordFlag: boolean = false; + resetPasswordForm: FormGroup; + hidePassword: boolean = true; + hideConfirmPassword: boolean = true; + breadcrumbs: any[] = []; + + prepareRoute(outlet: RouterOutlet) { + return outlet && outlet.activatedRouteData && outlet.activatedRouteData['animation']; + } + + title = 'mod-fe'; + right: boolean = true; + down: boolean = false; + + menu_tree(){ + this.right = !this.right; + this.down = !this.down; + } + + isHandset$: Observable = this.breakpointObserver.observe(Breakpoints.Handset) + .pipe( + map(result => result.matches), + shareReplay() + ); + + redirectTo(link){ + window.open(link, "_blank"); + } + + redirectToAPIs() { + window.open(`http://${environment.api_baseURL}:31001/swagger-ui.html#/`, "_blank"); + } + + constructor(private breakpointObserver: BreakpointObserver, private router: Router, public authService: AuthService, + private bread: BreadcrumbService, private fb: FormBuilder, private userService: UserService) { + this.dataSource.data = TREE_DATA; + } + + ngOnInit() { + this.resetPasswordForm = this.fb.group( + { + password: ['', [Validators.minLength(6)]], + confirm_password: '' + }, + {validators: [this.passwordValidator]} + ); + + // Subscribe to breadcrumb changes + this.bread.breadcrumbs$.subscribe( (crumbArray) => {this.breadcrumbs = crumbArray}); + } + + setCrumbs(component, action) { + this.bread.setBreadcrumbs(component, action) + } + + passwordValidator(group: FormGroup) { + if(group.value.password === group.value.confirm_password){ + return null; + } else { + return {errMsg: 'Passwords do not match!'}; + } + } + + private _transformer = (node: TreeNode, level: number) => { + return { + expandable: !!node.children && node.children.length > 0, + name: node.name, + level: level, + }; + } + + tree_handler(name, treenode) { + if (name == "MOD APIs") { + window.open(`http://${environment.api_baseURL}:31001/swagger-ui.html#/`, "_blank"); + } else if (name == "MS Instances") { + this.router.navigate(["ms-instances"]); + this.activeNode = treenode; + } else if(name == "Microservices") { + this.router.navigate(["base-microservices"]); + this.activeNode = treenode; + } else if(name == "Blueprints") { + this.router.navigate(["blueprints"]); + this.activeNode = treenode; + } + + } + + treeControl = new FlatTreeControl( + node => node.level, node => node.expandable); + + treeFlattener = new MatTreeFlattener( + this._transformer, node => node.level, node => node.expandable, node => node.children); + + dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); + + hasChild = (_: number, node: ExampleFlatNode) => node.expandable; + + showMsMenu = false; + msMenuIconRight = true; + msMenu() { + this.showMsMenu = !this.showMsMenu + this.msMenuIconRight = !this.msMenuIconRight + } + + showUtilitiesMenu = false; + utilitiesMenuIconRight = true; + utilitiesMenu(){ + this.showUtilitiesMenu = !this.showUtilitiesMenu + this.utilitiesMenuIconRight = !this.utilitiesMenuIconRight + } + + onConfirm() { + this.authService.reLoginMsg = false; + } + + handleLogout() { + this.showMsMenu = false + this.authService.logout() + } + + handleProfile() { + console.log(this.authService.getUser().roles); + this.resetPasswordFlag = true; + } + + closeResetDialog() { + this.resetPasswordForm.reset(); + this.resetPasswordFlag = false; + } + + submitReset() { + if(this.authService.getUser().roles.includes(Authority.ADMIN)){ + this.userService.editUser(this.authService.getUser().username, this.resetPasswordForm.value as User).subscribe(res=>{ + alert("Password reset successful. Please login in using the new credentials."); + this.authService.logout(); + }, (err)=>{ + alert(err.error.message); + + }); + } else { + this.userService.editProfile(this.authService.getUser().username, this.resetPasswordForm.value as User).subscribe(res=>{ + alert("Password reset successful. Please login in using the new credentials."); + this.authService.logout(); + }, (err)=>{ + alert(err.error.message); + }); + } + this.resetPasswordForm.reset(); + this.resetPasswordFlag = false; + } + +} diff --git a/mod2/ui/src/app/app.module.ts b/mod2/ui/src/app/app.module.ts new file mode 100644 index 0000000..1e1da59 --- /dev/null +++ b/mod2/ui/src/app/app.module.ts @@ -0,0 +1,172 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; + +import { AppRoutingModule } from './app-routing.module'; +import { AppComponent } from './app.component'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { PathLocationStrategy, LocationStrategy} from '@angular/common'; + +import { SharedModule } from './shared/shared-module'; +import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; +import { HomeComponent } from './home/home.component'; +import { LayoutModule } from '@angular/cdk/layout'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatButtonModule } from '@angular/material/button'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { MatIconModule } from '@angular/material/icon'; +import { MatListModule } from '@angular/material/list'; +import { MatTableModule } from '@angular/material/table'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatSortModule } from '@angular/material/sort'; +import { MatTreeModule } from '@angular/material/tree'; +import { MatCardModule } from '@angular/material/card'; +import { MaterialElevationDirective } from './material-elevation.directive'; +import { MatProgressSpinnerModule, MatTooltipModule, MatMenuModule } from '@angular/material'; +import { OnboardingToolsComponent, SafePipe } from './onboarding-tools/onboarding-tools.component'; +import { CompSpecsComponent } from './comp-specs/comp-specs.component'; +import { MatTableExporterModule } from 'mat-table-exporter'; +import { TableModule } from 'primeng/table'; +import { MsInstancesComponent } from './msInstances/msInstances.component'; +import { ButtonModule } from 'primeng/button'; +import { SidebarModule } from 'primeng/sidebar'; +import { MenuModule } from 'primeng/menu'; +import { ToolbarModule } from 'primeng/toolbar'; +import { PanelMenuModule } from 'primeng/panelmenu'; +import { CardModule } from 'primeng/card'; +import { LoginComponent } from './login/login.component'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { RegisterComponent } from './register/register.component'; +import { ResetPasswordComponent } from './reset-password/reset-password.component'; +import { AuthGuard } from './guards/auth.guard'; +import { RoleGuard } from './guards/role.guard'; +import { JwtInterceptorService } from './services/jwt-interceptor.service'; +import { JwtModule } from '@auth0/angular-jwt'; +import { UserManagementComponent } from './user-management/user-management.component'; +import { DialogModule } from 'primeng/dialog'; +import { ToastModule } from 'primeng/toast'; +import { Ng4LoadingSpinnerModule } from 'ng4-loading-spinner'; +import { PasswordModule } from 'primeng/password'; +import { TooltipModule } from 'primeng/tooltip'; +import { AccordionModule } from 'primeng/accordion'; +import { SplitButtonModule } from 'primeng/splitbutton'; +import { DropdownModule } from 'primeng/dropdown'; +import { FileUploadModule } from 'primeng/fileupload'; +import { InputTextareaModule } from 'primeng/inputtextarea'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { RadioButtonModule } from 'primeng/radiobutton'; +import { SelectButtonModule } from 'primeng/selectbutton'; +import { MessageService } from 'primeng/api'; +import { MsAddChangeComponent } from './ms-add-change/ms-add-change.component'; +import { MicroservicesComponent } from './microservices/microservices.component'; +import { PaginatorModule } from 'primeng/paginator'; +import { ScrollPanelModule } from 'primeng/scrollpanel'; +import { CalendarModule } from 'primeng/calendar'; +import { BlueprintsComponent } from './blueprints/blueprints.component'; +import { CompSpecAddComponent } from './comp-spec-add/comp-spec-add.component'; +import { MsInstanceAddComponent } from './ms-instance-add/ms-instance-add.component'; +import {MultiSelectModule} from 'primeng/multiselect'; +import {CheckboxModule} from 'primeng/checkbox'; +import { InputSwitchModule } from 'primeng/inputswitch'; +import { CompSpecValidationComponent } from './comp-spec-validation/comp-spec-validation.component'; + +@NgModule({ + declarations: [ + AppComponent, + HomeComponent, + MaterialElevationDirective, + SafePipe, + OnboardingToolsComponent, + CompSpecsComponent, + MsInstancesComponent, + LoginComponent, + RegisterComponent, + ResetPasswordComponent, + UserManagementComponent, + MsAddChangeComponent, + MicroservicesComponent, + BlueprintsComponent, + CompSpecAddComponent, + MsInstanceAddComponent, + CompSpecValidationComponent + ], + imports: [ + BrowserModule, + AppRoutingModule, + BrowserAnimationsModule, + SharedModule, + HttpClientModule, + LayoutModule, + MatToolbarModule, + MatButtonModule, + MatSidenavModule, + MatIconModule, + MatListModule, + MatTableModule, + MatPaginatorModule, + MatSortModule, + MatTreeModule, + MatCardModule, + MatProgressSpinnerModule, + MatTableExporterModule, + TableModule, + ButtonModule, + SidebarModule, + MenuModule, + ToolbarModule, + PanelMenuModule, + FormsModule, + ReactiveFormsModule, + CardModule, + JwtModule.forRoot({ + config: { + tokenGetter: ()=>localStorage.getItem('jwt') + } + }), + DialogModule, + ToastModule, + Ng4LoadingSpinnerModule, + TooltipModule, + AccordionModule, + SplitButtonModule, + DropdownModule, + FileUploadModule, + InputTextareaModule, + MatExpansionModule, + PasswordModule, + RadioButtonModule, + SelectButtonModule, + MatTooltipModule, + PaginatorModule, + ScrollPanelModule, + MatMenuModule, + CalendarModule, + MultiSelectModule, + CheckboxModule, + InputSwitchModule + ], + providers: [AuthGuard, RoleGuard, { + provide: HTTP_INTERCEPTORS, + useClass: JwtInterceptorService, + multi: true + }, Location, { provide: LocationStrategy, useClass: PathLocationStrategy }, MessageService], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/mod2/ui/src/app/blueprints/blueprints.component.css b/mod2/ui/src/app/blueprints/blueprints.component.css new file mode 100644 index 0000000..ccc03d9 --- /dev/null +++ b/mod2/ui/src/app/blueprints/blueprints.component.css @@ -0,0 +1,142 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +td{ + word-break:break-all; + font-size: 12px; +} + +th{ + font-size: 12px; +} + +.table_column_filter{ + width: 100%; + height: 20px; + font-size: 10px; +} + +.table_div{ + margin: 0px 50px 10px 20px; + min-width: 900px; + width: 98%; + border: 1px solid darkslategray; +} + +.fa-refresh{ + cursor: pointer; +} + +textarea +{ + font-size: 12px; +} + +.row-expand-layout{ + display: grid; + width: 100%; + grid-template-columns: 20% 30% auto 25%; + grid-gap: 10px; + grid-auto-rows: minmax(100px, auto); +} + +.row-expand-card{ + font-size: 12px; + border-radius: 5px; + border: 1px solid slategray; + padding: 10px 5px 5px 10px; + /* This height prevents vertical scroll bars in Notes and Failure Reason */ + height: 92px; + overflow: hidden; +} + +.table_export_buttons_alignment{ + margin-left: 5px; + margin-top: -32px; + float: left; +} + +.table_export_button{ + border-radius: 5px; + height: 22px; + font-size: 14px; + border: none; + margin-top: 4px; + margin-right: 7px; + display: inline-flex; +} + +.table_caption_header{ + margin-left: -18%; + width: 82%; + max-height: 25px; + display: inline-flex; +} + +.table_global_filter{ + width: 250px; + height:25px; + margin-bottom: -5px; + font-size: 12px; + margin-left: 15px; +} + +.table_title{ + width: 40%; + margin-left: 10%; +} + +.table_action_item{ + outline: none; + font-size: 12px; +} + +::ng-deep .mat-menu-content { +padding-top: 0px !important; +padding-bottom: 0px !important; +} +.mat-menu-item{ +line-height:30px; +height:30px; +} + +.greenStatus{ + background-color: rgba(80, 233, 105, 0.616) +} + +.redStatus{ + background-color: rgba(255, 29, 29, 0.527) +} + +.blueStatus{ + background-color: rgba(0, 183, 255, 0.384) +} + +.greyStatus{ + background-color: rgba(150, 150, 150, 0.432) +} + +.ui-toast-detail{ + white-space: pre-wrap; + font-size: 12px; +} + +.ui-state-highlight { + background-color: #878C94 !important; + color: black !important; +} diff --git a/mod2/ui/src/app/blueprints/blueprints.component.html b/mod2/ui/src/app/blueprints/blueprints.component.html new file mode 100644 index 0000000..53fed09 --- /dev/null +++ b/mod2/ui/src/app/blueprints/blueprints.component.html @@ -0,0 +1,283 @@ + + + +
+ + + + + + +
+ +
+ + + + + +
+ +

Deployment Artifacts

+ +
+
+ + + + + + + {{col.header}}
+ + + + Actions + + + + + + + + + + + + + + + + +
+ + + + + +
+ Download +
+ + +
+ +
+ +
+ Delete +
+ + +
+ +
+ +
+ Update +
+ + +
+ +
+ + +
+
+ + +
+ + + + + + + + + + + + +
+ {{bpElem[col.field]}} +
+
{{bpElem[col.field]}}
+ + + + +
+ + + + + +
+ View +
+ + + +
+ Delete +
+ +
+ +
+ +
+ Update +
+ +
+
+ +
+
+
+
+ + +
+ + + + + + +
+ +
+ Created By: {{rowData.metadata.createdBy}}
+ Created On: {{rowData.metadata.createdOn}}
+ Updated By: {{rowData.metadata.updatedBy}}
+ Updated On: {{rowData.metadata.updatedOn}}
+
+ +
+ Notes:
+ +
{{rowData.metadata.notes}}
+
+
+ +
+ Labels:
+
+
+ {{label}} +
+
+
+ +
+ Failure Reason:
+ +
{{rowData.metadata.failureReason}}
+
+
+ +
+ + +
+
+ + +
+ + +
+
+ + + +

{{message.summary}}

+

{{message.detail}}

+
+
+ + + + + + + + +
+ +

{{message.summary}}

+

{{message.detail}}

+
+
+ + +
+
+
+ + + + +
+ +

{{message.summary}}

+ Confirm to delete blueprint(s):

+

{{message.detail}}


+
+
+ + +
+
+
+ + + +
{{BpContentToView}}
+ + + + +
\ No newline at end of file diff --git a/mod2/ui/src/app/blueprints/blueprints.component.spec.ts b/mod2/ui/src/app/blueprints/blueprints.component.spec.ts new file mode 100644 index 0000000..caa5c38 --- /dev/null +++ b/mod2/ui/src/app/blueprints/blueprints.component.spec.ts @@ -0,0 +1,153 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatMenuModule, MatTooltipModule } from '@angular/material'; +import { RouterTestingModule } from '@angular/router/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; +import { Ng4LoadingSpinnerModule } from 'ng4-loading-spinner'; +import { MessageService } from 'primeng/api'; +import { ButtonModule } from 'primeng/button'; +import { DialogModule } from 'primeng/dialog'; +import { DropdownModule } from 'primeng/dropdown'; +import { ScrollPanelModule } from 'primeng/scrollpanel'; +import { TableModule } from 'primeng/table'; +import { ToastModule } from 'primeng/toast'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; + +import { BlueprintsComponent } from './blueprints.component'; + +describe('BlueprintsComponent', () => { + let component: BlueprintsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [BlueprintsComponent], + imports: [ + Ng4LoadingSpinnerModule, + TableModule, + MatMenuModule, + ScrollPanelModule, + ToastModule, + DialogModule, + DropdownModule, + FormsModule, + ReactiveFormsModule, + ButtonModule, + HttpClientTestingModule, + ToastModule, + RouterTestingModule, + MatTooltipModule, + BrowserAnimationsModule + ], + providers: [ + MessageService, + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(BlueprintsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it(`should set states`, async(() => { + const fixture = TestBed.createComponent(BlueprintsComponent); + const app = fixture.debugElement.componentInstance; + let mockStates = [ + 'state1', + 'state2' + ] + app.setMenuStates(mockStates) + fixture.detectChanges(); + expect(app.states).toEqual([ ]); + })); + + it(`should not enable action buttons`, async(() => { + const fixture = TestBed.createComponent(BlueprintsComponent); + const app = fixture.debugElement.componentInstance; + + app.selectedBPs = [] + app.enableButtonCheck() + fixture.detectChanges(); + + expect(app.canDownload).toEqual(false); + expect(app.canUpdate).toEqual(false); + expect(app.canDelete).toEqual(false); + })); + + it(`should enable download/update buttons but not delete`, async(() => { + const fixture = TestBed.createComponent(BlueprintsComponent); + const app = fixture.debugElement.componentInstance; + + app.selectedBPs = [{status: 'TEST'}] + app.enableButtonCheck() + fixture.detectChanges(); + + expect(app.canDownload).toEqual(true); + expect(app.canUpdate).toEqual(true); + expect(app.canDelete).toEqual(false); + })); + + it(`should enable download/update buttons but not delete`, async(() => { + const fixture = TestBed.createComponent(BlueprintsComponent); + const app = fixture.debugElement.componentInstance; + + app.selectedBPs = [{ status: 'IN_DEV' }] + app.enableButtonCheck() + fixture.detectChanges(); + + expect(app.canDownload).toEqual(true); + expect(app.canUpdate).toEqual(true); + expect(app.canDelete).toEqual(true); + })); + + it(`should enable download/update buttons but not delete`, async(() => { + const fixture = TestBed.createComponent(BlueprintsComponent); + const app = fixture.debugElement.componentInstance; + + let mockBpToView = { + tag: 'test-tag', + type: 'k8s', + instanceRelease: '2008', + version: '1', + content: 'test' + } + + app.viewBpContent(mockBpToView) + fixture.detectChanges(); + + expect(app.BpFileNameForDownload).toEqual('test-tag_k8s_2008_1'); + expect(app.BpContentToView).toEqual('test'); + expect(app.showBpContentDialog).toEqual(true); + })); + +}); + + diff --git a/mod2/ui/src/app/blueprints/blueprints.component.ts b/mod2/ui/src/app/blueprints/blueprints.component.ts new file mode 100644 index 0000000..b4a7e73 --- /dev/null +++ b/mod2/ui/src/app/blueprints/blueprints.component.ts @@ -0,0 +1,602 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Component, OnInit, ViewChild, ElementRef, Input, EventEmitter, Output, ChangeDetectorRef } from '@angular/core'; +import { Table } from 'primeng/table'; +import { MessageService } from 'primeng/api'; +import { trigger, state, style, transition, animate } from '@angular/animations'; +import * as saveAs from 'file-saver'; +import * as JSZip from 'jszip'; +import { AuthService } from '../services/auth.service'; +import { DatePipe } from '@angular/common'; +import { DeploymentArtifactService } from '../services/deployment-artifact.service'; +import { Ng4LoadingSpinnerService } from 'ng4-loading-spinner'; +import { Toast } from 'primeng/toast' +import { ActivatedRoute } from '@angular/router'; +import { DownloadService } from '../services/download.service'; + +@Component({ + selector: 'app-blueprints', + templateUrl: './blueprints.component.html', + styleUrls: ['./blueprints.component.css'], + animations: [ + trigger('rowExpansionTrigger', [ + state('void', style({ + transform: 'translateX(-10%)', + opacity: 0 + })), + state('active', style({ + transform: 'translateX(0)', + opacity: 1 + })), + transition('* <=> *', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)')) + ]) + ], + providers: [DatePipe, MessageService] +}) +export class BlueprintsComponent implements OnInit { + @ViewChild(Table, { static: false }) dt: Table; + @ViewChild(Toast, { static: false }) toast: Toast; + + /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. **/ + bpElements: BlueprintElement[] = []; + cols: any[] = [ + { field: 'instanceName', header: 'Instance Name' }, + { field: 'instanceRelease', header: 'Instance Release', width: '7%' }, + { field: 'tag', header: 'Tag' }, + { field: 'type', header: 'Type', width: '7%' }, + { field: 'version', header: 'Version', width: '6%' }, + { field: 'status', header: 'Status', width: '125px' }]; + states: {field: string, label: string}[] = []; + columns: any[]; + filteredRows: any; + downloadItems: { label: string; command: () => void; }[]; + username: string; + showBpContentDialog: boolean = false; + selectedBPs: BlueprintElement[] = []; + // Hides the BP list until the rows are retrieved and filtered + visible = "hidden"; + // These 2 fields are passed from MS Instance to filter the BP list + tag: string; + release: string; + + filteredName: string; + filteredRelease: string; + filteredTag: string; + filteredType: string; + filteredVersion: string; + filteredStatus: string; + + constructor(private change: ChangeDetectorRef, private messageService: MessageService, private authService: AuthService, + private datePipe: DatePipe, private bpApis: DeploymentArtifactService, private spinnerService: Ng4LoadingSpinnerService, + private route: ActivatedRoute, private downloadService: DownloadService) { } + + ngOnInit() { + + this.username = this.authService.getUser().username; + + this.getStates(); + this.getAllBPs(); + + this.change.markForCheck(); + + this.route.queryParams.subscribe((params) => { + this.filteredTag = params['tag']; + this.filteredRelease = params['release']}); + } + + //gets statuses for status updates + getStates(){ + this.states = [] + this.bpApis.getStatuses().subscribe((response) => {this.setMenuStates(response)}) + } + + //fills actions menu with states + setMenuStates(states){ + for(let item of states){ + this.states.push({ + field: item, + label: 'To ' + item + }) + } + } + + canDelete: boolean = false; + canDownload: boolean = false; + canUpdate: boolean = false; + deleteTooltip: string; + enableButtonCheck(){ + if(this.selectedBPs.length > 0){ + this.canDownload = true; + this.canUpdate = true; + + for(let item of this.selectedBPs){ + if (item.status !== 'IN_DEV' && item.status !== 'NOT_NEEDED' && item.status !== 'DEV_COMPLETE'){ + this.canDelete = false; + this.deleteTooltip = 'Only blueprints that are in a status of "In Dev", "Not Needed" or "Dev Complete" can be deleted' + break + } else { + this.canDelete = true; + } + } + + } else { + this.canDownload = false; + this.canUpdate = false; + this.canDelete = false; + this.deleteTooltip = 'No Blueprints Selected' + } + } + + updateStateTo: string = ''; //selected state to update blueprint to + //checks if there are different releases/statuses selected + updateSelectedStatusesCheck(state){ + this.updateStateTo = state.field + let multipleStates: boolean = false + let multipleReleases: boolean = false + let firstStatus = this.selectedBPs[0]['status'] + let firstRelease = this.selectedBPs[0]['instanceRelease'] + + for(let bp of this.selectedBPs){ + if(bp.instanceRelease !== firstRelease){ + multipleReleases = true + } + if (bp.status !== firstStatus) { + multipleStates = true + } + } + + if(multipleReleases && multipleStates){ + this.messageService.add({ key: 'confirmToast', sticky: true, severity: 'warn', summary: 'Are you sure?', detail: 'You are about to update blueprints for different releases and statuses. Confirm to proceed.' }); + } else if (multipleReleases && !multipleStates) { + this.messageService.add({ key: 'confirmToast', sticky: true, severity: 'warn', summary: 'Are you sure?', detail: 'You are about to update blueprints for different releases. Confirm to proceed.' }); + } else if (!multipleReleases && multipleStates) { + this.messageService.add({ key: 'confirmToast', sticky: true, severity: 'warn', summary: 'Are you sure?', detail: 'You are about to update blueprints for different statuses. Confirm to proceed.' }); + } else if (!multipleReleases && !multipleStates){ + this.updateSelectedStatuses() + } + } + onConfirm() { + this.messageService.clear('confirmToast') + this.updateSelectedStatuses() + } + onReject() { + this.messageService.clear('confirmToast') + } + + /* * * * Update status for multiple blueprints * * * */ + successfulStatusUpdates: number = 0 //keeps track of how many status updates were successful + selectionLength: number = 0 //length of array of blueprints with different statuses than update choice + statusUpdateCount: number = 0 //keeps track of how many api calls have been made throughout a loop + statusUpdateErrors: string[] = [] //keeps list of errors + updateSelectedStatuses(){ + this.successfulStatusUpdates = 0 + this.statusUpdateErrors = [] + this.statusUpdateCount = 0 + + let bpsToUpdate = this.selectedBPs.filter(bp => bp.status !== this.updateStateTo) //array of blueprints with different statuses than update choice + this.selectionLength = bpsToUpdate.length; + + if (this.selectionLength === 0) { this.selectedBPs = [] } else { + this.spinnerService.show(); + this.updateState(this.updateStateTo, bpsToUpdate, true) + } + } + + /* * * * Update Statuses * * * */ + //state is the state to update to + //data is the bp data from selection + //multiple is whether updates were called for single blueprint or multiple selected blueprints + updateState(state, data, multiple){ + //single status update + if(!multiple){ + this.bpApis.patchBlueprintStatus(state.field, data['id']).subscribe( + (response: string) => { + data.status = state.field + this.messageService.add({ key: 'statusUpdate', severity: 'success', summary: 'Status Updated' }); + }, errResponse => { + this.statusUpdatesResponseHandler(errResponse, false) + } + ) + } + + //multiple status updates + if(multiple){ + (async () => { + for (let bp of data) { + this.bpApis.patchBlueprintStatus(this.updateStateTo, bp.id).subscribe( + (response: string) => { + bp.status = this.updateStateTo + this.statusUpdatesResponseHandler(null, true) + }, errResponse => { + this.statusUpdatesResponseHandler(errResponse, true) + } + ) + await timeout(1500); + } + })(); + + function timeout(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + } + } + + /* * * * Handles errors and messages for status updates * * * */ + statusUpdatesResponseHandler(response, multiple){ + if(!multiple){ + if(response !== null){ + if (response.error.message.includes('Only 1 blueprint can be in the DEV_COMPLETE state.')) { + let message = response.error.message.replace('Only 1 blueprint can be in the DEV_COMPLETE state. ', '\n\nOnly 1 blueprint can be in the DEV_COMPLETE state.\n') + this.messageService.add({ key: 'statusUpdate', severity: 'error', summary: 'Status Not Updated', detail: message, sticky: true }); + } else { + this.messageService.add({ key: 'statusUpdate', severity: 'error', summary: 'Error Message', detail: response.error.message, sticky: true }); + } + } + } + + if(multiple){ + this.statusUpdateCount++ + if (response === null) { + this.successfulStatusUpdates++ + } else { + if (response.error.message.includes('Only 1 blueprint can be in the DEV_COMPLETE state.')) { + let error = response.error.message.split('Only 1 blueprint can be in the DEV_COMPLETE state.')[0] + this.statusUpdateErrors.push(error) + } else { + this.messageService.add({ key: 'statusUpdate', severity: 'error', summary: 'Error Message', detail: response.error.message, sticky: true }); + } + } + + if (this.statusUpdateCount === this.selectionLength) { + if (this.successfulStatusUpdates > 0) { + this.messageService.add({ key: 'statusUpdate', severity: 'success', summary: `(${this.successfulStatusUpdates} of ${this.selectionLength}) Statuses Updated`, life: 5000 }); + } + if (this.statusUpdateErrors.length > 0) { + let message: string = '' + for (let elem of this.statusUpdateErrors) { + message += '- ' + elem + '\n' + } + message += '\nOnly 1 blueprint can be in the DEV_COMPLETE state.\nChange the current DEV_COMPLETE blueprint to NOT_NEEDED or IN_DEV before changing another to DEV_COMPLETE.' + this.messageService.add({ key: 'statusUpdate', severity: 'error', summary: 'Statuses Not Updated', detail: message, sticky: true }); + } + this.spinnerService.hide() + this.selectedBPs = [] + } + } + } + + bpToDelete: any; + deleteSingle: boolean = false; + rowIndexToDelete; + rowIndexToDeleteFiltered; + warnDeleteBlueprint(data){ + if(data !== null){ + this.deleteSingle = true; + this.rowIndexToDeleteFiltered = this.filteredRows.map(function (x) { return x.id; }).indexOf(data['id']); + this.rowIndexToDelete = this.bpElements.map(function (x) { return x.id; }).indexOf(data['id']); + this.bpToDelete = data; + this.messageService.add({ key: 'confirmDeleteToast', sticky: true, severity: 'warn', summary: 'Are you sure?', detail: `- ${data.instanceName} (v${data.version}) for ${data.instanceRelease}` }); + } else { + this.deleteSingle = false; + this.selectionLength = this.selectedBPs.length; + let warnMessage: string = '' + for(let item of this.selectedBPs){ + warnMessage += `- ${item.instanceName} (v${item.version}) for ${item.instanceRelease}\n` + } + this.messageService.add({ key: 'confirmDeleteToast', sticky: true, severity: 'warn', summary: 'Are you sure?', detail: warnMessage }); + } + } + + resetFilter = false; + onConfirmDelete() { + this.messageService.clear('confirmDeleteToast') + + if (this.filteredName !== '' || this.filteredRelease !== '' || this.filteredTag !== '' || this.filteredType !== '' || this.filteredVersion !== '' || this.filteredStatus !== ''){ + this.resetFilter = true; + } else {this.resetFilter = false} + + if(this.deleteSingle){ + this.bpApis.deleteBlueprint(this.bpToDelete['id']).subscribe(response => { + this.checkBpWasSelected(this.bpToDelete['id']) + this.bpElements.splice(this.rowIndexToDelete, 1) + if (this.resetFilter) { + this.resetFilters() + } + this.messageService.add({ key: 'bpDeleteResponse', severity: 'success', summary: 'Success Message', detail: 'Deployment Artifact Deleted' }); + }, error => { + this.messageService.add({ key: 'bpDeleteResponse', severity: 'error', summary: 'Error Message', detail: error.error.message }); + }) + } else { + for(let item of this.selectedBPs){ + this.bpApis.deleteBlueprint(item.id).subscribe(response => { + this.deleteResponseHandler(true, item.id) + }, error => { + this.messageService.add({ key: 'bpDeleteResponse', severity: 'error', summary: 'Error Message', detail: error.error.message }); + }) + } + } + } + onRejectDelete() { + this.messageService.clear('confirmDeleteToast') + } + + checkBpWasSelected(id){ + if(this.selectedBPs.length > 0){ + for(let item of this.selectedBPs){ + if(item.id === id){ + let indexToDelete = this.selectedBPs.map(function (x) { return x.id; }).indexOf(item['id']); + this.selectedBPs.splice(indexToDelete, 1) + } + } + } + } + + bpsToDelete: string[] = []; + deleteBpCount = 0; + deleteResponseHandler(success, bpToDeleteId){ + this.deleteBpCount++ + if(success){ + this.bpsToDelete.push(bpToDeleteId) + } + if(this.deleteBpCount === this.selectionLength){ + for(let item of this.bpsToDelete){ + + let indexToDelete = this.bpElements.map(function (x) { return x.id; }).indexOf(item); + this.bpElements.splice(indexToDelete, 1) + } + + if(this.resetFilter){ + this.resetFilters() + } + + this.selectedBPs = []; + this.bpsToDelete = []; + this.deleteBpCount = 0; + this.messageService.add({ key: 'bpDeleteResponse', severity: 'success', summary: 'Success Message', detail: 'Deployment Artifacts Deleted' }); + } + } + + resetFilters(){ + let filters: {field: string, value: string}[] = []; + filters.push({field: 'instanceName', value: this.filteredName}) + filters.push({ field: 'instanceRelease', value: this.filteredRelease }) + filters.push({ field: 'tag', value: this.filteredTag }) + filters.push({ field: 'type', value: this.filteredType }) + filters.push({ field: 'version', value: this.filteredVersion }) + filters.push({ field: 'status', value: this.filteredStatus }) + + for(let item of filters){ + this.dt.filter(item.value, item.field, 'contains') + } + } + + /* * * * Gets all blueprints * * * */ + getAllBPs() { + this.spinnerService.show(); + this.bpElements = []; + this.columns = this.cols.map(col => ({ title: col.header, dataKey: col.field })); + + this.visible = "hidden"; + + this.bpApis.getAllBlueprints() + .subscribe((data: any[]) => { + this.fillTable(data) + }) + + } + + /* * * * Checks when table is filtered and stores filtered data in new object to be downloaded when download button is clicked * * * */ + onTableFiltered(values) { + if (values) { + this.filteredRows = values; + } else { + this.filteredRows = this.bpElements + } + } + + /* * * * Download table as excel file * * * */ + exportTable(exportTo) { + let downloadElements: any[] = [] + + for (let row of this.filteredRows) { + let labels; + let notes; + if (exportTo === "excel") { + if (row.metadata.labels !== undefined && row.metadata.labels !== null ) { + labels = row.metadata.labels.join(",") + } + } else { + labels = row.metadata.labels + } + + if (row.metadata.notes !== null && row.metadata.notes !== undefined && row.metadata.notes !== '') { + notes = encodeURI(row.metadata.notes).replace(/%20/g, " ").replace(/%0A/g, "\\n") + } + + downloadElements.push({ + Instance_Name: row.instanceName, + Instance_Release: row.instanceRelease, + Tag: row.tag, + Type: row.type, + Version: row.version, + Status: row.status, + Created_By: row.metadata.createdBy, + Created_On: row.metadata.createdOn, + Updated_By: row.metadata.updatedBy, + Updated_On: row.metadata.updatedOn, + Failure_Reason: row.metadata.failureReason, + Notes: notes, + Labels: labels + }) + } + + let csvHeaders = [] + + if (exportTo === "csv") { + csvHeaders = [ + "Instance_Name", + "Instance_Release", + "Tag", + "Type", + "Version", + "Status", + "Created_By", + "Created_On", + "Updated_By", + "Updated_On", + "Failure_Reason", + "Notes", + "Labels"]; + + } + + this.downloadService.exportTableData(exportTo, downloadElements, csvHeaders) + } + + /* * * * Fills object with blueprint data to be used to fill table * * * */ + fillTable(data) { + let fileName: string; + let tag: string; + let type: string; + + for (let elem of data) { + fileName = elem.fileName; + if(fileName.includes('docker')){ + type = 'docker' + if(fileName.includes('-docker')){ + tag = fileName.split('-docker')[0] + } else if (fileName.includes('_docker')){ + tag = fileName.split('_docker')[0] + } + } else if (fileName.includes('k8s')){ + type = 'k8s' + if (fileName.includes('-k8s')) { + tag = fileName.split('-k8s')[0] + } else if (fileName.includes('_k8s')) { + tag = fileName.split('_k8s')[0] + } + } + + //create temporary bp element to push to array of blueprints + var tempBpElement: BlueprintElement = { + instanceId: elem.msInstanceInfo.id, + instanceName: elem.msInstanceInfo.name, + instanceRelease: elem.msInstanceInfo.release, + id: elem.id, + version: elem.version, + content: elem.content, + status: elem.status, + fileName: fileName, + tag: tag, + type: type, + metadata: { + failureReason: elem.metadata.failureReason, + notes: elem.metadata.notes, + labels: elem.metadata.labels, + createdBy: elem.metadata.createdBy, + createdOn: this.datePipe.transform(elem.metadata.createdOn, 'MM-dd-yyyy HH:mm'), + updatedBy: elem.metadata.updatedBy, + updatedOn: this.datePipe.transform(elem.metadata.updatedOn, 'MM-dd-yyyy HH:mm') + }, + specification: { + id: elem.specificationInfo.id + } + } + + this.bpElements.push(tempBpElement) + } + this.bpElements.reverse(); + this.filteredRows = this.bpElements; + + this.resetFilters(); + + this.visible = "visible"; + this.spinnerService.hide(); + } + + /* * * * Define content to show in bp view dialog pop up * * * */ + BpContentToView: string; + viewBpContent(data){ + this.BpFileNameForDownload = `${data['tag']}_${data['type']}_${data['instanceRelease']}_${data['version']}` + this.BpContentToView = data['content'] + this.showBpContentDialog = true + } + + /* * * * Download single blueprint * * * */ + BpFileNameForDownload: string; + download() { + let file = new Blob([this.BpContentToView], { type: 'text;charset=utf-8' }); + let name: string = this.BpFileNameForDownload + '.yaml' + saveAs(file, name) + } + +/* * * * Download selected blueprints * * * */ + downloadSelectedBps() { + let canDownloadBps: boolean = true; + + //checks if blueprints for multiple releases are selected + let selectedBpRelease: string = this.selectedBPs[0]['instanceRelease']; + for (let bp in this.selectedBPs) { + if (this.selectedBPs[bp]['instanceRelease'] !== selectedBpRelease) { + canDownloadBps = false + break + } + } + + //downloads blueprints to zip file if all selected blueprints are for one release + if (canDownloadBps) { + var zip = new JSZip(); + for (var i in this.selectedBPs) { + zip.file(`${this.selectedBPs[i]['tag']}_${this.selectedBPs[i]['type']}_${this.selectedBPs[i]['instanceRelease']}_${this.selectedBPs[i]['version']}.yaml`, this.selectedBPs[i]['content']) + } + zip.generateAsync({ type: "blob" }).then(function (content) { + saveAs(content, 'Blueprints.zip'); + }); + } else { + this.messageService.add({ key: 'multipleBpReleasesSelected', severity: 'error', summary: 'Error Message', detail: "Cannot download blueprints for different releases" }); + } + + this.selectedBPs = [] + } +} + +export interface BlueprintElement{ + instanceId: string + instanceName: string + instanceRelease: string + id: string + version: string + content: string + status: string + fileName: string + tag: string + type: string + metadata: { + failureReason: string + notes: string + labels: string[] + createdBy: string + createdOn: string + updatedBy: string + updatedOn: string + }, + specification: { + id: string + } +} \ No newline at end of file diff --git a/mod2/ui/src/app/comp-spec-add/comp-spec-add.component.css b/mod2/ui/src/app/comp-spec-add/comp-spec-add.component.css new file mode 100644 index 0000000..54806ab --- /dev/null +++ b/mod2/ui/src/app/comp-spec-add/comp-spec-add.component.css @@ -0,0 +1,43 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +.input { + padding-top: 10px; +} + +.inputLabel { + font-weight: 600; + margin-left: 20px; + width: 135px; +} + +.inputFieldSm { + width: 200px; + height: 35px; + padding-left: 6px; +} +.inputFieldMed { + width: 300px; + height: 35px; + padding-left: 6px; +} +.inputFieldLg { + width: 400px; + height: 35px; + padding-left: 6px; +} diff --git a/mod2/ui/src/app/comp-spec-add/comp-spec-add.component.html b/mod2/ui/src/app/comp-spec-add/comp-spec-add.component.html new file mode 100644 index 0000000..f15f19b --- /dev/null +++ b/mod2/ui/src/app/comp-spec-add/comp-spec-add.component.html @@ -0,0 +1,64 @@ + + + + + +
+ +
+ + +
+ +
+ + +
+ (Separate labels with a space) + +
+ + +
+ +
+ + + +
+ + +
+ + + +
+ + +
+   + +
+
+ + + +
diff --git a/mod2/ui/src/app/comp-spec-add/comp-spec-add.component.spec.ts b/mod2/ui/src/app/comp-spec-add/comp-spec-add.component.spec.ts new file mode 100644 index 0000000..e29a110 --- /dev/null +++ b/mod2/ui/src/app/comp-spec-add/comp-spec-add.component.spec.ts @@ -0,0 +1,77 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { RouterTestingModule } from '@angular/router/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; +import { MessageService } from 'primeng/api'; +import { ButtonModule } from 'primeng/button'; +import { DialogModule } from 'primeng/dialog'; +import { DropdownModule } from 'primeng/dropdown'; +import { ToastModule } from 'primeng/toast'; + + +import { CompSpecAddComponent } from './comp-spec-add.component'; + +describe('CompSpecAddComponent', () => { + let component: CompSpecAddComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [CompSpecAddComponent], + imports: [ + DialogModule, + DropdownModule, + ToastModule, + FormsModule, + ReactiveFormsModule, + ButtonModule, + HttpClientTestingModule, + RouterTestingModule + ], + providers: [ + MessageService, + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CompSpecAddComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it(`should invalidate spec JSON structure`, async(() => { + const fixture = TestBed.createComponent(CompSpecAddComponent); + const app = fixture.debugElement.componentInstance; + let mockErrorJson = "test: 'test}" + app.compSpecContent = mockErrorJson + expect(() => app.validateJsonStructure()).toThrowError('JSON Structure error, quit!') + })); + +}); diff --git a/mod2/ui/src/app/comp-spec-add/comp-spec-add.component.ts b/mod2/ui/src/app/comp-spec-add/comp-spec-add.component.ts new file mode 100644 index 0000000..7564852 --- /dev/null +++ b/mod2/ui/src/app/comp-spec-add/comp-spec-add.component.ts @@ -0,0 +1,180 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { InputTextModule } from 'primeng/inputtext'; +import { DropdownModule } from 'primeng/dropdown'; +import { FormGroup, FormBuilder, Validators, FormControl } from '@angular/forms'; +import { AuthService } from '../services/auth.service'; +import { MessageService } from 'primeng/api'; + +interface Type { + type: string; +} + +@Component({ + selector: 'app-comp-spec-add', + templateUrl: './comp-spec-add.component.html', + styleUrls: ['./comp-spec-add.component.css'] +}) + +export class CompSpecAddComponent implements OnInit { + + compSpecSelected: any; + compSpecContent: any; + + policySelected: any; + policyContent: any; + + csAddForm: FormGroup; + // The loggged in user + username: string; + + // Input form fields + type: string; + labels: string; + notes: string; + specFile: any; + policyJson: any; + + // Dropdowns + types: Type[]; + + // Build JSON as a string + csAddString: any; + // Return JSON to parent component + csAddJson: any; + + constructor(private fb: FormBuilder, private authService: AuthService, private messageService: MessageService) { + } + + @Input() visible: boolean; + @Output() handler: EventEmitter = new EventEmitter(); + + ngOnInit() { + // The logged in user + this.username = this.authService.getUser().username; + + this.csAddForm = new FormGroup({ + type: new FormControl(), + labels: new FormControl(), + notes: new FormControl(), + specFile: new FormControl(), + policyJson: new FormControl() + }); + + // FORM fields and validations + this.csAddForm = this.fb.group({ + type: ['', [Validators.required]], + labels: ['', []], + notes: ['', []], + specFile: ['', []], + policyJson: ['', []] + }); + + // TYPE Dropdown + this.types = [ + { type: 'DOCKER' }, + { type: 'K8S' } + ]; + } + + saveCs() { + this.createOutputJson(); + this.csAddJson = JSON.stringify(this.csAddString); + this.handler.emit(this.csAddJson); + this.closeDialog(); + } + + // Create the JSON to be sent to the parent component + // The "labels" functions below take into account leading/trailing spaces, multiple spaces between labels, and conversion into an array + createOutputJson() { + this.validateJsonStructure(); + + let policy; + if(this.policyContent !== undefined){ + policy = JSON.parse(this.policyContent) + } else { + policy = null + } + + this.csAddString = { + specContent: JSON.parse(this.compSpecContent), + policyJson: policy, + type: this.csAddForm.value['type'].type, + metadata: { + labels: this.csAddForm.value['labels'].trim().replace(/\s{2,}/g, ' ').split(" "), + notes: this.csAddForm.value['notes'] + }, + user: this.username + }; + } + + // Validate, catch, display JSON structure error, and quit! + validateJsonStructure() { + try { + JSON.parse(this.compSpecContent); + } catch (error) { + this.messageService.add({ key: 'jsonError', severity: 'error', summary: 'Invalid Component Spec JSON', detail: error, sticky: true }); + throw new Error('JSON Structure error, quit!'); + } + + if(this.policyContent !== undefined){ + try { + JSON.parse(this.policyContent); + } catch (error) { + this.messageService.add({ key: 'jsonError', severity: 'error', summary: 'Invalid Policy JSON', detail: error, sticky: true }); + throw new Error('JSON Structure error, quit!'); + } + } + } + + // Read the selected Component Spec JSON file + onCompSpecUpload(event) { + this.compSpecSelected = event.target.files[0]; + this.readCsFileContent(this.compSpecSelected); + } + //Read the selected Component Spec JSON file + onPolicyUpload(event) { + this.policySelected = event.target.files[0]; + this.readPolicyFileContent(this.policySelected); + } + + readCsFileContent(file) { + if (file) { + let fileReader = new FileReader(); + fileReader.onload = (e) => { this.compSpecContent = fileReader.result; }; + fileReader.readAsText(file); + } + } + + readPolicyFileContent(file) { + if (file) { + let fileReader = new FileReader(); + fileReader.onload = (e) => { this.policyContent = fileReader.result; }; + fileReader.readAsText(file); + } + } + + // The handler emits 'null' back to parent to close dialog and make it available again when clicked + closeDialog() { + this.visible = false; + this.handler.emit(null); + } + +} diff --git a/mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.css b/mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.css new file mode 100644 index 0000000..d76e0fa --- /dev/null +++ b/mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.css @@ -0,0 +1,73 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +.validator-input-card{ + width: 40%; + min-width: 430px; + min-height: 100%; + border: 2px solid slategray; + border-radius: 5px; + background-color: white; + padding: 1%; +} +.validator-output-card{ + width: 58%; + border: 2px solid slategray; + margin-left: 2%; + min-height: 100%; + border-radius: 5px; + background-color: white; + padding: 1%; +} + +.input-section-card{ + font-size: 14px; + border: solid slategray; + border-style: double; + margin-top: 7px +} + +.validateSpec{ + margin-top: 10px; +} +.downloadSchema{ + margin-top: 80px; +} + +.type-selection{ + position: absolute; + background-color: rgba(128, 128, 128, 0.15); + width: 190px; + height: 40px; + display: inline-flex; + align-items: center; + margin-left: 150px; +} + +.greenOutput{ + background-color: rgba(0, 255, 0, 0.205); + height: 40px; + padding: 3px; + border: 2px solid rgba(0, 66, 0, 0.747); +} +.redOutput{ + background-color: rgba(255, 0, 0, 0.267); + height: 87%; + padding: 10px; + border: 2px solid rgba(65, 0, 0, 0.712); +} diff --git a/mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.html b/mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.html new file mode 100644 index 0000000..9867364 --- /dev/null +++ b/mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.html @@ -0,0 +1,108 @@ + + +
+

Component Specification Validation

+
+
+
+ + Action*
+
+
+   Validate Spec File +
+
+   Download Schema +
+
+
+ + + Release*
+
+
+   2007+ +
+
+
+ + + Type*
+
+
+   K8s +
+
+
+   Docker +
+
+
+ + + +
+ Component Spec File*
+ +
+ + + +
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+ +
+ Output:

+
+
+ {{specValidationOutputHeader}} +
+ +
+ +
Summary:
{{specValidationOutputSummary}}
+
Message(s):
{{specValidationOutputMessage}}
+
+
+ +
+
+
+
+
\ No newline at end of file diff --git a/mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.spec.ts b/mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.spec.ts new file mode 100644 index 0000000..a9efe28 --- /dev/null +++ b/mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.spec.ts @@ -0,0 +1,132 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { RadioButtonModule } from 'primeng/radiobutton'; + +import { CompSpecValidationComponent } from './comp-spec-validation.component'; +import { FormsModule } from '@angular/forms'; +import { MatCardModule, MatTooltipModule } from '@angular/material'; +import { ScrollPanelModule } from 'primeng/scrollpanel'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { MessageService } from 'primeng/api'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; +import { Ng4LoadingSpinnerModule } from 'ng4-loading-spinner'; + +describe('CompSpecValidationComponent', () => { + let component: CompSpecValidationComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CompSpecValidationComponent ], + imports: [ + RadioButtonModule, + FormsModule, + MatCardModule, + MatTooltipModule, + ScrollPanelModule, + HttpClientTestingModule, + Ng4LoadingSpinnerModule + ], + providers: [ + MessageService, + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CompSpecValidationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it(`should have as shouldValidate 'false'`, () => { + const fixture = TestBed.createComponent(CompSpecValidationComponent); + const app = fixture.debugElement.componentInstance; + expect(app.shouldValidate).toEqual(false); + }); + + it(`should change shouldValidate to 'true'`, async(() => { + const fixture = TestBed.createComponent(CompSpecValidationComponent); + const app = fixture.debugElement.componentInstance; + app.validateRadioButton() + fixture.detectChanges(); + expect(app.shouldValidate).toEqual(true); + })); + + it(`should have as shouldDownload 'false'`, () => { + const fixture = TestBed.createComponent(CompSpecValidationComponent); + const app = fixture.debugElement.componentInstance; + expect(app.shouldDownload).toEqual(false); + }); + + it(`should change shouldDownload to 'true'`, async(() => { + const fixture = TestBed.createComponent(CompSpecValidationComponent); + const app = fixture.debugElement.componentInstance; + app.downloadRadioButton() + fixture.detectChanges(); + expect(app.shouldDownload).toEqual(true); + })); + + it(`should set validation error message`, async(() => { + const fixture = TestBed.createComponent(CompSpecValidationComponent); + const app = fixture.debugElement.componentInstance; + let mockSuccess = { + status: 200 + } + app.setSpecValidationMessage(mockSuccess) + fixture.detectChanges(); + expect(app.validCompSpec).toEqual(true) + })); + + it(`should set validation error message`, async(() => { + const fixture = TestBed.createComponent(CompSpecValidationComponent); + const app = fixture.debugElement.componentInstance; + + let mockError = { + status: 400, + error: { + summary: 'Test', + errors: [ + 'error1', + 'error2' + ] + } + } + app.setSpecValidationMessage(mockError) + fixture.detectChanges(); + expect(app.validCompSpec).toEqual(false) + })); + + it(`should invalidate JSON structure`, async(() => { + const fixture = TestBed.createComponent(CompSpecValidationComponent); + const app = fixture.debugElement.componentInstance; + let mockErrorJson = "test: 'test}" + app.compSpecContent = mockErrorJson + expect(() => app.validateJsonStructure()).toThrowError('JSON Structure error, quit!') + })); + +}); diff --git a/mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.ts b/mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.ts new file mode 100644 index 0000000..2ce8fef --- /dev/null +++ b/mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.ts @@ -0,0 +1,145 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; +import { SpecValidationService } from '../services/spec-validation.service'; +import { DownloadService } from '../services/download.service'; +import { Ng4LoadingSpinnerService } from 'ng4-loading-spinner'; + +@Component({ + selector: 'app-comp-spec-validation', + templateUrl: './comp-spec-validation.component.html', + styleUrls: ['./comp-spec-validation.component.css'] +}) +export class CompSpecValidationComponent implements OnInit { + + @ViewChild('myFile', {static: false}) + myInputVariable: ElementRef; + + spec_validator_action; + release = ''; + type = ''; + shouldValidate = false; + shouldDownload = false; + validCompSpec = false; + specValidated = false; + specValidationOutputSummary: any; + + constructor(private specValidator: SpecValidationService, private downloadService: DownloadService, private spinnerService: Ng4LoadingSpinnerService ) { } + + ngOnInit() { + + } + + compSpecSelected: any; + onCompSpecUpload(event){ + this.compSpecSelected = event.target.files[0]; + this.readCsFileContent(this.compSpecSelected); + } + + compSpecContent: any = null; + readCsFileContent(file) { + if (file) { + let fileReader = new FileReader(); + fileReader.onload = (e) => { this.compSpecContent = fileReader.result; }; + fileReader.readAsText(file); + } + } + + validateRadioButton(){ + this.shouldValidate = true; + this.shouldDownload = false; + } + + downloadRadioButton(){ + this.shouldValidate = false; + this.shouldDownload = true; + this.compSpecContent = null; + this.specValidated = false + } + + resetFile(){ + this.myInputVariable.nativeElement.value = ""; + this.compSpecContent = null + this.compSpecSelected = null + this.specValidated = false + } + + specValidationOutputHeader = '' + specValidationOutputMessage = ''; + validateSpec(){ + this.specValidationOutputHeader = "" + this.specValidationOutputMessage = "" + this.specValidationOutputSummary = "" + + this.spinnerService.show() + this.validateJsonStructure() + this.specValidator.sendSpecFile(this.compSpecContent, this.type, this.release).subscribe( + res => {}, err => { + this.setSpecValidationMessage(err) + } + ) + } + + setSpecValidationMessage(res){ + if(res.status === 200){ + this.specValidationOutputHeader = "Success: Valid Component Spec" + this.specValidationOutputMessage = "" + this.validCompSpec = true + } else { + this.specValidationOutputHeader = `${res.status} Error: Invalid Component Spec` + this.specValidationOutputSummary = res.error.summary + + for(let item of res.error.errors){ + this.specValidationOutputMessage += `- ${item}\n\n` + } + + this.validCompSpec = false + } + + this.specValidated = true; + this.spinnerService.hide() + } + + validateJsonStructure() { + try { + JSON.parse(this.compSpecContent); + } catch (error) { + this.specValidationOutputHeader = "Error: Invalid Component Spec" + this.specValidationOutputSummary = "JSON Structure Error" + this.specValidationOutputMessage = error + this.validCompSpec = false + this.specValidated = true + this.spinnerService.hide() + throw new Error('JSON Structure error, quit!'); + } + } + + downloadSchema(){ + this.spinnerService.show() + this.specValidator.getSchema(this.type).subscribe( + res => { + this.downloadService.downloadJSON(res, `${this.release}+_${this.type}_Schema`) + this.spinnerService.hide() + }, err => { + console.log(err) + this.spinnerService.hide() + } + ) + } +} diff --git a/mod2/ui/src/app/comp-specs/comp-specs.component.css b/mod2/ui/src/app/comp-specs/comp-specs.component.css new file mode 100644 index 0000000..5f86e73 --- /dev/null +++ b/mod2/ui/src/app/comp-specs/comp-specs.component.css @@ -0,0 +1,121 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +td{ + word-break:break-all +} + +textarea +{ + font-size: 12px; +} + +.table_actions_button{ + background-color: transparent; + border: none; + width: 20px; + height: 20px; + vertical-align: middle; +} + +.row-expand-layout{ + display: grid; + grid-template-columns: 30% 40% auto; + grid-gap: 10px; + grid-auto-rows: minmax(100px, auto); +} + +.row-expand-card{ + font-size: 12px; + grid-row: 1; + border-radius: 5px; + border: 1px solid slategray; + padding: 10px; + /* This height prevents vertical scroll bar in Notes */ + height: 92px; + overflow: hidden; +} + +label { + cursor: pointer; +} + +.fa-refresh{ + cursor: pointer; +} + +.input{ + padding-top: 10px; +} + +.inputLabel { + font-weight: 600; + margin-left: 20px; + width: 140px; +} + +.inputFieldSm { + width: 200px; + height: 35px; + padding-left: 6px; +} +.inputFieldMed { + width: 300px; + height: 35px; + padding-left: 6px; +} +.inputFieldLg { + width: 400px; + height: 35px; + padding-left: 6px; +} + +.table_action_item{ + outline: none; + font-size: 12px; +} + +::ng-deep .mat-menu-content { +padding-top: 0px !important; +padding-bottom: 0px !important; +} +.mat-menu-item{ +line-height:30px; +height:30px; +} + +.greenStatus{ + background-color: rgba(80, 233, 105, 0.87) +} + +.redStatus{ + background-color: rgba(255, 29, 29, 0.733) +} + +.blueStatus{ + background-color: rgba(0, 183, 255, 0.432) +} + +.greyStatus{ + background-color: rgba(150, 150, 150, 0.432) +} + +.ui-state-highlight { + background-color: #878C94 !important; + color: black !important; +} diff --git a/mod2/ui/src/app/comp-specs/comp-specs.component.html b/mod2/ui/src/app/comp-specs/comp-specs.component.html new file mode 100644 index 0000000..3061cdf --- /dev/null +++ b/mod2/ui/src/app/comp-specs/comp-specs.component.html @@ -0,0 +1,189 @@ + + +
+ + + +
+ +
+ + + + + +
+ +

Component Specs

+ +
+
+ + + + + + {{col.header}}
+ + + + Actions + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ {{csElem[col.field]}} +
+
{{csElem[col.field]}}
+ + + + +
+ + + +
+ View +
+ + +
+ +
+ +
+
+ + +
+ + + + + +
+ +
+ Created By: {{rowData.metadata.createdBy}}
+ Created On: {{rowData.metadata.createdOn}}
+ Updated By: {{rowData.metadata.updatedBy}}
+ Updated On: {{rowData.metadata.updatedOn}} +
+ +
+ Notes:
+ +
{{rowData.metadata.notes}}
+
+
+ +
+ Labels:
+
+
+ {{label}} +
+
+
+
+ + +
+
+ + +
+ + +
+
+ + + +
{{specContentToView | json}}
+ + + + +
+ + + +
{{policyJsonToView | json}}
+ + + + +
diff --git a/mod2/ui/src/app/comp-specs/comp-specs.component.spec.ts b/mod2/ui/src/app/comp-specs/comp-specs.component.spec.ts new file mode 100644 index 0000000..64405e3 --- /dev/null +++ b/mod2/ui/src/app/comp-specs/comp-specs.component.spec.ts @@ -0,0 +1,133 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientModule } from '@angular/common/http'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatMenuModule, MatTooltipModule } from '@angular/material'; +import { RouterTestingModule } from '@angular/router/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; +import { Ng4LoadingSpinnerModule } from 'ng4-loading-spinner'; +import { MessageService } from 'primeng/api'; +import { ButtonModule } from 'primeng/button'; +import { DialogModule } from 'primeng/dialog'; +import { DropdownModule } from 'primeng/dropdown'; +import { ScrollPanelModule } from 'primeng/scrollpanel'; +import { TableModule } from 'primeng/table'; +import { ToastModule } from 'primeng/toast'; + +import { CompSpecsComponent } from './comp-specs.component'; + +describe('CompSpecsComponent', () => { + let component: CompSpecsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + CompSpecsComponent + ], + imports: [ + Ng4LoadingSpinnerModule, + TableModule, + MatMenuModule, + ScrollPanelModule, + ToastModule, + DialogModule, + DropdownModule, + FormsModule, + ReactiveFormsModule, + ButtonModule, + HttpClientModule, + ToastModule, + RouterTestingModule, + MatTooltipModule + ], + providers: [ + MessageService, + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CompSpecsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it(`should fill csElements Object`, () => { + const fixture = TestBed.createComponent(CompSpecsComponent); + const app = fixture.debugElement.componentInstance; + + let mockCsElement = [{ + id: 'testId1234', + name: 'test-MS', + type: 'k8s', + specContent: '', + policyJson: 'test', + status: 'New', + msInstanceInfo: { + release: '2008', + name: 'test Ms', + }, + metadata: { + createdBy: 'test', + createdOn: '01-01-2020 12:00', + updatedBy: 'test', + updatedOn: '01-01-2020 12:00', + notes: 'test', + labels: ['test'], + } + }] + + app.fillTable(mockCsElement) + + expect(app.loadTable).toEqual(true); + expect(app.csElements.length).toEqual(1); + }); + + it(`should set spec content to view`, () => { + const fixture = TestBed.createComponent(CompSpecsComponent); + const app = fixture.debugElement.componentInstance; + let mockData = { + specContent: 'test' + } + app.showViewCsDialog(mockData) + expect(app.showViewCs).toEqual(true); + expect(app.specContentToView).toEqual('test'); + }); + + it(`should set policy json content to view`, () => { + const fixture = TestBed.createComponent(CompSpecsComponent); + const app = fixture.debugElement.componentInstance; + let mockData = { + policyJson: 'test' + } + app.showViewPolicyDialog(mockData) + expect(app.showViewPolicy).toEqual(true); + expect(app.policyJsonToView).toEqual('test'); + }); +}); + diff --git a/mod2/ui/src/app/comp-specs/comp-specs.component.ts b/mod2/ui/src/app/comp-specs/comp-specs.component.ts new file mode 100644 index 0000000..4327c0b --- /dev/null +++ b/mod2/ui/src/app/comp-specs/comp-specs.component.ts @@ -0,0 +1,211 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; +import { compSpecsService } from '../services/comp-specs-service.service'; +import { Table } from 'primeng/table'; +import { MessageService } from 'primeng/api'; +import { Ng4LoadingSpinnerService } from 'ng4-loading-spinner'; +import { DatePipe } from '@angular/common'; +import { trigger, state, transition, style, animate } from '@angular/animations'; +import { ActivatedRoute } from '@angular/router'; +import { DownloadService } from '../services/download.service'; + +@Component({ + selector: 'app-comp-specs', + templateUrl: './comp-specs.component.html', + styleUrls: ['./comp-specs.component.css'], + animations: [ + trigger('rowExpansionTrigger', [ + state('void', style({ + transform: 'translateX(-10%)', + opacity: 0 + })), + state('active', style({ + transform: 'translateX(0)', + opacity: 1 + })), + transition('* <=> *', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)')) + ]) + ], + providers: [DatePipe] +}) +export class CompSpecsComponent implements OnInit { + @ViewChild(Table, { static: false }) dt: Table; + + csElements: any[] = []; + + cols: any[] = [ + { field: 'instanceName', header: 'Instance Name' }, + { field: 'release', header: 'Release', width: '15%' }, + { field: 'type', header: 'Type', width: '15%' }, + { field: 'policy', header: 'Policy', width: '15%' }, + { field: 'status', header: 'Status', width: '15%' } + ]; + + columns: any[]; + loadTable: boolean; + filteredRows: any; + summaryRows: any; + downloadItems: { label: string; command: () => void; }[]; + + msInstanceId: string; + msInstanceName: any; + msInstanceRelease: any; + + constructor(private csApis: compSpecsService, private messageService: MessageService, + private spinnerService: Ng4LoadingSpinnerService, private datePipe: DatePipe, + private route: ActivatedRoute, private downloadService: DownloadService) { } + + //create table of comp specs + ngOnInit() { + this.loadTable = false; + + this.route.queryParams.subscribe((params) => { + this.msInstanceId = params['instanceId']; + }); + + this.getAllCs() + } + + getAllCs() { + + this.csElements = []; + + this.csApis.getAllCompSpecs(this.msInstanceId) + .subscribe((data: any[]) => { + this.fillTable(data) + }) + + this.columns = this.cols.map(col => ({ title: col.header, dataKey: col.field })); + } + + //filter table + onTableFiltered(values) { + if (values) { this.filteredRows = values; } + else { this.filteredRows = this.summaryRows; } + } + + /* * * * Export ms instance table to excel or csv * * * */ + exportTable(exportTo) { + let downloadElements: any[] = [] + + //labels array not handled well by excel download so converted them to a single string + for (let row of this.filteredRows) { + let labels; + let notes; + if (exportTo === "excel") { + if (row.metadata.labels !== undefined) { + labels = row.metadata.labels.join(",") + } + } else { + labels = row.metadata.labels + } + + if (row.metadata.notes !== null && row.metadata.notes !== undefined && row.metadata.notes !== '') { + notes = encodeURI(row.metadata.notes).replace(/%20/g, " ").replace(/%0A/g, "\\n") + } + + downloadElements.push({ + Instance_Name: row.instanceName, + Release: row.release, + Type: row.type, + Status: row.status, + Created_By: row.metadata.createdBy, + Created_On: row.metadata.createdOn, + Updated_By: row.metadata.updatedBy, + Updated_On: row.metadata.updatedOn, + Notes: notes, + Labels: labels + }) + } + + let arrHeader = [] + + if (exportTo === "csv") { + arrHeader = [ + "Instance_Name", + "Release", + "Type", + "Status", + "Created_By", + "Created_On", + "Updated_By", + "Updated_On", + "Notes", + "Labels" + ]; + } + + this.downloadService.exportTableData(exportTo, downloadElements, arrHeader) + } + + //fill object with microservice data, to be used to fill table. + //checks if fields are empty and if they are, store 'N/A' as the values + fillTable(data) { + for (let elem of data) { + let policy = ''; + if(elem.policyJson){policy = "Included"} + + let tempCsElement: any = { + id: elem.id, + instanceName: elem.msInstanceInfo.name, + release: elem.msInstanceInfo.release, + type: elem.type, + policy: policy, + status: elem.status, + specContent: elem.specContent, + policyJson: elem.policyJson, + metadata: { + createdBy: elem.metadata.createdBy, + createdOn: this.datePipe.transform(elem.metadata.createdOn, 'MM-dd-yyyy HH:mm'), + updatedBy: elem.metadata.updatedBy, + updatedOn: this.datePipe.transform(elem.metadata.updatedOn, 'MM-dd-yyyy HH:mm'), + notes: elem.metadata.notes, + labels: elem.metadata.labels + } + } + this.csElements.unshift(tempCsElement) + } + this.msInstanceName = this.csElements[0]['instanceName'] + this.msInstanceRelease = this.csElements[0]['release'] + this.filteredRows = this.csElements + this.loadTable = true; + this.spinnerService.hide(); + } + + showViewCs: boolean = false; + specContentToView: string; + showViewCsDialog(data) { + this.showViewCs = true; + this.specContentToView = data.specContent; + } + + showViewPolicy: boolean = false; + policyJsonToView: string; + showViewPolicyDialog(data) { + this.showViewPolicy = true; + this.policyJsonToView = data.policyJson; + } + + /* * * * Download single spec file or policy * * * */ + download(content, contentType) { + let fileName = `${this.msInstanceName}_${this.msInstanceRelease}_${contentType}` + this.downloadService.downloadJSON(content, fileName) + } +} \ No newline at end of file diff --git a/mod2/ui/src/app/guards/auth.guard.spec.ts b/mod2/ui/src/app/guards/auth.guard.spec.ts new file mode 100644 index 0000000..c82f8d6 --- /dev/null +++ b/mod2/ui/src/app/guards/auth.guard.spec.ts @@ -0,0 +1,44 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TestBed, async, inject } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; + +import { AuthGuard } from './auth.guard'; + +describe('AuthGuard', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + AuthGuard, + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ], + imports: [ + HttpClientTestingModule, + RouterTestingModule + ] + }); + }); + + it('should ...', inject([AuthGuard], (guard: AuthGuard) => { + expect(guard).toBeTruthy(); + })); +}); diff --git a/mod2/ui/src/app/guards/auth.guard.ts b/mod2/ui/src/app/guards/auth.guard.ts new file mode 100644 index 0000000..cce544b --- /dev/null +++ b/mod2/ui/src/app/guards/auth.guard.ts @@ -0,0 +1,62 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Injectable } from '@angular/core'; +import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router'; +import { Observable, of } from 'rxjs'; +import { AuthService } from '../services/auth.service'; +import { catchError, map } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthGuard implements CanActivate { + + constructor( + private authService: AuthService, + private router: Router + ) {} + + canActivate( + next: ActivatedRouteSnapshot, + state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { + + return this.authService.checkLogin().pipe(map((e:boolean)=>{ + if(e){ + this.authService.setUser(); + if(this.authService.getUser().roles.includes("ROLE_ADMIN")){ + this.authService.isAdmin = true; + }else{ + this.authService.isAdmin = false; + } + this.authService.authPass=true; + return true; + } + }), catchError(err=>{ + this.authService.reLoginMsg = true; + // window.alert("Your login has expired. Please log in again."); + this.authService.authPass=false; + this.router.navigate(['/login']); + return of(false); + })) + } + + + + +} diff --git a/mod2/ui/src/app/guards/login.guard.spec.ts b/mod2/ui/src/app/guards/login.guard.spec.ts new file mode 100644 index 0000000..d58450c --- /dev/null +++ b/mod2/ui/src/app/guards/login.guard.spec.ts @@ -0,0 +1,41 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TestBed, async, inject } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; + +import { LoginGuard } from './login.guard'; + +describe('LoginGuard', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + LoginGuard, + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ], + imports: [HttpClientTestingModule, RouterTestingModule] + }); + }); + + it('should ...', inject([LoginGuard], (guard: LoginGuard) => { + expect(guard).toBeTruthy(); + })); +}); diff --git a/mod2/ui/src/app/guards/login.guard.ts b/mod2/ui/src/app/guards/login.guard.ts new file mode 100644 index 0000000..851ef61 --- /dev/null +++ b/mod2/ui/src/app/guards/login.guard.ts @@ -0,0 +1,52 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Injectable } from '@angular/core'; +import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router'; +import { Observable, of } from 'rxjs'; +import { AuthService } from '../services/auth.service'; +import { catchError, map } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class LoginGuard implements CanActivate { + constructor( + private authService: AuthService, + private router: Router + ) {} + + canActivate( + next: ActivatedRouteSnapshot, + state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { + + return this.authService.checkLogin().pipe(map((e:boolean)=>{ + if(e){ + console.log("Hi this is loginguard"); + this.authService.authPass=true; + this.router.navigate(['/home']); + return true; + } + }), catchError(err=>{ + console.log("Login guard out"); + this.authService.authPass=false; + this.router.navigate(['/login']); + return of(false); + })) +} +} diff --git a/mod2/ui/src/app/guards/role.guard.spec.ts b/mod2/ui/src/app/guards/role.guard.spec.ts new file mode 100644 index 0000000..c57dfe6 --- /dev/null +++ b/mod2/ui/src/app/guards/role.guard.spec.ts @@ -0,0 +1,44 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TestBed, async, inject } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; + +import { RoleGuard } from './role.guard'; + +describe('RoleGuard', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + RoleGuard, + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ], + imports: [ + HttpClientTestingModule, + RouterTestingModule + ] + }); + }); + + it('should ...', inject([RoleGuard], (guard: RoleGuard) => { + expect(guard).toBeTruthy(); + })); +}); diff --git a/mod2/ui/src/app/guards/role.guard.ts b/mod2/ui/src/app/guards/role.guard.ts new file mode 100644 index 0000000..7e5b7ae --- /dev/null +++ b/mod2/ui/src/app/guards/role.guard.ts @@ -0,0 +1,61 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Injectable } from '@angular/core'; +import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router'; +import { Observable, of } from 'rxjs'; +import { AuthService } from '../services/auth.service'; +import { catchError, map } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class RoleGuard implements CanActivate { + + + constructor( + private authService: AuthService, + private router: Router + ) {} + + canActivate( + next: ActivatedRouteSnapshot, + state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { + + return this.authService.checkLogin().pipe(map((e:boolean)=>{ + if(e){ + console.log("role guard in"); + this.authService.setUser(); + if(this.authService.getUser().roles.includes("ROLE_ADMIN")){ + this.authService.isAdmin = true; + }else{ + this.authService.isAdmin = false; + this.router.navigate(['/home']); + } + return this.authService.getUser().roles.includes("ROLE_ADMIN"); + } + }), catchError(err=>{ + this.authService.reLoginMsg = true; + // window.alert("Your login has expired. Please log in again."); + this.authService.authPass=false; + this.router.navigate(['/login']); + return of(false); + })) + +} +} diff --git a/mod2/ui/src/app/home/home.component.css b/mod2/ui/src/app/home/home.component.css new file mode 100644 index 0000000..f345db2 --- /dev/null +++ b/mod2/ui/src/app/home/home.component.css @@ -0,0 +1,50 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + + +.card{ + display: inline-block; + margin-top: 56px; + margin-left: 75px; + color: rgb(34, 32, 32); + font-family: Arial, Helvetica, sans-serif; + font-weight: 800; + text-align: center; + padding-top: 42px; + font-size: 20px; + border-width: 2px; + border-color: gray; + border-radius: 8px; + height: 112px; + width: 261px; + cursor: pointer +} + +#subMenu{ + height: 75px; + width: 229px; + padding-top: 24px; + margin-left: 61px; + font-size: 18px; + border-radius: 5px +} + + +.mat-form-field + .mat-form-field { + margin-left: 8px; +} \ No newline at end of file diff --git a/mod2/ui/src/app/home/home.component.html b/mod2/ui/src/app/home/home.component.html new file mode 100644 index 0000000..31fe720 --- /dev/null +++ b/mod2/ui/src/app/home/home.component.html @@ -0,0 +1,93 @@ + + +
+ + Microservices... + + + Onboarding Tools + + + User Management + +
+
+
+ + Microservices + +
+
+
+ + MS Instances + +
+
+
+ + Blueprints + +
+
+
+ + MOD APIs + +
\ No newline at end of file diff --git a/mod2/ui/src/app/home/home.component.ts b/mod2/ui/src/app/home/home.component.ts new file mode 100644 index 0000000..36f9ee1 --- /dev/null +++ b/mod2/ui/src/app/home/home.component.ts @@ -0,0 +1,64 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Component, OnInit, HostBinding } from '@angular/core'; +import { AppComponent } from '../app.component'; +import { AuthService } from '../services/auth.service'; +import { BreadcrumbService } from '../services/breadcrumb.service'; + +@Component({ + selector: 'app-home', + templateUrl: './home.component.html', + styleUrls: ['./home.component.css'] +}) +export class HomeComponent implements OnInit { + + defaultElevation = 2; + raisedElevation = 8; + panelOpenState = false; + + name = 'Angular'; + + displayMsMenu = false; + + constructor(private appComp: AppComponent, public authService: AuthService, private bread: BreadcrumbService) { } + + ngOnInit() { + } + + disableAnimation = true; + ngAfterViewInit(): void { + // timeout required to avoid the dreaded 'ExpressionChangedAfterItHasBeenCheckedError' + setTimeout(() => this.disableAnimation = false); + } + + toggleMsMenu() { + if (this.displayMsMenu == false) { + this.displayMsMenu = true + } else { + this.displayMsMenu = false + } + } + + navSelect(menuItem: any) { + this.appComp.tree_handler(menuItem, null); + // Set the breadcrumbs for the selected menu item (card) + this.bread.setBreadcrumbs(menuItem, "reset"); + } + +} diff --git a/mod2/ui/src/app/login/login.component.css b/mod2/ui/src/app/login/login.component.css new file mode 100644 index 0000000..55e48a1 --- /dev/null +++ b/mod2/ui/src/app/login/login.component.css @@ -0,0 +1,25 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +.field-icon { + float:right; + left: -15px; + margin-top: 1px; + position: relative; + z-index: 2; +} \ No newline at end of file diff --git a/mod2/ui/src/app/login/login.component.html b/mod2/ui/src/app/login/login.component.html new file mode 100644 index 0000000..b98baff --- /dev/null +++ b/mod2/ui/src/app/login/login.component.html @@ -0,0 +1,58 @@ + + + +
+
+
+
+
+ + +
+
+
+ +
+
+
+ + + + +
+
+
+ + + + + +
+

Not a registered user? Contact the DCAE-MOD team at dcae-mod-team@att.com

+ Cick here to contact now +
+
+
diff --git a/mod2/ui/src/app/login/login.component.spec.ts b/mod2/ui/src/app/login/login.component.spec.ts new file mode 100644 index 0000000..5656be4 --- /dev/null +++ b/mod2/ui/src/app/login/login.component.spec.ts @@ -0,0 +1,59 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { RouterTestingModule } from '@angular/router/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; +import { CardModule } from 'primeng/card'; + +import { LoginComponent } from './login.component'; + +describe('LoginComponent', () => { + let component: LoginComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [LoginComponent], + imports: [ + FormsModule, + ReactiveFormsModule, + CardModule, + HttpClientTestingModule, + RouterTestingModule + ], + providers: [ + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/mod2/ui/src/app/login/login.component.ts b/mod2/ui/src/app/login/login.component.ts new file mode 100644 index 0000000..16cafee --- /dev/null +++ b/mod2/ui/src/app/login/login.component.ts @@ -0,0 +1,65 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Component, OnInit } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { AuthService } from '../services/auth.service'; +import { User } from '../models/User'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.css'] +}) + +export class LoginComponent implements OnInit { + + form: FormGroup; + hide: boolean=true; + + constructor(private fb: FormBuilder, private authService: AuthService, private router: Router) { } + + ngOnInit() { + this.form = this.fb.group({ + username: ['', [Validators.required]], + // uid: ['', [Validators.required]], + password: ['', [Validators.required]] + }); + } + + submit() { + this.authService.login(this.form.value as User).subscribe( + res => { + // if (this.authService.getUser().roles && this.authService.getUser().roles.includes("ROLE_USER")) { + // this.authService.isAdmin = false; + // } + if(this.authService.getUser().roles &&this.authService.getUser().roles.includes("ROLE_ADMIN")) { + this.authService.isAdmin = true; + } else { + this.authService.isAdmin = false; + } + this.router.navigate(['/home']); + }, + (err) => { + alert('User or Password is not correct, please re-enter'); + this.form.reset(); + } + ); + } +} diff --git a/mod2/ui/src/app/material-elevation.directive.ts b/mod2/ui/src/app/material-elevation.directive.ts new file mode 100644 index 0000000..8843d1e --- /dev/null +++ b/mod2/ui/src/app/material-elevation.directive.ts @@ -0,0 +1,64 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Directive, ElementRef, HostListener, Input, Renderer2, OnChanges, SimpleChanges } from '@angular/core'; + +@Directive({ + selector: '[appMaterialElevation]' +}) +export class MaterialElevationDirective implements OnChanges { + + @Input() + defaultElevation = 2; + + @Input() + raisedElevation = 8; + + constructor( + private element: ElementRef, + private renderer: Renderer2 + ) { + this.setElevation(this.defaultElevation); + } + + ngOnChanges(_changes: SimpleChanges) { + this.setElevation(this.defaultElevation); + } + + @HostListener('mouseenter') + onMouseEnter() { + this.setElevation(this.raisedElevation); + } + + @HostListener('mouseleave') + onMouseLeave() { + this.setElevation(this.defaultElevation); + } + + setElevation(amount: number) { + // remove all elevation classes + const classesToRemove = Array.from((this.element.nativeElement).classList).filter(c => c.startsWith('mat-elevation-z')); + classesToRemove.forEach((c) => { + this.renderer.removeClass(this.element.nativeElement, c); + }); + + // add the given elevation class + const newClass = `mat-elevation-z${amount}`; + this.renderer.addClass(this.element.nativeElement, newClass); + } +} diff --git a/mod2/ui/src/app/microservices/microservices.component.css b/mod2/ui/src/app/microservices/microservices.component.css new file mode 100644 index 0000000..5f86e73 --- /dev/null +++ b/mod2/ui/src/app/microservices/microservices.component.css @@ -0,0 +1,121 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +td{ + word-break:break-all +} + +textarea +{ + font-size: 12px; +} + +.table_actions_button{ + background-color: transparent; + border: none; + width: 20px; + height: 20px; + vertical-align: middle; +} + +.row-expand-layout{ + display: grid; + grid-template-columns: 30% 40% auto; + grid-gap: 10px; + grid-auto-rows: minmax(100px, auto); +} + +.row-expand-card{ + font-size: 12px; + grid-row: 1; + border-radius: 5px; + border: 1px solid slategray; + padding: 10px; + /* This height prevents vertical scroll bar in Notes */ + height: 92px; + overflow: hidden; +} + +label { + cursor: pointer; +} + +.fa-refresh{ + cursor: pointer; +} + +.input{ + padding-top: 10px; +} + +.inputLabel { + font-weight: 600; + margin-left: 20px; + width: 140px; +} + +.inputFieldSm { + width: 200px; + height: 35px; + padding-left: 6px; +} +.inputFieldMed { + width: 300px; + height: 35px; + padding-left: 6px; +} +.inputFieldLg { + width: 400px; + height: 35px; + padding-left: 6px; +} + +.table_action_item{ + outline: none; + font-size: 12px; +} + +::ng-deep .mat-menu-content { +padding-top: 0px !important; +padding-bottom: 0px !important; +} +.mat-menu-item{ +line-height:30px; +height:30px; +} + +.greenStatus{ + background-color: rgba(80, 233, 105, 0.87) +} + +.redStatus{ + background-color: rgba(255, 29, 29, 0.733) +} + +.blueStatus{ + background-color: rgba(0, 183, 255, 0.432) +} + +.greyStatus{ + background-color: rgba(150, 150, 150, 0.432) +} + +.ui-state-highlight { + background-color: #878C94 !important; + color: black !important; +} diff --git a/mod2/ui/src/app/microservices/microservices.component.html b/mod2/ui/src/app/microservices/microservices.component.html new file mode 100644 index 0000000..95ec45c --- /dev/null +++ b/mod2/ui/src/app/microservices/microservices.component.html @@ -0,0 +1,185 @@ + + + +
+ + + + + + +
+ +
+ + + + + +
+ +

Microservices

+ +
+ +
+ +
+ +
+ + + + + + + {{col.header}}
+ + + + Actions + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ {{msElem[col.field]}} +
+
{{msElem[col.field]}}
+ + + + +
+ + + +
+ Add +
+ + + +
+ Update +
+ + +
+
+ + +
+ + + + + +
+ +
+ Created By: {{rowData.metadata.createdBy}}
+ Created On: {{rowData.metadata.createdOn}}
+ Updated By: {{rowData.metadata.updatedBy}}
+ Updated On: {{rowData.metadata.updatedOn}} +
+ +
+ Notes:
+ +
{{rowData.metadata.notes}}
+
+
+ +
+ Labels:
+
+
+ {{label}} +
+
+
+
+ + +
+
+ + +
+ + +
+ + + + + + + + + + +
\ No newline at end of file diff --git a/mod2/ui/src/app/microservices/microservices.component.spec.ts b/mod2/ui/src/app/microservices/microservices.component.spec.ts new file mode 100644 index 0000000..2d2e5f1 --- /dev/null +++ b/mod2/ui/src/app/microservices/microservices.component.spec.ts @@ -0,0 +1,134 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatMenuModule } from '@angular/material'; +import { RouterTestingModule } from '@angular/router/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; +import { Ng4LoadingSpinnerModule } from 'ng4-loading-spinner'; +import { MessageService } from 'primeng/api'; +import { ButtonModule } from 'primeng/button'; +import { CalendarModule } from 'primeng/calendar'; +import { DialogModule } from 'primeng/dialog'; +import { DropdownModule } from 'primeng/dropdown'; +import { ScrollPanelModule } from 'primeng/scrollpanel'; +import { TableModule } from 'primeng/table'; +import { ToastModule } from 'primeng/toast'; +import { MsAddChangeComponent } from '../ms-add-change/ms-add-change.component'; +import { MsInstanceAddComponent } from '../ms-instance-add/ms-instance-add.component'; + +import { MicroservicesComponent } from './microservices.component'; + +describe('MicroservicesComponent', () => { + let component: MicroservicesComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + MicroservicesComponent, + MsAddChangeComponent, + MsInstanceAddComponent, + ], + imports: [ + Ng4LoadingSpinnerModule, + TableModule, + MatMenuModule, + ScrollPanelModule, + ToastModule, + DialogModule, + DropdownModule, + FormsModule, + ReactiveFormsModule, + ButtonModule, + CalendarModule, + HttpClientTestingModule, + ToastModule, + RouterTestingModule + ], + providers: [ + MessageService, + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MicroservicesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it(`should fill msInstances Object`, () => { + const fixture = TestBed.createComponent(MicroservicesComponent); + const app = fixture.debugElement.componentInstance; + + let mockMicroservice = [{ + id: 'testId1234', + name: 'test-MS', + tag: 'test-MS-tag', + serviceName: 'testServiceName', + type: 'testType', + location: 'TestLocation', + namespace: 'testNameSpace', + status: 'testStatus', + metadata: { + createdBy: 'test', + createdOn: '01-01-2020 12:00', + updatedBy: 'test', + updatedOn: '01-01-2020 12:00', + notes: 'test', + labels: ['test'], + }, + msInstances: 'test' + }] + + app.fillTable(mockMicroservice) + + expect(app.loadTable).toEqual(true); + expect(app.msElements.length).toEqual(1); + }); + + it(`should set addOrChange to "Add"`, () => { + const fixture = TestBed.createComponent(MicroservicesComponent); + const app = fixture.debugElement.componentInstance; + + let mockRowData = null + app.showAddChangeDialog(mockRowData) + + expect(app.addOrChange).toEqual('Add'); + }); + + it(`should set addOrChange to "Change"`, () => { + const fixture = TestBed.createComponent(MicroservicesComponent); + const app = fixture.debugElement.componentInstance; + + let mockRowData = {field: 'test'} + app.showAddChangeDialog(mockRowData) + + expect(app.addOrChange).toEqual('Change'); + }); +}); diff --git a/mod2/ui/src/app/microservices/microservices.component.ts b/mod2/ui/src/app/microservices/microservices.component.ts new file mode 100644 index 0000000..640e750 --- /dev/null +++ b/mod2/ui/src/app/microservices/microservices.component.ts @@ -0,0 +1,311 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Component, OnInit, ViewChild, ElementRef, Input, EventEmitter, Output } from '@angular/core'; +import { MatTableDataSource } from '@angular/material/table'; +import { Table } from 'primeng/table'; +import { MessageService } from 'primeng/api'; +import { trigger, state, style, transition, animate } from '@angular/animations'; +import { BaseMicroserviceService } from '../services/base-microservice.service'; +import { MsAddService } from '../services/ms-add.service'; +import { MicroserviceInstanceService } from '../services/microservice-instance.service'; +import { AuthService } from '../services/auth.service'; +import { DatePipe } from '@angular/common'; +import { Ng4LoadingSpinnerService } from 'ng4-loading-spinner'; +import { DownloadService } from '../services/download.service'; + +@Component({ + selector: 'app-microservices', + templateUrl: './microservices.component.html', + styleUrls: ['./microservices.component.css'], + animations: [ + trigger('rowExpansionTrigger', [ + state('void', style({ + transform: 'translateX(-10%)', + opacity: 0 + })), + state('active', style({ + transform: 'translateX(0)', + opacity: 1 + })), + transition('* <=> *', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)')) + ]) + ], + providers: [DatePipe] +}) +export class MicroservicesComponent implements OnInit { + @ViewChild(Table, { static: false }) dt: Table; + + /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ + msElements: any[] = []; + dataSource = new MatTableDataSource(this.msElements); + cols: any[] = [ + { field: 'name', header: 'MS Name' }, + { field: 'tag', header: 'MS Tag' }, + { field: 'serviceName', header: 'Service Short Name'}, + { field: 'type', header: 'Type', width: '11%' }, + { field: 'namespace', header: 'Namespace', width: '15%' }, + { field: 'status', header: 'Status', width: '90px' } + ]; + columns: any[]; + loadTable: boolean; + filteredRows: any; + downloadItems: { label: string; command: () => void; }[]; + showAddChangeMsInstance: boolean; + addInstanceTo: string = ""; + msInstanceStates: { label: string, value: string }[] = [ + { label: 'New', value: 'new' }, + { label: 'In Dev', value: 'in-dev' }, + { label: 'Dev Complete', value: 'dev-complete' }, + { label: 'In Test', value: 'in-test' }, + { label: 'Certified', value: 'certified' }, + { label: 'Prod Deployed', value: 'prod-deployed' } + ] + + // Json to add MS to DB, returned from child + msAddChangeJson: any; + + showMsAddChangeDialog: boolean = false; + currentRow: any; + currentMsRow: string = ""; + addOrChange: string; + username: string; + errorMessage: any; + successMessage: string; + + constructor(private spinnerService: Ng4LoadingSpinnerService, private baseMsService: BaseMicroserviceService, + private msInstanceApi: MicroserviceInstanceService, private messageService: MessageService, + private addChangeMsApi: MsAddService, private authService: AuthService, private datePipe: DatePipe, + private downloadService: DownloadService) { } + + ngOnInit() { + this.username = this.authService.getUser().username; + this.getAllMs(); + } + + getAllMs() { + this.spinnerService.show(); + this.msElements = []; + this.loadTable = false; + + this.baseMsService.getAllBaseMs() + .subscribe((data: any[]) => { + this.fillTable(data) + }) + + this.columns = this.cols.map(col => ({ title: col.header, dataKey: col.field })); + } + + /*checks when table is filtered and stores filtered data in new + object to be downloaded when download button is clicked*/ + onTableFiltered(values) { + if (values !== null) { + this.filteredRows = values; + } else { + this.filteredRows = this.msElements; + } + } + + //download table as excel file + exportTable(exportTo: string) { + let downloadElements: any[] = [] + + //labels array not handled well by excel download so converted them to a single string + for (let row of this.filteredRows) { + let labels; + let notes; + if (exportTo === "excel") { + if (row.metadata.labels !== undefined) { + labels = row.metadata.labels.join(",") + } + } else { + labels = row.metadata.labels + } + + if (row.metadata.notes !== null && row.metadata.notes !== undefined && row.metadata.notes !== '') { + notes = encodeURI(row.metadata.notes).replace(/%20/g, " ").replace(/%0A/g, "\\n") + } + + downloadElements.push({ + MS_Name: row.name, + MS_Tag: row.tag, + Service_Short_Name: row.serviceName, + Type: row.type, + Location: row.location, + Namespace: row.namespace, + Status: row.status, + Created_By: row.metadata.createdBy, + Created_On: row.metadata.createdOn, + Updated_By: row.metadata.updatedBy, + Updated_On: row.metadata.updatedOn, + Notes: notes, + Labels: labels + }) + } + + let csvHeaders = [] + + if (exportTo === "csv") { + csvHeaders = [ + "MS_Name", + "MS_Tag", + "Service_Short_Name", + "Type", + "Location", + "Namespace", + "Status", + "Created_By", + "Created_On", + "Updated_By", + "Updated_On", + "Notes", + "Labels" + ]; + } + + this.downloadService.exportTableData(exportTo, downloadElements, csvHeaders) + } + + // * * * * * Show the Dialog to Add a MS ( tag in the html) * * * * * + showAddChangeDialog(rowData) { + this.showMsAddChangeDialog = true; + this.currentRow = rowData; + if (this.currentRow) { + this.addOrChange = "Change"; + } else { + this.addOrChange = "Add"; + } + } + + // * * * * * Add or Change a MS * * * * * + // The response includes the entire MS record that was Added or Changed, (along with ID and audit fields). + // When Added, the response is added directly to the table. When Changed, the current record is updated field-by-field. + addOrChangeMs(jsonFromChildDialog) { + if (jsonFromChildDialog) { + this.msAddChangeJson = jsonFromChildDialog; + if (this.addOrChange == "Change") { + this.currentMsRow = this.currentRow['id'] + } + this.addChangeMsApi.addChangeMsToCatalog(this.addOrChange, this.currentMsRow, this.msAddChangeJson).subscribe( + (response: any) => { + if (this.addOrChange == "Add") { + this.msElements.unshift(response); + this.successMessage = "Microservice Added"; + } else { + this.updateCurrentRow(jsonFromChildDialog); + this.successMessage = "Microservice Updated"; + } + this.showMsAddChangeDialog = false; + this.messageService.add({ key: 'addChangeSuccess', severity: 'success', summary: 'Success', detail: this.successMessage, life: 5000 }); + }, + errResponse => { + // for testing only - this.updateCurrentRow(jsonFromChildDialog); + this.messageService.add({ key: 'msAddChangeError', severity: 'error', summary: 'Error', detail: errResponse.error.message, sticky: true }); + } + ) + } + else { + this.showMsAddChangeDialog = false; + }; + } + + updateCurrentRow(jsonFromChildDialog) { + const newRow = JSON.parse(jsonFromChildDialog); + this.currentRow['name'] = newRow['name']; + this.currentRow['serviceName'] = newRow['serviceName']; + this.currentRow['type'] = newRow['type']; + this.currentRow['location'] = newRow['location']; + this.currentRow['namespace'] = newRow['namespace']; + this.currentRow['metadata']['labels'] = newRow['metadata']['labels']; + this.currentRow['metadata']['notes'] = newRow['metadata']['notes']; + } + + /* * * * Show pop up for Adding a new MS Instance * * * */ + showAddChangeMsInstanceDialog(data) { + this.addInstanceTo = data['name'] + this.showAddChangeMsInstance = true + } + + /* * * * Call API to Add a new MS Instance * * * */ + addMsInstance(body) { + if (body === null) { + this.showAddChangeMsInstance = false; + } else { + this.msInstanceApi.addChangeMsInstance("ADD", this.addInstanceTo, body).subscribe( + (data) => { + this.messageService.add({ key: 'addChangeSuccess', severity: 'success', summary: 'Success', detail: "MS Instance Added", life: 5000 }); + this.showAddChangeMsInstance = false; + }, + (errResponse) => { + console.log(errResponse) + this.messageService.add({ key: 'instanceAddChangeError', severity: 'error', summary: 'Error', detail: errResponse.error.message, sticky: true }); + } + ) + } + } + + //fill object with microservice data, to be used to fill table. + //checks if fields are empty and if they are, store 'N/A' as the values + fillTable(data) { + for (let elem of data) { + var tempMsElement: any = { + id: elem.id, + name: elem.name, + tag: elem.tag, + serviceName: elem.serviceName, + type: elem.type, + location: elem.location, + namespace: elem.namespace, + status: elem.status, + metadata: { + createdBy: elem.metadata.createdBy, + createdOn: this.datePipe.transform(elem.metadata.createdOn, 'MM-dd-yyyy HH:mm'), + updatedBy: elem.metadata.updatedBy, + updatedOn: this.datePipe.transform(elem.metadata.updatedOn, 'MM-dd-yyyy HH:mm'), + notes: elem.metadata.notes, + labels: elem.metadata.labels + }, + msInstances: elem.msInstances + } + this.msElements.push(tempMsElement) + } + this.filteredRows = this.msElements + this.loadTable = true; + this.spinnerService.hide(); + } +} + +export interface AddMsInstance{ + name: string, + release: string, + metadata: { + scrumLead: string, + scrumLeadId: string, + systemsEngineer: string, + systemsEngineerId: string, + developer: string; + developerId: string; + pstDueDate: string, + pstDueIteration: string, + eteDueDate: string, + eteDueIteration: string, + labels: string[], + notes: string + } + user: string +} diff --git a/mod2/ui/src/app/models/AuthResponse.ts b/mod2/ui/src/app/models/AuthResponse.ts new file mode 100644 index 0000000..f44104d --- /dev/null +++ b/mod2/ui/src/app/models/AuthResponse.ts @@ -0,0 +1,28 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Authority } from './Authority.enum'; + +export class AuthResponse { + token?: string; + type?: null; + id?: string; + username?:string; + roles?: Authority[]; + message?:string; +} \ No newline at end of file diff --git a/mod2/ui/src/app/models/Authority.enum.ts b/mod2/ui/src/app/models/Authority.enum.ts new file mode 100644 index 0000000..cbac582 --- /dev/null +++ b/mod2/ui/src/app/models/Authority.enum.ts @@ -0,0 +1,22 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +export enum Authority { + ADMIN = 'ROLE_ADMIN', + USER = 'ROLE_USER' +} \ No newline at end of file diff --git a/mod2/ui/src/app/models/User.ts b/mod2/ui/src/app/models/User.ts new file mode 100644 index 0000000..3c662d9 --- /dev/null +++ b/mod2/ui/src/app/models/User.ts @@ -0,0 +1,25 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +export class User{ + username: string; + fullName?: string; + password?: string; + active?: boolean; + roles?: any[]; +} \ No newline at end of file diff --git a/mod2/ui/src/app/ms-add-change/ms-add-change.component.css b/mod2/ui/src/app/ms-add-change/ms-add-change.component.css new file mode 100644 index 0000000..f256b2f --- /dev/null +++ b/mod2/ui/src/app/ms-add-change/ms-add-change.component.css @@ -0,0 +1,57 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +.input { + padding-top: 10px; +} + +.inputLabel { + font-weight: 600; + margin-left: 20px; + width: 150px; +} + +.inputFieldSm { + width: 200px; + height: 35px; + padding-left: 6px; +} +.inputFieldMed { + width: 300px; + height: 35px; + padding-left: 6px; +} +.inputFieldLg { + width: 400px; + height: 35px; + padding-left: 6px; +} + +.validationMsg { + padding-left: 175px; + color: red; + font-size: 11px; + font-weight: 550; +} + +.validationMsgWarning { + padding-left: 175px; + color: rgb(255, 81, 0); + font-size: 11px; + font-weight: 550; +} \ No newline at end of file diff --git a/mod2/ui/src/app/ms-add-change/ms-add-change.component.html b/mod2/ui/src/app/ms-add-change/ms-add-change.component.html new file mode 100644 index 0000000..2fd19d7 --- /dev/null +++ b/mod2/ui/src/app/ms-add-change/ms-add-change.component.html @@ -0,0 +1,109 @@ + + + + + + + + +
+ +
+ + +
+ +
+ + + + + +
+ Format: lowercase alphanumeric with embedded dashes (5-50 characters) +
+ +
+ MS Tag cannot exceed 50 chars +
+
+ +
+ + + +
+ Format: lowercase alpha with embedded dashes +
+ +
+ Warning! Only Global Site short names can exceed 12 chars (max 25) +
+ +
+ Global Site short names cannot exceed 25 chars +
+
+ +
+ + +
+ + +
+ + + +
+ Format: lowercase alphanumeric with embedded dashes +
+
+ +
+ + +
+ (Separate labels with a space) + +
+ + +
+ +
+ + +
+
+ +
diff --git a/mod2/ui/src/app/ms-add-change/ms-add-change.component.spec.ts b/mod2/ui/src/app/ms-add-change/ms-add-change.component.spec.ts new file mode 100644 index 0000000..4ba7ae9 --- /dev/null +++ b/mod2/ui/src/app/ms-add-change/ms-add-change.component.spec.ts @@ -0,0 +1,67 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { RouterTestingModule } from '@angular/router/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; +import { MessageService } from 'primeng/api'; +import { ButtonModule } from 'primeng/button'; +import { DialogModule } from 'primeng/dialog'; +import { DropdownModule } from 'primeng/dropdown'; +import { ToastModule } from 'primeng/toast'; + +import { MsAddChangeComponent } from './ms-add-change.component'; + +describe('MsAddChangeComponent', () => { + let component: MsAddChangeComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [MsAddChangeComponent], + imports: [ + DialogModule, + DropdownModule, + ToastModule, + FormsModule, + ReactiveFormsModule, + ButtonModule, + HttpClientTestingModule, + RouterTestingModule + ], + providers: [ + MessageService, + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MsAddChangeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/mod2/ui/src/app/ms-add-change/ms-add-change.component.ts b/mod2/ui/src/app/ms-add-change/ms-add-change.component.ts new file mode 100644 index 0000000..6991104 --- /dev/null +++ b/mod2/ui/src/app/ms-add-change/ms-add-change.component.ts @@ -0,0 +1,180 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { InputTextModule } from 'primeng/inputtext'; +import { DropdownModule } from 'primeng/dropdown'; +import { FormGroup, FormBuilder, Validators, FormControl } from '@angular/forms'; +import { AuthService } from '../services/auth.service'; + +interface Type { + type: string; +} +interface Location { + location: string; +} + +@Component({ + selector: 'app-ms-add-change', + templateUrl: './ms-add-change.component.html', + styleUrls: ['./ms-add-change.component.css'] +}) + +export class MsAddChangeComponent implements OnInit { + + guiHeader: string = "Microservice ADD" + // Used for the Add/Update button label + addOrUpdate: string = "Add"; + + msAddForm: FormGroup; + // The loggged in user + username: string; + + // Input form fields + name: string; + tag: string; + serviceName: string = ""; + type: string; + location: string; + namespace: string; + labels: string; + notes: string; + + // Dropdowns + types: Type[]; + locations: Location[]; + + // Return JSON to parent component + msAddChangeString: any; + msAddChangeJson: any; + + constructor(private fb: FormBuilder, private authService: AuthService) { + } + + @Input() visible: boolean; + @Input() currentRow: any; + @Output() handler: EventEmitter = new EventEmitter(); + + ngOnInit() { + // The logged in user + this.username = this.authService.getUser().username; + + this.msAddForm = new FormGroup({ + name: new FormControl(), + tag: new FormControl(), + serviceName: new FormControl(), + type: new FormControl(), + //location: new FormControl(), + namespace: new FormControl(), + labels: new FormControl(), + notes: new FormControl() + }); + + // FORM fields and validations + this.msAddForm = this.fb.group({ + name: ['', [Validators.required]], + tag: ['', [Validators.required, Validators.pattern('^([a-z0-9](-[a-z0-9])*)+$'), Validators.minLength(5), Validators.maxLength(50)]], + serviceName: ['', [Validators.pattern('^([a-z](-[a-z])*)+$'), Validators.maxLength(25)]], + type: ['', [Validators.required]], + //location: ['', [Validators.required]], + namespace: ['', [Validators.pattern('^([a-z0-9](-[a-z0-9])*)+$')]], + labels: ['', []], + notes: ['', []] + }, + {updateOn: "blur"} + ); + + // TYPE Dropdown + this.types = [ + { type: 'FM_COLLECTOR' }, + { type: 'PM_COLLECTOR' }, + { type: 'ANALYTIC' }, + { type: 'TICK' }, + { type: 'OTHER' } + ]; + // LOCATION Dropdown + this.locations = [ + { location: 'EDGE' }, + { location: 'CENTRAL' }, + { location: 'UNSPECIFIED' } + ]; + + // "Update" was selected, so populate the current row data in the GUI + if (this.currentRow) { + this.guiHeader = "Microservice Update"; + this.addOrUpdate = "Update"; + this.populateFields(); + } + + } + + populateFields() { + let labelsStr: string; + if (this.currentRow['metadata']['labels']) { + labelsStr = this.currentRow['metadata']['labels'].join(' ') + } + + // Prevent validation (length check) from failing in the html + if (this.currentRow['serviceName']) { + this.serviceName = this.currentRow['serviceName'] + } + + this.msAddForm.patchValue({ + name: this.currentRow['name'], + tag: this.currentRow['tag'], + serviceName: this.serviceName, + type: {type: this.currentRow['type']}, + //location: {location: this.currentRow['location']}, + namespace: this.currentRow['namespace'], + labels: labelsStr, + notes: this.currentRow['metadata']['notes'] + }) + } + + // The handler emits 'null' back to parent to close dialog and make it available again when clicked + closeDialog() { + this.visible = false; + this.handler.emit(null); + } + + // Create the JSON to be sent to the parent component + // The "labels" functions below take into account leading/trailing spaces, multiple spaces between labels, and conversion into an array + createOutputJson() { + this.msAddChangeString = { + name: this.msAddForm.value['name'].trim(), + tag: this.msAddForm.value['tag'], + serviceName: this.msAddForm.value['serviceName'], + type: this.msAddForm.value['type'].type, + location: 'UNSPECIFIED', + //location: this.msAddForm.value['location'].location, + namespace: this.msAddForm.value['namespace'].trim(), + metadata: { + labels: this.msAddForm.value['labels'].trim().replace(/\s{2,}/g, ' ').split(" "), + notes: this.msAddForm.value['notes'] + }, + user: this.username + }; + } + + saveMs() { + this.createOutputJson(); + this.msAddChangeJson = JSON.stringify(this.msAddChangeString); + this.handler.emit(this.msAddChangeJson); + } + +} diff --git a/mod2/ui/src/app/ms-instance-add/ms-instance-add.component.css b/mod2/ui/src/app/ms-instance-add/ms-instance-add.component.css new file mode 100644 index 0000000..8ecfc18 --- /dev/null +++ b/mod2/ui/src/app/ms-instance-add/ms-instance-add.component.css @@ -0,0 +1,48 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +.input{ + padding-top: 8px; +} + +.inputLabel { + font-weight: 600; + margin-left: 20px; + width: 165px; +} + +.inputFieldXSm { + width: 75px; + height: 30px; + padding-left: 6px; +} +.inputFieldSm { + width: 200px; + height: 30px; + padding-left: 6px; +} +.inputFieldMed { + width: 300px; + height: 30px; + padding-left: 6px; +} +.inputFieldLg { + width: 400px; + height: 30px; + padding-left: 6px; +} \ No newline at end of file diff --git a/mod2/ui/src/app/ms-instance-add/ms-instance-add.component.html b/mod2/ui/src/app/ms-instance-add/ms-instance-add.component.html new file mode 100644 index 0000000..804de33 --- /dev/null +++ b/mod2/ui/src/app/ms-instance-add/ms-instance-add.component.html @@ -0,0 +1,93 @@ + + + + + + + +
+ +
+ + {{msName}} +
+ +
+ + +
+ +
+ + / +
+ +
+ + / +
+ +
+ + / +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ (Separate labels with a space) + +
+ + +
+ + +
+   + +
+
+ +
\ No newline at end of file diff --git a/mod2/ui/src/app/ms-instance-add/ms-instance-add.component.spec.ts b/mod2/ui/src/app/ms-instance-add/ms-instance-add.component.spec.ts new file mode 100644 index 0000000..2318a2c --- /dev/null +++ b/mod2/ui/src/app/ms-instance-add/ms-instance-add.component.spec.ts @@ -0,0 +1,71 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { DatePipe } from '@angular/common'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { RouterTestingModule } from '@angular/router/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; +import { MessageService } from 'primeng/api'; +import { ButtonModule } from 'primeng/button'; +import { CalendarModule } from 'primeng/calendar'; +import { DialogModule } from 'primeng/dialog'; +import { DropdownModule } from 'primeng/dropdown'; +import { ToastModule } from 'primeng/toast'; + +import { MsInstanceAddComponent } from './ms-instance-add.component'; + +describe('MsInstanceAddComponent', () => { + let component: MsInstanceAddComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [MsInstanceAddComponent], + imports: [ + DialogModule, + DropdownModule, + ToastModule, + FormsModule, + ReactiveFormsModule, + ButtonModule, + HttpClientTestingModule, + RouterTestingModule, + CalendarModule, + ], + providers: [ + MessageService, + DatePipe, + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MsInstanceAddComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/mod2/ui/src/app/ms-instance-add/ms-instance-add.component.ts b/mod2/ui/src/app/ms-instance-add/ms-instance-add.component.ts new file mode 100644 index 0000000..228ddc1 --- /dev/null +++ b/mod2/ui/src/app/ms-instance-add/ms-instance-add.component.ts @@ -0,0 +1,189 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { MicroserviceInstanceService } from '../services/microservice-instance.service'; +import { MessageService } from 'primeng/api'; +import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms'; +import { AuthService } from '../services/auth.service'; +import { DatePipe } from '@angular/common'; + +@Component({ + selector: 'app-ms-instance-add', + templateUrl: './ms-instance-add.component.html', + styleUrls: ['./ms-instance-add.component.css'] +}) +export class MsInstanceAddComponent implements OnInit { + + guiHeader: string = "Microservice Instance ADD"; + // Used for the Add/Update button label + addOrUpdate: string = "Add"; + + msInstanceAddForm: FormGroup; + msInstanceToAdd: AddMsInstance; + addInstanceTo: string = ""; + username: string; + msInstanceReleases: { label: string, value: string }[] = [ + { label: '2004', value: '2004' }, + { label: '2006', value: '2006' }, + { label: '2008', value: '2008' }, + { label: '2009', value: '2009' }, + { label: '2010', value: '2010' }, + { label: '2011', value: '2011' }, + { label: '2012', value: '2012' } + ] + + @Input() visible: boolean; + @Input() msName: string; + @Input() msInstanceChange: string; // Use to differentiate Add from Change, since currentRow can be problematic + @Input() currentRow: any; + @Output() handler: EventEmitter = new EventEmitter(); + + constructor(private addChangeMsInstanceApi: MicroserviceInstanceService, private messageService: MessageService, private fb: FormBuilder, private authService: AuthService, private datePipe: DatePipe) { } + + ngOnInit() { + this.username = this.authService.getUser().username; + + this.msInstanceAddForm = new FormGroup({ + name: new FormControl(), + release: new FormControl(), + scrumLead: new FormControl(), + scrumLeadId: new FormControl(), + systemsEngineer: new FormControl(), + systemsEngineerId: new FormControl(), + developer: new FormControl(), + developerId: new FormControl(), + status: new FormControl(), + pstDueDate: new FormControl(), + pstDueIteration: new FormControl(), + eteDueDate: new FormControl(), + eteDueIteration: new FormControl(), + labels: new FormControl(), + notes: new FormControl() + }); + + this.msInstanceAddForm = this.fb.group({ + name: ['', []], + release: ['', [Validators.required]], + scrumLead: ['', []], + scrumLeadId: ['', []], + systemsEngineer: ['', []], + systemsEngineerId: ['', []], + developer: ['', [Validators.required]], + developerId: ['', [Validators.required]], + status: ['', []], + pstDueDate: ['', []], + pstDueIteration: ['', []], + eteDueDate: ['', []], + eteDueIteration: ['', []], + labels: ['', []], + notes: ['', []] + }); + + if (this.msInstanceChange) { + this.guiHeader = "Microservice Instance Update"; + this.addOrUpdate = "Update"; + this.populateFields(); + } + } + + populateFields() { + this.msName = this.currentRow['name']; + + let labelsStr: string; + if (this.currentRow['metadata']['labels']) { + labelsStr = this.currentRow['metadata']['labels'].join(' ') + } + + this.msInstanceAddForm.patchValue({ + release: this.currentRow['release'], + scrumLead: this.currentRow['metadata']['scrumLead'], + scrumLeadId: this.currentRow['metadata']['scrumLeadId'], + systemsEngineer: this.currentRow['metadata']['systemsEngineer'], + systemsEngineerId: this.currentRow['metadata']['systemsEngineerId'], + developer: this.currentRow['metadata']['developer'], + developerId: this.currentRow['metadata']['developerId'], + pstDueDate: this.currentRow['pstDueDate'], + pstDueIteration: this.currentRow['pstDueIteration'], + eteDueDate: this.currentRow['eteDueDate'], + eteDueIteration: this.currentRow['eteDueIteration'], + labels: labelsStr, + notes: this.currentRow['metadata']['notes'] + }) +} + + /* * * * On click of cancel * * * */ + closeDialog() { + this.visible = false; + this.handler.emit(null) + } + + /* * * * On click of add * * * */ + submitMsInstance() { + // Prevent error on "split" if record existed before "labels" were implemented + let labels: string[] = [] + if (!this.msInstanceAddForm.value['labels']){ + labels = [] + } else { + labels = this.msInstanceAddForm.value['labels'].trim().replace(/\s{2,}/g, ' ').split(" ") + } + + //build request body + this.msInstanceToAdd = { + name: this.msName, + release: this.msInstanceAddForm.value['release'], + metadata: { + scrumLead: this.msInstanceAddForm.value['scrumLead'], + scrumLeadId: this.msInstanceAddForm.value['scrumLeadId'], + systemsEngineer: this.msInstanceAddForm.value['systemsEngineer'], + systemsEngineerId: this.msInstanceAddForm.value['systemsEngineerId'], + developer: this.msInstanceAddForm.value['developer'], + developerId: this.msInstanceAddForm.value['developerId'], + pstDueDate: this.msInstanceAddForm.value['pstDueDate'], + pstDueIteration: this.msInstanceAddForm.value['pstDueIteration'], + eteDueDate: this.msInstanceAddForm.value['eteDueDate'], + eteDueIteration: this.msInstanceAddForm.value['eteDueIteration'], + labels: labels, + notes: this.msInstanceAddForm.value['notes'] + }, + user: this.username + } + + this.handler.emit(this.msInstanceToAdd) //return request body back to parent + } +} + +export interface AddMsInstance { + name: string, + release: string, + metadata: { + scrumLead: string, + scrumLeadId: string, + systemsEngineer: string, + systemsEngineerId: string, + developer: string, + developerId: string, + pstDueDate: any, + pstDueIteration: string, + eteDueDate: any, + eteDueIteration: string, + labels: string[], + notes: string + } + user: string +} \ No newline at end of file diff --git a/mod2/ui/src/app/msInstances/msInstances.component.css b/mod2/ui/src/app/msInstances/msInstances.component.css new file mode 100644 index 0000000..62c5ac8 --- /dev/null +++ b/mod2/ui/src/app/msInstances/msInstances.component.css @@ -0,0 +1,148 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +td{ + word-break:break-all; + font-size: 12px; +} + +th{ + text-align: center; + font-size: 12px; +} + +.table_column_filter{ + width: 100%; + height: 20px; + font-size: 10px; +} + +.table_div{ + margin: 0px 50px 10px 20px; + min-width: 980px; + width: 98%; + border: 1px solid darkslategray; +} + +.fa-refresh{ + cursor: pointer; +} + +textarea +{ + font-size: 12px; +} + +.row-expand-layout{ + display: grid; + grid-template-columns: 17% 30% 30% auto; + grid-gap: 10px; + grid-auto-rows: minmax(100px, auto); +} + +.row-expand-card{ + font-size: 12px; + border-radius: 5px; + border: 1px solid slategray; + padding: 10px; + /* This height prevents vertical scroll bar in Notes */ + height: 92px; + overflow: hidden; + min-width: 195px; +} + +.table_export_buttons_alignment{ + margin-left: 5px; + margin-top: -32px; + float: left; +} + +.table_export_button{ + border-radius: 5px; + height: 22px; + font-size: 14px; + border: none; + margin-top: 4px; + margin-right: 7px; + display: inline-flex; +} + +.table_caption_header{ + margin-left: -18%; + width: 82%; + max-height: 25px; + display: inline-flex; +} + +.table_global_filter{ + width: 250px; + height: 25px; + margin-left: 15px; + font-size: 12px; +} + +.table_title{ + margin-left: 15%; +} + +.table_actions_button{ + background-color: transparent; + border: none; + width: 20px; + height: 20px; + vertical-align: middle; +} + +.table_action_item{ + outline: none; + font-size: 12px; +} + +::ng-deep .mat-menu-content { + padding-top: 0px !important; + padding-bottom: 0px !important; +} +.mat-menu-item{ + line-height:30px; + height:30px; +} + +.greenStatus{ + background-color: rgba(80, 233, 105, 0.87) +} + +.redStatus{ + background-color: rgba(255, 29, 29, 0.733) +} + +.blueStatus{ + background-color: rgba(0, 183, 255, 0.432) +} + +.greyStatus{ + background-color: rgba(150, 150, 150, 0.432) +} + +.toast-newline { + white-space: pre-line; +} + +.ui-state-highlight { + background-color: #878C94 !important; + color: black !important; +} \ No newline at end of file diff --git a/mod2/ui/src/app/msInstances/msInstances.component.html b/mod2/ui/src/app/msInstances/msInstances.component.html new file mode 100644 index 0000000..1545de3 --- /dev/null +++ b/mod2/ui/src/app/msInstances/msInstances.component.html @@ -0,0 +1,221 @@ + + + +
+ + + +
+ +
+ + + + + +
+ +
+

Microservice Instances

+
+
+ +
+ + + + + + + {{col.header}}
+ + + + + Actions + + + + + + + + + +
+ + + + +
+ Add +
+ +
+ +
+
+
+ + +
+ + + + + + + + + + + + +
+ {{msElem[col.field]}} +
+
{{msElem[col.field]}}
+ + + +
+ + + + +
+ Add +
+ + + +
+ +
+ +
+ View +
+ +
+ +
+ + + +
+ Update +
+ + +
+
+ + +
+ + + + + +
+ +
+ Created By: {{rowData.metadata.createdBy}}
+ Created On: {{rowData.metadata.createdOn}}
+ Updated By: {{rowData.metadata.updatedBy}}
+ Updated On: {{rowData.metadata.updatedOn}} +
+ +
+ Scrum Lead: {{rowData.metadata.scrumLead}} + ({{rowData.metadata.scrumLeadId}})
+ Systems Engineer: {{rowData.metadata.systemsEngineer}} + ({{rowData.metadata.systemsEngineerId}})
+ Developer: {{rowData.metadata.developer}} + ({{rowData.metadata.developerId}}) +
+ +
+ Notes:
+ +
{{rowData.metadata.notes}}
+
+
+ +
+ Labels:
+
+
+ {{label}} +
+
+
+
+ + +
+ +
+ + +
+ + +
+ + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/mod2/ui/src/app/msInstances/msInstances.component.spec.ts b/mod2/ui/src/app/msInstances/msInstances.component.spec.ts new file mode 100644 index 0000000..5a26b58 --- /dev/null +++ b/mod2/ui/src/app/msInstances/msInstances.component.spec.ts @@ -0,0 +1,127 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatMenuModule, MatTooltipModule } from '@angular/material'; +import { RouterTestingModule } from '@angular/router/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; +import { Ng4LoadingSpinnerModule } from 'ng4-loading-spinner'; +import { MessageService } from 'primeng/api'; +import { ButtonModule } from 'primeng/button'; +import { CalendarModule } from 'primeng/calendar'; +import { DialogModule } from 'primeng/dialog'; +import { DropdownModule } from 'primeng/dropdown'; +import { ScrollPanelModule } from 'primeng/scrollpanel'; +import { TableModule } from 'primeng/table'; +import { ToastModule } from 'primeng/toast'; +import { CompSpecAddComponent } from '../comp-spec-add/comp-spec-add.component'; +import { MsInstanceAddComponent } from '../ms-instance-add/ms-instance-add.component'; + +import { MsInstancesComponent } from './msInstances.component'; + +describe('MsInstancesComponent', () => { + let component: MsInstancesComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + MsInstancesComponent, + CompSpecAddComponent, + MsInstanceAddComponent + ], + imports: [ + Ng4LoadingSpinnerModule, + TableModule, + MatMenuModule, + ScrollPanelModule, + ToastModule, + DialogModule, + DropdownModule, + FormsModule, + ReactiveFormsModule, + ButtonModule, + CalendarModule, + HttpClientTestingModule, + ToastModule, + RouterTestingModule, + MatTooltipModule + ], + providers: [ + MessageService, + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MsInstancesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it(`should fill msInstances Object`, () => { + const fixture = TestBed.createComponent(MsInstancesComponent); + const app = fixture.debugElement.componentInstance; + + let mockMsInstance = [{ + id: 'testId1234', + name: 'test-MS', + release: '2008', + version: '1.0.0', + status: 'New', + msInfo: { + id: 'testBaseMsId1234', + name: 'test Base Ms', + tag: 'test-MS-tag', + }, + metadata: { + scrumLead: 'test', + scrumLeadId: 'testId', + systemsEngineer: 'test', + systemsEngineerId: 'testId', + developer: 'test', + developerId: 'testId', + pstDueDate: '01-01-2020 12:00', + pstDueIteration: '1.1', + eteDueDate: '01-01-2020 12:00', + eteDueIteration: '1.1', + createdBy: 'test', + createdOn: '01-01-2020 12:00', + updatedBy: 'test', + updatedOn: '01-01-2020 12:00', + notes: 'test', + labels: ['test'], + }, + activeSpec: 'test' + }] + + app.fillTable(mockMsInstance) + + expect(app.loadTable).toEqual(true); + expect(app.msInstances.length).toEqual(1); + }); +}); diff --git a/mod2/ui/src/app/msInstances/msInstances.component.ts b/mod2/ui/src/app/msInstances/msInstances.component.ts new file mode 100644 index 0000000..e011cb9 --- /dev/null +++ b/mod2/ui/src/app/msInstances/msInstances.component.ts @@ -0,0 +1,511 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; +import { MatTableDataSource } from '@angular/material/table'; +import { Table } from 'primeng/table'; +import { MessageService } from 'primeng/api'; +import { trigger, state, style, transition, animate } from '@angular/animations'; +import { MicroserviceInstanceService } from '../services/microservice-instance.service'; +import { DatePipe } from '@angular/common'; +import { DeploymentArtifactService } from '../services/deployment-artifact.service'; +import { CompSpecAddService } from '../services/comp-spec-add.service'; +import { BreadcrumbService } from '../services/breadcrumb.service'; +import { Router } from '@angular/router'; +import { Ng4LoadingSpinnerService } from 'ng4-loading-spinner'; +import { DownloadService } from '../services/download.service'; + +@Component({ + selector: 'app-msInstances', + templateUrl: './msInstances.component.html', + styleUrls: ['./msInstances.component.css'], + animations: [ + trigger('rowExpansionTrigger', [ + state('void', style({ + transform: 'translateX(-10%)', + opacity: 0 + })), + state('active', style({ + transform: 'translateX(0)', + opacity: 1 + })), + transition('* <=> *', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)')) + ]) + ], + providers: [DatePipe] +}) +export class MsInstancesComponent implements OnInit { + @ViewChild(Table, { static: false }) dt: Table; + @ViewChild('myInput', { static: false }) myInputVariable: ElementRef; + + + /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ + msInstances: msInstance[] = []; + expandedItems: Array = new Array(); + dataSource = new MatTableDataSource(this.msInstances); + cols: any[] = [ + { field: 'baseMsName', header: 'MS Name' }, + { field: 'tag', header: 'MS Tag' }, + { field: 'release', header: 'Release', width: '7%' }, + { field: 'pstDueDate', header: 'PST Date', width: '9%' }, + { field: 'pstDueIteration', header: 'PST Iteration', width: '6.5%' }, + { field: 'eteDueDate', header: 'ETE Date', width: '9%' }, + { field: 'eteDueIteration', header: 'ETE Iteration', width: '6.5%' }, + { field: 'status', header: 'Status', width: '125px' } + ]; + selectedMsInstances: msInstance[] = []; + columns: any[]; + loadTable: boolean; + filteredRows: any; + downloadItems: { label: string; command: () => void; }[]; + showAddChangeMsInstance: boolean; + currentRow: any; + msInstanceChange: string = "change"; + generatedBPs: any[] = []; + canGenerateSelectedBPs: boolean = false; + generateSelectedBPsTooltip: string = ''; + + // Json to add CS (Component Spec) to DB, returned from child + csAddJson: any; + + showCsAddDialog: boolean = false; + + showViewCs: boolean =false; + msInstanceId: string = ''; + errorList: string[]; + + constructor(private spinnerService: Ng4LoadingSpinnerService, private msInstanceApi: MicroserviceInstanceService, + private bpApis: DeploymentArtifactService, private addCsApi: CompSpecAddService, private messageService: MessageService, + private datePipe: DatePipe, private router: Router, private downloadService: DownloadService, private bread: BreadcrumbService) { } + + ngOnInit() { + + this.getAllInstances(); + + } + + getAllInstances() { + this.spinnerService.show(); + this.msInstances = []; + this.loadTable = false; + + this.msInstanceApi.getAllMsInstances() + .subscribe((data: any[]) => { + this.fillTable(data) + }) + + this.columns = this.cols.map(col => ({ title: col.header, dataKey: col.field })); + } + + + // * * * * * Show the Dialog to Change an MS Instance ( tag in the html) * * * * * + showAddChangeDialog(rowData) { + this.msInstanceId = rowData['id'] + this.showAddChangeMsInstance = true; + this.currentRow = rowData; + } + + /* * * * Call API to Change an MS Instance * * * */ + addChangeMsInstance(jsonFromChildDialog) { + if (jsonFromChildDialog === null) { + this.showAddChangeMsInstance = false; + } else { + this.msInstanceApi.addChangeMsInstance("CHANGE", this.msInstanceId, jsonFromChildDialog).subscribe( + (data) => { + this.updateCurrentRow(data); + this.messageService.add({ key: 'changeSuccess', severity: 'success', summary: 'Success', detail: "MS Instance Updated", life: 5000 }); + this.showAddChangeMsInstance = false; + }, + (errResponse) => { + if (errResponse.error.message) { + this.messageService.add({ key: 'instanceAddChangeError', severity: 'error', summary: 'Error', detail: errResponse.error.message, sticky: true }); + } else { + this.messageService.add({ key: 'instanceAddChangeError', severity: 'error', summary: 'Error', detail: errResponse.error.status, sticky: true }); + } + } + ) + } + } + + updateCurrentRow(responseData) { + const newRow = responseData; + this.currentRow['release'] = newRow['release']; + this.currentRow['metadata']['scrumLead'] = newRow['metadata']['scrumLead']; + this.currentRow['metadata']['scrumLeadId'] = newRow['metadata']['scrumLeadId']; + this.currentRow['metadata']['systemsEngineer'] = newRow['metadata']['systemsEngineer']; + this.currentRow['metadata']['systemsEngineerId'] = newRow['metadata']['systemsEngineerId']; + this.currentRow['metadata']['developer'] = newRow['metadata']['developer']; + this.currentRow['metadata']['developerId'] = newRow['metadata']['developerId']; + this.currentRow['pstDueDate'] = this.datePipe.transform(newRow['metadata']['pstDueDate'], 'yyyy-MM-dd'); + this.currentRow['pstDueIteration'] = newRow['metadata']['pstDueIteration']; + this.currentRow['eteDueDate'] = this.datePipe.transform(newRow['metadata']['eteDueDate'], 'yyyy-MM-dd'); + this.currentRow['eteDueIteration'] = newRow['metadata']['eteDueIteration']; + this.currentRow['metadata']['labels'] = newRow['metadata']['labels']; + this.currentRow['metadata']['notes'] = newRow['metadata']['notes']; + this.currentRow['metadata']['updatedBy'] = newRow['metadata']['updatedBy']; + this.currentRow['metadata']['updatedOn'] = this.datePipe.transform(newRow['metadata']['updatedOn'], 'MM-dd-yyyy HH:mm'); + } + + // * * * * * Show the Dialog to Add a CS (in the html) * * * * * + // * * * * * Store the MS Instance ID for the URL and the "current row" to update when a CS is saved * * * * * + showAddCSDialog(rowData) { + this.showCsAddDialog = true; + this.msInstanceId = rowData['id']; + this.currentRow = rowData; + } + + // * * * * * Add a CS * * * * * + addNewCs(jsonFromChildDialog) { + let compSpecAddMessage = ''; + if (jsonFromChildDialog) { + this.csAddJson = jsonFromChildDialog; + if((JSON.parse(this.csAddJson)).policyJson === null){ + compSpecAddMessage = 'Component Spec Added'; + } else { + console.log("here") + compSpecAddMessage = 'Component Spec and Policy added ' + } + + this.addCsApi.addCsToCatalog(this.msInstanceId, this.csAddJson).subscribe( + (response: any) => { + this.messageService.add({ key: 'compSpecAdded', severity: 'success', summary: 'Success', detail: compSpecAddMessage, life: 5000 }); + this.showCsAddDialog = false; + this.currentRow['activeSpec'] = true; + }, + errResponse => { + if (errResponse.error.errors) { + this.messageService.add({ key: 'errorOnCsAdd', severity: 'error', summary: errResponse.error.message, detail: errResponse.error.errors.join('\n'), sticky: true}); + } else { + let summary = errResponse.error.status + " - " + errResponse.error.error; + this.messageService.add({ key: 'errorOnCsAdd', severity: 'error', summary: summary, detail: errResponse.error.message, sticky: true}); + } + }); + } else { + this.showCsAddDialog = false + }; + } + +/* * * * View Component Specs +msName: string; +msRelease: string; + showViewCsDialog(data){ + this.msInstanceId = data['id'] + this.msName = data['name'] + this.msRelease = data['release'] + this.showViewCs = true; + } + csView(data){ + if(data===null){ + this.showViewCs = false + } else { + this.showViewCs = false + this.messageService.add({ key: 'csViewError', severity: 'error', summary: 'Error Message', detail: data.error.message, sticky: true }); + } + } + * * * */ + + /* * * * Generate single blueprint * * * */ + generateBlueprints(data){ + this.bpApis.postBlueprint(data['id']).subscribe((response) => { + this.messageService.add({ key: 'bpGenMessage', severity: 'success', summary: 'Success Message', detail: 'Blueprint Generated', life: 5000 }); + }, (errResponse) => { + this.messageService.add({ key: 'bpGenMessage', severity: 'error', summary: 'Error Message', detail: errResponse.error.message, life: 15000 }); + }) + } + + /* * * * Check if generate selected blueprints button should be disabled and set tooltip message * * * */ + checkCanGenerateBp() { + if (this.selectedMsInstances.length > 0) { + + let noActiveSpecs: string[] = []; + let checkReleases: boolean = true; + let firstRelease = this.selectedMsInstances[0]['release']; + for (let elem of this.selectedMsInstances) { + if (elem.release !== firstRelease){ + checkReleases = false + this.canGenerateSelectedBPs = false + this.generateSelectedBPsTooltip = 'Cannot Generate Blueprints For Different Releases' + break + } + if (elem.activeSpec === null) { + noActiveSpecs.push(elem.name) + this.generateSelectedBPsTooltip += elem.name + } + } + + if (noActiveSpecs.length < 1 && checkReleases) { + this.canGenerateSelectedBPs = true + } else if (noActiveSpecs.length > 0 && checkReleases){ + this.canGenerateSelectedBPs = false + this.generateSelectedBPsTooltip = 'No Active Specs For : ' + let i: number = 1; + for (let elem of noActiveSpecs) { + if (i === noActiveSpecs.length) { + this.generateSelectedBPsTooltip += '{' + elem + '}' + } else { + this.generateSelectedBPsTooltip += '{' + elem + '}, ' + } + i++ + } + } + } else { + this.canGenerateSelectedBPs = false + this.generateSelectedBPsTooltip = "No Instances Selected" + } + } + + /* * * * Generate multiple blueprint * * * */ + successfulBpGens: number; + generateSelectedBlueprints(){ + this.successfulBpGens = 0; + this.count = 0; + this.selectionLength = this.selectedMsInstances.length; + + this.spinnerService.show(); + + (async () => { + for (let instance of this.selectedMsInstances) { + this.bpApis.postBlueprint(instance.id).subscribe((response) => { + this.bpGenSuccess() + }, (errResponse) => { + this.bpGenError(errResponse.error.message) + }) + await timeout(1500); + } + })(); + + function timeout(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + this.selectedMsInstances = [] + } + + /* * * * For BP Gen Successes * * * */ + selectionLength: number; + count: number; + bpGenSuccess(){ + this.successfulBpGens++; + this.count++; + if (this.count === this.selectionLength){ + if(this.successfulBpGens > 0){ + this.messageService.add({ key: 'bpGenMessage', severity: 'success', summary: 'Success Message', detail: 'Blueprints Generated', life: 5000 }); + this.spinnerService.hide() + } + } + } + + /* * * * For BP Gen Errors * * * */ + bpGenError(err){ + this.count++; + if (this.count === this.selectionLength) { + if (this.successfulBpGens > 0) { + this.messageService.add({ key: 'bpGenMessage', severity: 'success', summary: 'Success Message', detail: 'Blueprints Generated', life: 5000 }); + this.spinnerService.hide() + } + } + this.messageService.add({ key: 'bpGenMessage', severity: 'error', summary: 'Error Message', detail: err, life: 15000 }); + } + + /* * * * View the Blueprints for the selected MS Instance * * * */ + viewBlueprints(rowData) { + this.router.navigate(["blueprints"], {queryParams:{tag: rowData['tag'], release:rowData['release'] }}); + this.bread.setBreadcrumbs("Blueprints", "add"); + } + + /* * * * View the Component Spec for the selected MS Instance * * * */ + viewCompSpecs(rowData) { + this.router.navigate(["CompSpecs"], { queryParams: { instanceId: rowData['id'] }}); + this.bread.setBreadcrumbs("Component Specs", "add"); + } + + /* * * * Stores filtered data in new array * * * */ + onTableFiltered(values) { + if (values) { this.filteredRows = values; } + else { this.filteredRows = this.msInstances; } + } + + /* * * * Export ms instance table to excel or csv * * * */ + exportTable(exportTo) { + let downloadElements: any[] = [] + + //labels array not handled well by excel download so converted them to a single string + for(let row of this.filteredRows){ + let labels; + let notes; + if(exportTo === "excel"){ + if(row.metadata.labels !== undefined){ + labels = row.metadata.labels.join(",") + } + } else { + labels = row.metadata.labels + } + + if (row.metadata.notes !== null && row.metadata.notes !== undefined && row.metadata.notes !== '') { + notes = encodeURI(row.metadata.notes).replace(/%20/g, " ").replace(/%0A/g, "\\n") + } + + downloadElements.push({ + MS_Name: row.name, + MS_Tag: row.tag, + Release: row.release, + PST_Due_Date: row.pstDueDate, + PST_Due_Iteration: row.pstDueIteration, + ETE_Due_Date: row.eteDueDate, + ETE_Due_Iteration: row.eteDueIteration, + Status: row.status, + Created_By: row.metadata.createdBy, + Created_On: row.metadata.createdOn, + Updated_By: row.metadata.updatedBy, + Updated_On: row.metadata.updatedOn, + Scrum_Lead: row.metadata.scrumLead, + Scrum_Lead_Id: row.metadata.scrumLeadId, + Systems_Engineer: row.metadata.systemsEngineer, + Systems_Engineer_Id: row.metadata.systemsEngineerId, + Developer: row.metadata.developer, + Developer_Id: row.metadata.developerId, + Notes: notes, + Labels: labels + }) + } + + let csvHeaders = []; + + if (exportTo === "csv") { + csvHeaders = [ + "MS_Name", + "MS_Tag", + "Release", + "PST_Due_Date", + "PST_Due_Iteration", + "ETE_Due_Date", + "ETE_Due_Iteration", + "Status", + "Created_By", + "Created_On", + "Updated_By", + "Updated_On", + "Scrum_Lead", + "Scrum_Lead_Id", + "Systems_Engineer", + "Systems_Engineer_Id", + "Developer", + "Developer_Id", + "Notes", + "Labels" + ]; + } + + this.downloadService.exportTableData(exportTo, downloadElements, csvHeaders) + } + + /* * * * Fill ms instance table * * * */ + fillTable(data) { + + for (let elem of data) { + + /* * * Now storing as dates (not strings) on DB, so need to convert old data (mm-dd-yyyy and m-d-yyyy) * * */ + let pstDueDate: any; + if (elem.metadata.pstDueDate && (elem.metadata.pstDueDate.length <= 11 && + elem.metadata.pstDueDate.length > 7)) { + pstDueDate = new Date(elem.metadata.pstDueDate.replace(/-/g, '/')) // dash is invalid date format, FF fails + pstDueDate = this.datePipe.transform(pstDueDate, 'yyyy-MM-dd') + } else if (elem.metadata.pstDueDate) { + pstDueDate = this.datePipe.transform(elem.metadata.pstDueDate, 'yyyy-MM-dd') + } else { + pstDueDate = elem.metadata.pstDueDate + } + + let eteDueDate: any; + if (elem.metadata.eteDueDate && (elem.metadata.eteDueDate.length <= 11 && + elem.metadata.eteDueDate.length > 7)) { + eteDueDate = new Date(elem.metadata.eteDueDate.replace(/-/g, '/')) // dash is invalid date format, FF fails + eteDueDate = this.datePipe.transform(eteDueDate, 'yyyy-MM-dd') + } else if (elem.metadata.eteDueDate) { + eteDueDate = this.datePipe.transform(elem.metadata.eteDueDate, 'yyyy-MM-dd') + } else { + eteDueDate = elem.metadata.eteDueDate + } + + var tempElem: msInstance = { + id: elem.id, + name: elem.name, + tag: elem.msInfo.tag, + release: elem.release, + version: elem.version, + status: elem.status, + baseMsId: elem.msInfo.id, + baseMsName: elem.msInfo.name, + metadata: { + scrumLead: elem.metadata.scrumLead, + scrumLeadId: elem.metadata.scrumLeadId, + systemsEngineer: elem.metadata.systemsEngineer, + systemsEngineerId: elem.metadata.systemsEngineerId, + developer: elem.metadata.developer, + developerId: elem.metadata.developerId, + createdBy: elem.metadata.createdBy, + createdOn: this.datePipe.transform(elem.metadata.createdOn, 'MM-dd-yyyy HH:mm'), + updatedBy: elem.metadata.updatedBy, + updatedOn: this.datePipe.transform(elem.metadata.updatedOn, 'MM-dd-yyyy HH:mm'), + notes: elem.metadata.notes, + labels: elem.metadata.labels, + }, + pstDueDate: pstDueDate, + pstDueIteration: elem.metadata.pstDueIteration, + eteDueDate: eteDueDate, + eteDueIteration: elem.metadata.eteDueIteration, + activeSpec: elem.activeSpec + } + this.msInstances.push(tempElem) + } + + this.filteredRows = this.msInstances + this.loadTable = true; + this.spinnerService.hide() + } +} + +export interface msInstance{ + id: string, + name: string, + tag: string, + release: string, + version: string, + status: string, + baseMsId: string, + baseMsName: string , + metadata: { + scrumLead: string, + scrumLeadId: string, + systemsEngineer: string, + systemsEngineerId: string, + developer: string, + developerId: string, + createdBy: string, + createdOn: string, + updatedBy: string, + updatedOn: string, + notes: string, + labels: string[], + } + pstDueDate: string, + pstDueIteration: string, + eteDueDate: string, + eteDueIteration: string, + activeSpec: any +} \ No newline at end of file diff --git a/mod2/ui/src/app/onboarding-tools/onboarding-tools.component.css b/mod2/ui/src/app/onboarding-tools/onboarding-tools.component.css new file mode 100644 index 0000000..1130851 --- /dev/null +++ b/mod2/ui/src/app/onboarding-tools/onboarding-tools.component.css @@ -0,0 +1,27 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +iframe{ + padding-top: 0px; + border: none; +} + +#content{ + position:absolute; + left: 0; right: 0; bottom: 0; top: 0px; +} \ No newline at end of file diff --git a/mod2/ui/src/app/onboarding-tools/onboarding-tools.component.html b/mod2/ui/src/app/onboarding-tools/onboarding-tools.component.html new file mode 100644 index 0000000..417ee39 --- /dev/null +++ b/mod2/ui/src/app/onboarding-tools/onboarding-tools.component.html @@ -0,0 +1,23 @@ + + + +
+ +
+ diff --git a/mod2/ui/src/app/onboarding-tools/onboarding-tools.component.spec.ts b/mod2/ui/src/app/onboarding-tools/onboarding-tools.component.spec.ts new file mode 100644 index 0000000..d88c06d --- /dev/null +++ b/mod2/ui/src/app/onboarding-tools/onboarding-tools.component.spec.ts @@ -0,0 +1,48 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { Ng4LoadingSpinnerModule } from 'ng4-loading-spinner'; +import { SafePipe } from '../onboarding-tools/onboarding-tools.component'; + +import { OnboardingToolsComponent } from './onboarding-tools.component'; + +describe('OnboardingToolsComponent', () => { + let component: OnboardingToolsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [OnboardingToolsComponent, SafePipe], + imports: [ + Ng4LoadingSpinnerModule, + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(OnboardingToolsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/mod2/ui/src/app/onboarding-tools/onboarding-tools.component.ts b/mod2/ui/src/app/onboarding-tools/onboarding-tools.component.ts new file mode 100644 index 0000000..d400120 --- /dev/null +++ b/mod2/ui/src/app/onboarding-tools/onboarding-tools.component.ts @@ -0,0 +1,52 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Component, ViewEncapsulation, ViewChild, ElementRef, PipeTransform, Pipe, OnInit } from '@angular/core'; +import { DomSanitizer } from "@angular/platform-browser"; +import { Ng4LoadingSpinnerService } from 'ng4-loading-spinner'; +import { environment } from '../../environments/environment'; + +@Pipe({ name: 'safe' }) +export class SafePipe implements PipeTransform { + constructor(private sanitizer: DomSanitizer) { } + transform(url) { + return this.sanitizer.bypassSecurityTrustResourceUrl(url); + } +} + +@Component({ + selector: 'app-onboarding-tools', + templateUrl: './onboarding-tools.component.html', + styleUrls: ['./onboarding-tools.component.css'] +}) + +export class OnboardingToolsComponent implements OnInit { + + title = 'Onboarding Tools'; + + //video: string = `http://${environment.api_baseURL}:30991/onboarding-toolbox/blueprint-generator` + video: string = 'http://dcae-onboarding-toolbox-fe.ecomp.idns.cip.att.com:30991/onboarding-toolbox/blueprint-generator' + + constructor(private spinnerService: Ng4LoadingSpinnerService) { + this.spinnerService.show(); + } + + ngOnInit() { + } + +} diff --git a/mod2/ui/src/app/register/register.component.css b/mod2/ui/src/app/register/register.component.css new file mode 100644 index 0000000..730a52d --- /dev/null +++ b/mod2/ui/src/app/register/register.component.css @@ -0,0 +1,18 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + diff --git a/mod2/ui/src/app/register/register.component.html b/mod2/ui/src/app/register/register.component.html new file mode 100644 index 0000000..74f9347 --- /dev/null +++ b/mod2/ui/src/app/register/register.component.html @@ -0,0 +1,62 @@ + + + +
+
+
+
+
+ + +
+
+
+ username should be at least 5 characters + username should be at most 10 characters +
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+ password should be at least 6 characters +
+
+
+ + +
+
+
+ + + + +
+
diff --git a/mod2/ui/src/app/register/register.component.spec.ts b/mod2/ui/src/app/register/register.component.spec.ts new file mode 100644 index 0000000..d05fcd1 --- /dev/null +++ b/mod2/ui/src/app/register/register.component.spec.ts @@ -0,0 +1,61 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { RouterTestingModule } from '@angular/router/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; +import { CardModule } from 'primeng/card'; +import { MultiSelectModule } from 'primeng/multiselect'; + +import { RegisterComponent } from './register.component'; + +describe('RegisterComponent', () => { + let component: RegisterComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [RegisterComponent], + imports: [ + FormsModule, + ReactiveFormsModule, + MultiSelectModule, + CardModule, + HttpClientTestingModule, + RouterTestingModule + ], + providers: [ + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(RegisterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/mod2/ui/src/app/register/register.component.ts b/mod2/ui/src/app/register/register.component.ts new file mode 100644 index 0000000..28b0677 --- /dev/null +++ b/mod2/ui/src/app/register/register.component.ts @@ -0,0 +1,99 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Component, OnInit } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { AuthService } from '../services/auth.service'; +import { Router } from '@angular/router'; +import { User } from '../models/User'; +import { AuthResponse } from '../models/AuthResponse'; +import {SelectItem} from 'primeng/api'; +import { UserService } from '../services/user.service'; + +@Component({ + selector: 'app-register', + templateUrl: './register.component.html', + styleUrls: ['./register.component.css'] +}) +export class RegisterComponent implements OnInit { + + user: User = { + username:'', + fullName:'', + password:'', + roles: [] + }; + form: FormGroup; + roleOptions: SelectItem[]; + rolesFromBackend = []; + + constructor(private fb: FormBuilder, private authService: AuthService, private router: Router, private userService: UserService) { } + + ngOnInit() { + this.form = this.fb.group({ + username: ['', [Validators.required, Validators.minLength(5), Validators.maxLength(10)]], + fullName: ['', [Validators.required]], + password: ['', [Validators.required, Validators.minLength(6)]], + roles:['', [Validators.required]] + }); + // this.roleOptions = [{label:'Admin', value:{name:'ROLE_ADMIN'}}, {label:'User', value:{name:'ROLE_USER'}}, {label:'ScrumLead', value:{name:'ROLE_SCRUMLEAD'}}, {label:'Developer', value:{name:'ROLE_DEVELOPER'}}, {label:'PST', value:{name:'ROLE_PST'}}, {label:'ETE', value:{name:'ROLE_ETE'}}, {label:'Ops', value:{name:'ROLE_OPS'}}]; + this.userService.getRoles().subscribe(res=>{ + Object.values(res).forEach(ele=>{ + this.rolesFromBackend.push({label:ele.substring(5), value:ele}); + }); + }); + } + + generateNewPassword() { + this.form.value.password = ''; + const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&'; + var array = new Uint32Array(32); + window.crypto.getRandomValues(array); + for(let i=0;i<32;i++) { + const index = Math.floor(array[i] % chars.length); + this.form.value.password += chars.charAt(index); + } + this.form.patchValue({password: this.form.value.password}); + } + + cancel() { + const result = window.confirm("Are you sure to quit registering current user and go back to user management?"); + if(result === true){ + this.router.navigate(['/users']); + } + } + + submit() { + this.user.fullName = this. form.value.fullName; + this.user.username = this.form.value.username; + this.user.password = this.form.value.password; + this.user.roles = this.form.value.roles; + console.log(this.user.roles); + this.authService.register(this.user) + .subscribe( (res: AuthResponse) => { + alert(res.message); + this.router.navigate(['/users']); + }, (err) => { + console.log(err); + alert(err.error.message); + this.form.reset(); + } + ); + } + +} diff --git a/mod2/ui/src/app/reset-password/reset-password.component.css b/mod2/ui/src/app/reset-password/reset-password.component.css new file mode 100644 index 0000000..730a52d --- /dev/null +++ b/mod2/ui/src/app/reset-password/reset-password.component.css @@ -0,0 +1,18 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + diff --git a/mod2/ui/src/app/reset-password/reset-password.component.html b/mod2/ui/src/app/reset-password/reset-password.component.html new file mode 100644 index 0000000..748216a --- /dev/null +++ b/mod2/ui/src/app/reset-password/reset-password.component.html @@ -0,0 +1,64 @@ + + + +
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+ +
+
+ + +
+
+
+ + + + + +
+
diff --git a/mod2/ui/src/app/reset-password/reset-password.component.spec.ts b/mod2/ui/src/app/reset-password/reset-password.component.spec.ts new file mode 100644 index 0000000..a26d90b --- /dev/null +++ b/mod2/ui/src/app/reset-password/reset-password.component.spec.ts @@ -0,0 +1,58 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { RouterTestingModule } from '@angular/router/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; +import { CardModule } from 'primeng/card'; + +import { ResetPasswordComponent } from './reset-password.component'; + +describe('ResetPasswordComponent', () => { + let component: ResetPasswordComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ResetPasswordComponent], + imports: [ + FormsModule, + ReactiveFormsModule, + CardModule, + HttpClientTestingModule, + RouterTestingModule + ], + providers: [ + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ResetPasswordComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/mod2/ui/src/app/reset-password/reset-password.component.ts b/mod2/ui/src/app/reset-password/reset-password.component.ts new file mode 100644 index 0000000..b704b40 --- /dev/null +++ b/mod2/ui/src/app/reset-password/reset-password.component.ts @@ -0,0 +1,78 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Component, OnInit } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { AuthService } from '../services/auth.service'; +import { Router } from '@angular/router'; +import { User } from '../models/User'; + +@Component({ + selector: 'app-reset-password', + templateUrl: './reset-password.component.html', + styleUrls: ['./reset-password.component.css'] +}) +export class ResetPasswordComponent implements OnInit { + + form: FormGroup; + + constructor(private fb: FormBuilder, public authService: AuthService, private router: Router) { } + + ngOnInit() { + this.form = this.fb.group({ + id: ['', [Validators.required]], + password: '', + newPassword: '', + confirm_newPassword: '' + }); + } + + cancel() { + this.form.reset(); + } + + generateNewPassword() { + this.form.value.password = ''; + let p = ''; + const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&'; + for(let i=1;i<12;i++) { + const index = Math.floor(Math.random() * chars.length + 1); + this.form.value.password += chars.charAt(index); + } + this.form.patchValue({password: this.form.value.password}); + } + + submit() { + const id = this.form.value.id; + if(!this.authService.isAdmin){ + if(this.form.value.newPassword === this.form.value.confirm_newPassword){ + const password = this.form.value.newPassword; + console.log(id); + console.log(password); // toJane: need to call user API + } else { + alert('Your passwords do not match, please re-confirm!'); + this.form.patchValue({newPassword: '', confirm_newPassword: ''}); + } + } else { + const password = this.form.value.password; + console.log(id); + console.log(password);// toJane: need to call user API + } +} + +} diff --git a/mod2/ui/src/app/services/auth.service.spec.ts b/mod2/ui/src/app/services/auth.service.spec.ts new file mode 100644 index 0000000..eecc055 --- /dev/null +++ b/mod2/ui/src/app/services/auth.service.spec.ts @@ -0,0 +1,43 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; + +import { AuthService } from './auth.service'; + +describe('AuthService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ], + imports: [ + HttpClientTestingModule, + RouterTestingModule + ] + }); + }); + it('should be created', () => { + const service: AuthService = TestBed.get(AuthService); + expect(service).toBeTruthy(); + }); +}); diff --git a/mod2/ui/src/app/services/auth.service.ts b/mod2/ui/src/app/services/auth.service.ts new file mode 100644 index 0000000..49bb5f1 --- /dev/null +++ b/mod2/ui/src/app/services/auth.service.ts @@ -0,0 +1,95 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHandler, HttpHeaders } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { tap } from 'rxjs/internal/operators'; +import { User } from '../models/User'; +import { JwtHelperService } from '@auth0/angular-jwt'; +import { AuthResponse } from '../models/AuthResponse'; +import { Router } from '@angular/router'; +import { environment } from '../../environments/environment'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthService{ + + private user: User = { + username: '', + roles:[] + }; + authPass=false; + isAdmin=false; + reLoginMsg = false; + + constructor( + private http:HttpClient, + private jwtHelper: JwtHelperService, + private router: Router + ) { + + } + + register(user: User): Observable { + return this.http.post(`http://${environment.api_baseURL}:31003/api/auth/signup`, user); + // return this.http.post(`http://localhost:8082/api/auth/signup`, user); + + } + + login(user: User): Observable { + return this.http.post(`http://${environment.api_baseURL}:31003/api/auth/signin`, user) + .pipe( + tap((res: AuthResponse) => { + localStorage.setItem('token', res.token); + this.setUser(); + this.authPass = true; + }) + ); + } + + setUser() { + this.user.username = this.jwtHelper.decodeToken(localStorage.getItem('token')).sub; + this.user.roles = this.jwtHelper.decodeToken(localStorage.getItem('token')).roles; + this.user.fullName = this.jwtHelper.decodeToken(localStorage.getItem('token')).fullName; + + } + + checkLogin(): Observable{ + return this.http.post(`http://${environment.api_baseURL}:31003/api/auth/validate-token`, null); + } + + getJwt() { + if(localStorage.getItem('token')){ + return localStorage.getItem('token'); + } + return ""; + } + + logout() { + localStorage.removeItem('token'); + this.authPass = false; + this.router.navigate(['/login']); + } + + getUser(): User { + return this.user; + } + +} diff --git a/mod2/ui/src/app/services/base-microservice.service.spec.ts b/mod2/ui/src/app/services/base-microservice.service.spec.ts new file mode 100644 index 0000000..1cef5bd --- /dev/null +++ b/mod2/ui/src/app/services/base-microservice.service.spec.ts @@ -0,0 +1,42 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, TestBed } from '@angular/core/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; + +import { BaseMicroserviceService } from './base-microservice.service'; + +describe('BaseMicroserviceService', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + ], + providers: [ + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }).compileComponents(); + })); + + it('should be created', () => { + const service: BaseMicroserviceService = TestBed.get(BaseMicroserviceService); + expect(service).toBeTruthy(); + }); +}); diff --git a/mod2/ui/src/app/services/base-microservice.service.ts b/mod2/ui/src/app/services/base-microservice.service.ts new file mode 100644 index 0000000..a5c64b1 --- /dev/null +++ b/mod2/ui/src/app/services/base-microservice.service.ts @@ -0,0 +1,35 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Injectable } from '@angular/core'; +import { environment } from '../../environments/environment'; +import { HttpClient } from '@angular/common/http'; + +@Injectable({ + providedIn: 'root' +}) +export class BaseMicroserviceService { + + private url: string = `http://${environment.api_baseURL}:31001/api/`; + + constructor(private http: HttpClient) { } + + getAllBaseMs() { + return this.http.get(this.url + "base-microservice"); + } +} diff --git a/mod2/ui/src/app/services/breadcrumb.service.ts b/mod2/ui/src/app/services/breadcrumb.service.ts new file mode 100644 index 0000000..aa647aa --- /dev/null +++ b/mod2/ui/src/app/services/breadcrumb.service.ts @@ -0,0 +1,73 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Injectable } from '@angular/core'; +import { Observable, of, Subject, BehaviorSubject } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) + +export class BreadcrumbService { + + breadcrumbs: any[] = []; + + breadcrumbs$: Subject = new BehaviorSubject([]); + + constructor() { + this.setBreadcrumbs('', "reset"); + } + + setBreadcrumbs(component: string, action: string) { + if (action == "reset") { + this.breadcrumbs = []; + this.breadcrumbs.push({ page: 'Home', link: '/home' }); + } + + // If the breadcrumb item is clicked, remove evwrything to the right of the clicked item + if (action == "crumbClicked") { + const pos = this.breadcrumbs.map(function(crumb) { return crumb.page }).indexOf(component); + for (1; this.breadcrumbs.length -1 -pos; 1) { + this.breadcrumbs.pop() + } + } else { // Add the component that was selected + if (component == 'Microservices') { + this.breadcrumbs.push({ page: 'Microservices', link: '/base-microservices' }); + } else if (component == 'MS Instances') { + this.breadcrumbs.push({ page: 'MS Instances', link: '/ms-instances' }); + } else if (component == 'Blueprints') { + this.breadcrumbs.push({ page: 'Blueprints', link: '/blueprints' }); + } else if (component == 'Component Specs') { + this.breadcrumbs.push({ page: 'Component Specs', link: '/CompSpecs' }); + } else if (component == 'User Management') { + this.breadcrumbs.push({ page: 'User Management', link: '/users' }); + } else if (component == 'Onboarding Tools') { + this.breadcrumbs.push({ page: 'Onboarding Tools', link: '/OnboardingTools' }); + } else if (component == 'Spec Validator') { + this.breadcrumbs.push({ page: 'Spec Validator', link: '/spec-validator' }); + } + } + this.notifySubscriber() + } + + + notifySubscriber() { + this.breadcrumbs$.next(this.breadcrumbs); + } +} + diff --git a/mod2/ui/src/app/services/comp-spec-add.service.spec.ts b/mod2/ui/src/app/services/comp-spec-add.service.spec.ts new file mode 100644 index 0000000..f7f5913 --- /dev/null +++ b/mod2/ui/src/app/services/comp-spec-add.service.spec.ts @@ -0,0 +1,42 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, TestBed } from '@angular/core/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; + +import { CompSpecAddService } from './comp-spec-add.service'; + +describe('CompSpecAddService', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + ], + providers: [ + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }).compileComponents(); + })); + + it('should be created', () => { + const service: CompSpecAddService = TestBed.get(CompSpecAddService); + expect(service).toBeTruthy(); + }); +}); diff --git a/mod2/ui/src/app/services/comp-spec-add.service.ts b/mod2/ui/src/app/services/comp-spec-add.service.ts new file mode 100644 index 0000000..d269ac4 --- /dev/null +++ b/mod2/ui/src/app/services/comp-spec-add.service.ts @@ -0,0 +1,45 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { environment } from '../../environments/environment'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) + +export class CompSpecAddService { + + private URL: string = `http://${environment.api_baseURL}:31001/api/specification/`; + + constructor(private http: HttpClient) { } + + addCsToCatalog(msInstanceId: string, addCsJson: any): Observable { + let url = this.URL + msInstanceId; + let body = addCsJson; + let headers = new HttpHeaders({ + 'Content-Type': 'application/json' + }); + + let options = {headers:headers} + return this.http.post(url, body, options); + } + +} diff --git a/mod2/ui/src/app/services/comp-specs-service.service.spec.ts b/mod2/ui/src/app/services/comp-specs-service.service.spec.ts new file mode 100644 index 0000000..58cf0a2 --- /dev/null +++ b/mod2/ui/src/app/services/comp-specs-service.service.spec.ts @@ -0,0 +1,43 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; + +import { compSpecsService } from './comp-specs-service.service'; + +describe('CompSpecsServiceService', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + RouterTestingModule + ], + providers: [ + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }).compileComponents(); + })); + it('should be created', () => { + const service: compSpecsService = TestBed.get(compSpecsService); + expect(service).toBeTruthy(); + }); +}); diff --git a/mod2/ui/src/app/services/comp-specs-service.service.ts b/mod2/ui/src/app/services/comp-specs-service.service.ts new file mode 100644 index 0000000..6fcd92c --- /dev/null +++ b/mod2/ui/src/app/services/comp-specs-service.service.ts @@ -0,0 +1,43 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from '../../environments/environment'; + +@Injectable({ + providedIn: 'root' +}) +export class compSpecsService { + + private baseUrl: string = `http://${environment.api_baseURL}:31001/api/specification/`; + constructor(private http: HttpClient) { + } + + getAllCompSpecs(instanceId) { + let url = this.baseUrl + instanceId + + return this.http.get(url); + } + + post(msId, body){ + let url: string = this.baseUrl + msId + + return this.http.post(url, body) + } +} diff --git a/mod2/ui/src/app/services/deployment-artifact.service.spec.ts b/mod2/ui/src/app/services/deployment-artifact.service.spec.ts new file mode 100644 index 0000000..f392732 --- /dev/null +++ b/mod2/ui/src/app/services/deployment-artifact.service.spec.ts @@ -0,0 +1,43 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; + +import { DeploymentArtifactService } from './deployment-artifact.service'; + +describe('DeploymentArtifactService', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + RouterTestingModule + ], + providers: [ + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }).compileComponents(); + })); + it('should be created', () => { + const service: DeploymentArtifactService = TestBed.get(DeploymentArtifactService); + expect(service).toBeTruthy(); + }); +}); diff --git a/mod2/ui/src/app/services/deployment-artifact.service.ts b/mod2/ui/src/app/services/deployment-artifact.service.ts new file mode 100644 index 0000000..e344bff --- /dev/null +++ b/mod2/ui/src/app/services/deployment-artifact.service.ts @@ -0,0 +1,71 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from '../../environments/environment'; +import { AuthService } from './auth.service'; + +@Injectable({ + providedIn: 'root' +}) +export class DeploymentArtifactService { + + private URL: string = `http://${environment.api_baseURL}:31001/api/deployment-artifact/`; + private username = this.authService.getUser().username; + private url_User = "?user=" + this.username; + + constructor(private http: HttpClient, private authService: AuthService) { } + + getAllBlueprints() { + return this.http.get(this.URL); + } + + getStatuses() { + let url = this.URL + 'statuses' + + return this.http.get(url) + } + + patchBlueprintStatus(update, bpId){ + let url = this.URL + bpId + "?user=" + this.username + let status: string = update + let body = { + status: status + } + + return this.http.patch(url, body) + } + + postBlueprint(instanceID: string){ + let url = '' + + url = this.URL + instanceID + "?user=" + this.username + console.log(url) + + return this.http.post(url, '') + } + + deleteBlueprint(instanceID: string){ + console.log(instanceID) + let url = this.URL + instanceID + this.url_User + + return this.http.delete(url) + } + +} diff --git a/mod2/ui/src/app/services/download.service.spec.ts b/mod2/ui/src/app/services/download.service.spec.ts new file mode 100644 index 0000000..1df7bfd --- /dev/null +++ b/mod2/ui/src/app/services/download.service.spec.ts @@ -0,0 +1,41 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, TestBed } from '@angular/core/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; + +import { DownloadService } from './download.service'; + +describe('DownloadService', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + ], + providers: [ + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }).compileComponents(); + })); + it('should be created', () => { + const service: DownloadService = TestBed.get(DownloadService); + expect(service).toBeTruthy(); + }); +}); diff --git a/mod2/ui/src/app/services/download.service.ts b/mod2/ui/src/app/services/download.service.ts new file mode 100644 index 0000000..619eff7 --- /dev/null +++ b/mod2/ui/src/app/services/download.service.ts @@ -0,0 +1,85 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Injectable } from '@angular/core'; +import * as saveAs from 'file-saver'; + +@Injectable({ + providedIn: 'root' +}) +export class DownloadService { + + constructor() { } + + /* * * * Download json file * * * */ + downloadJSON(content, fileName){ + let file = new Blob([JSON.stringify(content)], { type: 'text;charset=utf-8' }); + let name: string = `${fileName}.json` + saveAs(file, name) + } + + /* * * * Export ms instance table to excel or csv * * * */ + exportTableData(exportTo, downloadElements, arrHeader) { + if (exportTo === "excel") { + import("xlsx").then(xlsx => { + const worksheet = xlsx.utils.json_to_sheet(downloadElements); + const workbook = { Sheets: { 'data': worksheet }, SheetNames: ['data'] }; + const excelBuffer: any = xlsx.write(workbook, { bookType: 'xlsx', type: 'array' }); + this.saveAsExcelFile(excelBuffer, "Table_Data"); + }); + } else if (exportTo === "csv") { + let csvData = this.convertToCSV(downloadElements, arrHeader) + var blob = new Blob([csvData], { type: 'text/csv' }) + saveAs(blob, "Table_Data.csv"); + } + } + saveAsExcelFile(buffer: any, fileName: string): void { + import("file-saver").then(FileSaver => { + let EXCEL_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8'; + let EXCEL_EXTENSION = '.xlsx'; + const data: Blob = new Blob([buffer], { + type: EXCEL_TYPE + }); + FileSaver.saveAs(data, fileName + '_export_' + new Date().getTime() + EXCEL_EXTENSION); + }); + } + convertToCSV(objArray, headerList) { + let array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray; + let str = ''; + let row = ''; + for (let index in headerList) { + row += headerList[index] + ','; + } + row = row.slice(0, -1); + str += row + '\r\n'; + for (let i = 0; i < array.length; i++) { + let line = ''; + for (let index in headerList) { + let head = headerList[index]; + if (array[i][head] === null || array[i][head] === undefined) { + line += ',' + } else { + if (head === "Labels" && array[i][head].length > 1) { line += '[' + array[i][head].join('] [') + '],'; } + else { line += array[i][head] + ','; } + } + } + str += line + '\r\n'; + } + return str; + } +} diff --git a/mod2/ui/src/app/services/global-filters.service.spec.ts b/mod2/ui/src/app/services/global-filters.service.spec.ts new file mode 100644 index 0000000..cadfd3f --- /dev/null +++ b/mod2/ui/src/app/services/global-filters.service.spec.ts @@ -0,0 +1,30 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { TestBed } from '@angular/core/testing'; + +import { GlobalFiltersService } from './global-filters.service'; + +describe('GlobalFiltersService', () => { + beforeEach(() => TestBed.configureTestingModule({})); + + it('should be created', () => { + const service: GlobalFiltersService = TestBed.get(GlobalFiltersService); + expect(service).toBeTruthy(); + }); +}); diff --git a/mod2/ui/src/app/services/global-filters.service.ts b/mod2/ui/src/app/services/global-filters.service.ts new file mode 100644 index 0000000..0937f92 --- /dev/null +++ b/mod2/ui/src/app/services/global-filters.service.ts @@ -0,0 +1,55 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class GlobalFiltersService { + + //global filter values + instanceName: string; + instanceTag: string; + release: string; + status: string; + + shouldFilter: boolean = false; + + constructor() { } + + setFilters(filters){ + this.instanceName = filters.instanceName + this.instanceTag = filters.instanceTag + this.release = filters.release + this.status = filters.status + } + + getFilters(){ + let globalFilters = {instanceName: this.instanceName, instanceTag: this.instanceTag, release: this.release, status: this.status} + return globalFilters + } + + checkShouldFilter(){ + return this.shouldFilter + } + + setShouldFilter(){ + this.shouldFilter = !this.shouldFilter + } +} diff --git a/mod2/ui/src/app/services/jwt-interceptor.service.spec.ts b/mod2/ui/src/app/services/jwt-interceptor.service.spec.ts new file mode 100644 index 0000000..6fea9ba --- /dev/null +++ b/mod2/ui/src/app/services/jwt-interceptor.service.spec.ts @@ -0,0 +1,30 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { TestBed } from '@angular/core/testing'; + +import { JwtInterceptorService } from './jwt-interceptor.service'; + +describe('JwtInterceptorService', () => { + beforeEach(() => TestBed.configureTestingModule({})); + + it('should be created', () => { + const service: JwtInterceptorService = TestBed.get(JwtInterceptorService); + expect(service).toBeTruthy(); + }); +}); diff --git a/mod2/ui/src/app/services/jwt-interceptor.service.ts b/mod2/ui/src/app/services/jwt-interceptor.service.ts new file mode 100644 index 0000000..2647583 --- /dev/null +++ b/mod2/ui/src/app/services/jwt-interceptor.service.ts @@ -0,0 +1,40 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Injectable, Injector } from '@angular/core'; +import { HttpInterceptor } from '@angular/common/http'; +import { AuthService } from './auth.service'; + +@Injectable({ + providedIn: 'root' +}) +export class JwtInterceptorService implements HttpInterceptor{ + + constructor(private injector: Injector) { } + + intercept(req: import("@angular/common/http").HttpRequest, next: import("@angular/common/http").HttpHandler): import("rxjs").Observable> { + let authService = this.injector.get(AuthService); + let jwtReq = req.clone({ + setHeaders: { + Authorization: `Bearer ${authService.getJwt()}` + } + }) + return next.handle(jwtReq); + } + +} diff --git a/mod2/ui/src/app/services/microservice-instance.service.spec.ts b/mod2/ui/src/app/services/microservice-instance.service.spec.ts new file mode 100644 index 0000000..b3c4074 --- /dev/null +++ b/mod2/ui/src/app/services/microservice-instance.service.spec.ts @@ -0,0 +1,41 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, TestBed } from '@angular/core/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; + +import { MicroserviceInstanceService } from './microservice-instance.service'; + +describe('MicroserviceInstanceService', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + ], + providers: [ + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }).compileComponents(); + })); + it('should be created', () => { + const service: MicroserviceInstanceService = TestBed.get(MicroserviceInstanceService); + expect(service).toBeTruthy(); + }); +}); diff --git a/mod2/ui/src/app/services/microservice-instance.service.ts b/mod2/ui/src/app/services/microservice-instance.service.ts new file mode 100644 index 0000000..96cce77 --- /dev/null +++ b/mod2/ui/src/app/services/microservice-instance.service.ts @@ -0,0 +1,46 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Injectable } from '@angular/core'; +import { environment } from '../../environments/environment'; +import { HttpClient } from '@angular/common/http'; +import { AddMsInstance } from '../microservices/microservices.component'; + +@Injectable({ + providedIn: 'root' +}) +export class MicroserviceInstanceService { + + private url: string = `http://${environment.api_baseURL}:31001/api/microservice-instance/`; + + constructor(private http: HttpClient) { } + + getAllMsInstances() { + return this.http.get(this.url); + } + + addChangeMsInstance(addOrChange: string, msNameOrId: string, body: AddMsInstance){ + let URL = this.url + msNameOrId + + if (addOrChange == "ADD") { + return this.http.post(URL, body); + } else { + return this.http.patch(URL, body); + } + } +} diff --git a/mod2/ui/src/app/services/ms-add.service.spec.ts b/mod2/ui/src/app/services/ms-add.service.spec.ts new file mode 100644 index 0000000..dc53a56 --- /dev/null +++ b/mod2/ui/src/app/services/ms-add.service.spec.ts @@ -0,0 +1,42 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, TestBed } from '@angular/core/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; + +import { MsAddService } from './ms-add.service'; + +describe('MsAddService', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + ], + providers: [ + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }).compileComponents(); + })); + + it('should be created', () => { + const service: MsAddService = TestBed.get(MsAddService); + expect(service).toBeTruthy(); + }); +}); diff --git a/mod2/ui/src/app/services/ms-add.service.ts b/mod2/ui/src/app/services/ms-add.service.ts new file mode 100644 index 0000000..367799d --- /dev/null +++ b/mod2/ui/src/app/services/ms-add.service.ts @@ -0,0 +1,52 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { environment } from '../../environments/environment'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) + + + +export class MsAddService { + + private URL: string = `http://${environment.api_baseURL}:31001/api/base-microservice`; + + constructor(private http: HttpClient) { } + + addChangeMsToCatalog(addOrChange, msID, addChangeMsJson): Observable { + let url = this.URL; + let headers = new HttpHeaders({'Content-Type': 'application/json'}); + let options = {headers:headers}; + let body; + + body = addChangeMsJson; + + if (addOrChange == "Add") { + return this.http.post(url, body, options); + } else { + url = url + "/" + msID; + return this.http.patch(url, body, options); + } + } + +} diff --git a/mod2/ui/src/app/services/spec-validation.service.spec.ts b/mod2/ui/src/app/services/spec-validation.service.spec.ts new file mode 100644 index 0000000..e83f832 --- /dev/null +++ b/mod2/ui/src/app/services/spec-validation.service.spec.ts @@ -0,0 +1,41 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, TestBed } from '@angular/core/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; + +import { SpecValidationService } from './spec-validation.service'; + +describe('SpecValidationService', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + ], + providers: [ + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }).compileComponents(); + })); + it('should be created', () => { + const service: SpecValidationService = TestBed.get(SpecValidationService); + expect(service).toBeTruthy(); + }); +}); diff --git a/mod2/ui/src/app/services/spec-validation.service.ts b/mod2/ui/src/app/services/spec-validation.service.ts new file mode 100644 index 0000000..2785efe --- /dev/null +++ b/mod2/ui/src/app/services/spec-validation.service.ts @@ -0,0 +1,53 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Injectable } from '@angular/core'; +import { environment } from '../../environments/environment'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; + +@Injectable({ + providedIn: 'root' +}) +export class SpecValidationService { + + private URL: string = 'http://zlecdyh2adcc1s2dokr04.1463c9.dyh2a.tci.att.com:31002/'; + + constructor(private http: HttpClient) { } + + sendSpecFile(specContent, type, release){ + + let url = '' + + if(release === "2007"){ + url = `${this.URL}v6/api/comp_spec_validator/` + } + + let body = { + compspec_content: specContent, + type: type + } + + return this.http.post(url, body) + } + + getSchema(type){ + let url = `${this.URL}v6/api/schema/${type}` + + return this.http.get(url) + } +} diff --git a/mod2/ui/src/app/services/user.service.spec.ts b/mod2/ui/src/app/services/user.service.spec.ts new file mode 100644 index 0000000..979e9fa --- /dev/null +++ b/mod2/ui/src/app/services/user.service.spec.ts @@ -0,0 +1,44 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, TestBed } from '@angular/core/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; + +import { UserService } from './user.service'; + +describe('UserService', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule + ], + providers: [ + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }).compileComponents(); + })); + + beforeEach(() => TestBed.configureTestingModule({})); + + it('should be created', () => { + const service: UserService = TestBed.get(UserService); + expect(service).toBeTruthy(); + }); +}); diff --git a/mod2/ui/src/app/services/user.service.ts b/mod2/ui/src/app/services/user.service.ts new file mode 100644 index 0000000..ec1d503 --- /dev/null +++ b/mod2/ui/src/app/services/user.service.ts @@ -0,0 +1,53 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { User } from '../models/User'; +import { AuthResponse } from '../models/AuthResponse'; +import { environment } from '../../environments/environment'; + +@Injectable({ + providedIn: 'root' +}) +export class UserService { + + constructor(private http: HttpClient) {} + + getUsers(): Observable { + return this.http.get(`http://${environment.api_baseURL}:31003/api/users/getAll`); + } + + editUser(username: string, user: User): Observable{ + return this.http.patch(`http://${environment.api_baseURL}:31003/api/users/admin/${username}`, user); + } + + editProfile(username: string, user: User): Observable{ + return this.http.patch(`http://${environment.api_baseURL}:31003/api/users/user/${username}`, user); + } + + deleteUser(username: string): Observable<{message:string}> { + return this.http.delete<{message: string}>(`http://${environment.api_baseURL}:31003/api/users/${username}`); + } + + getRoles() { + return this.http.get(`http://${environment.api_baseURL}:31003/api/roles`); + } + +} diff --git a/mod2/ui/src/app/shared/shared-module.ts b/mod2/ui/src/app/shared/shared-module.ts new file mode 100644 index 0000000..7ab21b6 --- /dev/null +++ b/mod2/ui/src/app/shared/shared-module.ts @@ -0,0 +1,61 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { NgModule } from '@angular/core'; +import {MatSelectModule} from '@angular/material/select'; +import { FormsModule } from '@angular/forms'; +import {MatTableModule} from '@angular/material/table'; +import { MatFormFieldModule, MatInputModule } from '@angular/material'; +import { MatPaginatorModule } from '@angular/material'; +//import { MatSort } from '@angular/material'; +import { MatToolbarModule, MatIconModule, MatSidenavModule, MatListModule, MatButtonModule } from '@angular/material'; + +@NgModule({ + declarations: [ + ], + imports: [ + MatSelectModule, + FormsModule, + MatToolbarModule, + MatIconModule, + MatSidenavModule, + MatListModule, + MatButtonModule, + MatTableModule, + MatPaginatorModule, + MatFormFieldModule, + MatInputModule + //MatSort + + ], + exports: [ + MatSelectModule, + FormsModule, + MatToolbarModule, + MatIconModule, + MatSidenavModule, + MatListModule, + MatButtonModule, + MatTableModule, + MatPaginatorModule, + MatFormFieldModule, + MatInputModule + // MatSort + ] + }) + export class SharedModule { } \ No newline at end of file diff --git a/mod2/ui/src/app/user-management/user-management.component.css b/mod2/ui/src/app/user-management/user-management.component.css new file mode 100644 index 0000000..495d0f8 --- /dev/null +++ b/mod2/ui/src/app/user-management/user-management.component.css @@ -0,0 +1,54 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +.pi-trash:hover { + color:red; +} +.pi-pencil:hover { + color:blue; +} + +label { + cursor: pointer; +} + +.input{ + padding-top: 10px; +} + +.inputLabel { + font-weight: 600; + margin-left: 20px; + width: 140px; +} + +.inputFieldSm { + width: 200px; + height: 35px; + padding-left: 6px; +} +.inputFieldMed { + width: 300px; + height: 35px; + padding-left: 6px; +} +.inputFieldLg { + width: 400px; + height: 35px; + padding-left: 6px; +} \ No newline at end of file diff --git a/mod2/ui/src/app/user-management/user-management.component.html b/mod2/ui/src/app/user-management/user-management.component.html new file mode 100644 index 0000000..6885fe1 --- /dev/null +++ b/mod2/ui/src/app/user-management/user-management.component.html @@ -0,0 +1,89 @@ + + +
+ + +
System User List and Management ( for admin only )
+
+ Want to register a new user? Click here! +
+ + + Username (ATT UID) + Full Name + Roles + + Actions + + + + + {{user.username}} + {{user.fullName}} + {{user.roles}} + + + + + + + +
+ + + + + Edit User Information + + +
+ +
+   + {{editUser.username}} +
+ +
+   + +
+ +
+   + + +
+ +
+
+   + + +
+
+ password should be at least 6 characters + +
+   + +
+
+
+
\ No newline at end of file diff --git a/mod2/ui/src/app/user-management/user-management.component.spec.ts b/mod2/ui/src/app/user-management/user-management.component.spec.ts new file mode 100644 index 0000000..2cb6a6e --- /dev/null +++ b/mod2/ui/src/app/user-management/user-management.component.spec.ts @@ -0,0 +1,63 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { RouterTestingModule } from '@angular/router/testing'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; +import { DialogModule } from 'primeng/dialog'; +import { MultiSelectModule } from 'primeng/multiselect'; +import { TableModule } from 'primeng/table'; + +import { UserManagementComponent } from './user-management.component'; + +describe('UserManagementComponent', () => { + let component: UserManagementComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [UserManagementComponent], + imports: [ + TableModule, + DialogModule, + MultiSelectModule, + FormsModule, + ReactiveFormsModule, + HttpClientTestingModule, + RouterTestingModule + ], + providers: [ + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(UserManagementComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/mod2/ui/src/app/user-management/user-management.component.ts b/mod2/ui/src/app/user-management/user-management.component.ts new file mode 100644 index 0000000..b7ee6e7 --- /dev/null +++ b/mod2/ui/src/app/user-management/user-management.component.ts @@ -0,0 +1,149 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import { Component, OnInit, ViewChild, ChangeDetectionStrategy } from '@angular/core'; +import { User } from '../models/User'; +import { UserService } from '../services/user.service'; +import { Router } from '@angular/router'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { SelectItem } from 'primeng/api'; +import { AuthService } from '../services/auth.service'; + +@Component({ + selector: 'app-user-management', + templateUrl: './user-management.component.html', + styleUrls: ['./user-management.component.css'] +}) +export class UserManagementComponent implements OnInit { + + users: User[] = []; + editUser: User = { + username:'', + fullName:'', + roles: [] + }; + editUserFlag: boolean = false; + editUserForm: FormGroup; + rolesFromBackend = []; + selectedRoles : Array= []; + + constructor(private userService: UserService, private router: Router, private fb: FormBuilder, private authService: AuthService) { } + + ngOnInit() { + this.userService.getUsers().subscribe((res: User[]) => { + this.users = res; + this.users.map(user=>{ + let tempRoles = []; + user.roles.map(role=>{ + tempRoles.push(role.name.substring(5)); + }); + user.roles = tempRoles; + }); + + }); + this.editUserForm = this.fb.group({ + username: '', + fullName: '', + password: [null, [ Validators.minLength(6)]], + roles: [this.selectedRoles, [Validators.required]] + }); + this.userService.getRoles().subscribe(res=>{ + Object.values(res).forEach(ele=>{ + this.rolesFromBackend.push({label:ele.substring(5), value:ele}); + }); + }); + } + + handleDelete(username) { + const result = window.confirm('Are you sure to delete this user?'); + if(result === true) { + this.userService.deleteUser(username).subscribe(res=>{ + alert(res.message); + this.userService.getUsers().subscribe( r => { + this.users = r; + this.users.map(user=>{ + let tempRoles = []; + user.roles.map(role=>{ + tempRoles.push(role.name.substring(5)); + }); + user.roles = tempRoles; + }); + }); + }); + } + } + + handleEdit(user) { + this.selectedRoles = []; + this.editUserFlag = true; + this.editUser = user; + this.editUserForm.get('username').setValue(user.username); + this.editUserForm.get('fullName').setValue(user.fullName); + user.roles.map(ele => { + let temp = "ROLE_" + ele; + this.selectedRoles.push(temp); + }) + + this.editUserForm.get('roles').setValue(this.selectedRoles); + + } + + closeEditDialog() { + this.editUserForm.reset(); + this.editUserFlag = false; + } + + generateNewPassword() { + this.editUserForm.value.password = ''; + const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&'; + var array = new Uint32Array(32); + window.crypto.getRandomValues(array); + for(let i=0;i<32;i++) { + const index = Math.floor(array[i] % chars.length); + this.editUserForm.value.password += chars.charAt(index); + } + this.editUserForm.patchValue({password: this.editUserForm.value.password}); + } + + submitEdit(user) { + this.editUserFlag = false; + console.log(this.editUserForm.value.fullName); + let tempUser = this.editUserForm.value as User; + console.log(tempUser); + this.userService.editUser(user.username, this.editUserForm.value as User).subscribe(res=>{ + alert("User information updated successfully."); + this.userService.getUsers().subscribe( r => { + this.users = r; + this.users.map(user=>{ + let tempRoles = []; + user.roles.map(role=>{ + tempRoles.push(role.name.substring(5)); + }); + user.roles = tempRoles; + }); + }, (err)=>{ + // alert(err.error.message); + alert("Sorry but your credentials are out of date. Please log in again to resolve this."); + this.authService.logout(); + }); + }, (err)=>{ + alert(err.error.message); + }); + } + +} diff --git a/mod2/ui/src/assets/.gitkeep b/mod2/ui/src/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/mod2/ui/src/assets/env.js b/mod2/ui/src/assets/env.js new file mode 100644 index 0000000..144ee8b --- /dev/null +++ b/mod2/ui/src/assets/env.js @@ -0,0 +1,17 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ \ No newline at end of file diff --git a/mod2/ui/src/environments/environment.prod.ts b/mod2/ui/src/environments/environment.prod.ts new file mode 100644 index 0000000..7edd870 --- /dev/null +++ b/mod2/ui/src/environments/environment.prod.ts @@ -0,0 +1,22 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +export const environment = { + production: true, + //api_baseURL: process.env.DCAE_HOSTNAME +}; diff --git a/mod2/ui/src/environments/environment.ts b/mod2/ui/src/environments/environment.ts new file mode 100644 index 0000000..1907c5e --- /dev/null +++ b/mod2/ui/src/environments/environment.ts @@ -0,0 +1,35 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false, + api_baseURL: `${process.env.DCAE_HOSTNAME}` +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +// import 'zone.js/dist/zone-error'; // Included with Angular CLI. diff --git a/mod2/ui/src/favicon.ico b/mod2/ui/src/favicon.ico new file mode 100644 index 0000000..8081c7c Binary files /dev/null and b/mod2/ui/src/favicon.ico differ diff --git a/mod2/ui/src/index.html b/mod2/ui/src/index.html new file mode 100644 index 0000000..f2836a8 --- /dev/null +++ b/mod2/ui/src/index.html @@ -0,0 +1,36 @@ + + + + + + + + ModFe + + + + + + + + + + + + diff --git a/mod2/ui/src/main.ts b/mod2/ui/src/main.ts new file mode 100644 index 0000000..54bf0d7 --- /dev/null +++ b/mod2/ui/src/main.ts @@ -0,0 +1,31 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +import 'hammerjs'; +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/mod2/ui/src/polyfills.ts b/mod2/ui/src/polyfills.ts new file mode 100644 index 0000000..20fe70e --- /dev/null +++ b/mod2/ui/src/polyfills.ts @@ -0,0 +1,81 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + * because those flags need to be set before `zone.js` being loaded, and webpack + * will put import in the top of bundle, so user need to create a separate file + * in this directory (for example: zone-flags.ts), and put the following flags + * into that file, and then add the following code before importing zone.js. + * import './zone-flags.ts'; + * + * The flags allowed in zone-flags.ts are listed here. + * + * The following flags will work for all browsers. + * + * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + * + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + * + * (window as any).__Zone_enable_cross_context_check = true; + * + */ + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/mod2/ui/src/styles.css b/mod2/ui/src/styles.css new file mode 100644 index 0000000..6a392de --- /dev/null +++ b/mod2/ui/src/styles.css @@ -0,0 +1,33 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +@import "~@angular/material/prebuilt-themes/indigo-pink.css"; +@import "~bootstrap/dist/css/bootstrap.css"; + +html, +body { + height: 100%; + overflow: hidden; + margin: 0; + padding: 0; +} + +body { + margin: 0; + font-family: Roboto, "Helvetica Neue", sans-serif; +} \ No newline at end of file diff --git a/mod2/ui/src/test.ts b/mod2/ui/src/test.ts new file mode 100644 index 0000000..ccff8e7 --- /dev/null +++ b/mod2/ui/src/test.ts @@ -0,0 +1,38 @@ +/* + * # ============LICENSE_START======================================================= + * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. + * # ================================================================================ + * # 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. + * # ============LICENSE_END========================================================= + */ + +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/zone-testing'; +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. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); -- cgit 1.2.3-korg