summaryrefslogtreecommitdiffstats
path: root/mod2/ui/src/app
diff options
context:
space:
mode:
Diffstat (limited to 'mod2/ui/src/app')
-rw-r--r--mod2/ui/src/app/app-routing.module.ts55
-rw-r--r--mod2/ui/src/app/app.component.css173
-rw-r--r--mod2/ui/src/app/app.component.html136
-rw-r--r--mod2/ui/src/app/app.component.spec.ts90
-rw-r--r--mod2/ui/src/app/app.component.ts218
-rw-r--r--mod2/ui/src/app/app.module.ts172
-rw-r--r--mod2/ui/src/app/blueprints/blueprints.component.css142
-rw-r--r--mod2/ui/src/app/blueprints/blueprints.component.html283
-rw-r--r--mod2/ui/src/app/blueprints/blueprints.component.spec.ts153
-rw-r--r--mod2/ui/src/app/blueprints/blueprints.component.ts602
-rw-r--r--mod2/ui/src/app/comp-spec-add/comp-spec-add.component.css43
-rw-r--r--mod2/ui/src/app/comp-spec-add/comp-spec-add.component.html64
-rw-r--r--mod2/ui/src/app/comp-spec-add/comp-spec-add.component.spec.ts77
-rw-r--r--mod2/ui/src/app/comp-spec-add/comp-spec-add.component.ts180
-rw-r--r--mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.css73
-rw-r--r--mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.html108
-rw-r--r--mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.spec.ts132
-rw-r--r--mod2/ui/src/app/comp-spec-validation/comp-spec-validation.component.ts145
-rw-r--r--mod2/ui/src/app/comp-specs/comp-specs.component.css121
-rw-r--r--mod2/ui/src/app/comp-specs/comp-specs.component.html189
-rw-r--r--mod2/ui/src/app/comp-specs/comp-specs.component.spec.ts133
-rw-r--r--mod2/ui/src/app/comp-specs/comp-specs.component.ts211
-rw-r--r--mod2/ui/src/app/guards/auth.guard.spec.ts44
-rw-r--r--mod2/ui/src/app/guards/auth.guard.ts62
-rw-r--r--mod2/ui/src/app/guards/login.guard.spec.ts41
-rw-r--r--mod2/ui/src/app/guards/login.guard.ts52
-rw-r--r--mod2/ui/src/app/guards/role.guard.spec.ts44
-rw-r--r--mod2/ui/src/app/guards/role.guard.ts61
-rw-r--r--mod2/ui/src/app/home/home.component.css50
-rw-r--r--mod2/ui/src/app/home/home.component.html93
-rw-r--r--mod2/ui/src/app/home/home.component.ts64
-rw-r--r--mod2/ui/src/app/login/login.component.css25
-rw-r--r--mod2/ui/src/app/login/login.component.html58
-rw-r--r--mod2/ui/src/app/login/login.component.spec.ts59
-rw-r--r--mod2/ui/src/app/login/login.component.ts65
-rw-r--r--mod2/ui/src/app/material-elevation.directive.ts64
-rw-r--r--mod2/ui/src/app/microservices/microservices.component.css121
-rw-r--r--mod2/ui/src/app/microservices/microservices.component.html185
-rw-r--r--mod2/ui/src/app/microservices/microservices.component.spec.ts134
-rw-r--r--mod2/ui/src/app/microservices/microservices.component.ts311
-rw-r--r--mod2/ui/src/app/models/AuthResponse.ts28
-rw-r--r--mod2/ui/src/app/models/Authority.enum.ts22
-rw-r--r--mod2/ui/src/app/models/User.ts25
-rw-r--r--mod2/ui/src/app/ms-add-change/ms-add-change.component.css57
-rw-r--r--mod2/ui/src/app/ms-add-change/ms-add-change.component.html109
-rw-r--r--mod2/ui/src/app/ms-add-change/ms-add-change.component.spec.ts67
-rw-r--r--mod2/ui/src/app/ms-add-change/ms-add-change.component.ts180
-rw-r--r--mod2/ui/src/app/ms-instance-add/ms-instance-add.component.css48
-rw-r--r--mod2/ui/src/app/ms-instance-add/ms-instance-add.component.html93
-rw-r--r--mod2/ui/src/app/ms-instance-add/ms-instance-add.component.spec.ts71
-rw-r--r--mod2/ui/src/app/ms-instance-add/ms-instance-add.component.ts189
-rw-r--r--mod2/ui/src/app/msInstances/msInstances.component.css148
-rw-r--r--mod2/ui/src/app/msInstances/msInstances.component.html221
-rw-r--r--mod2/ui/src/app/msInstances/msInstances.component.spec.ts127
-rw-r--r--mod2/ui/src/app/msInstances/msInstances.component.ts511
-rw-r--r--mod2/ui/src/app/onboarding-tools/onboarding-tools.component.css27
-rw-r--r--mod2/ui/src/app/onboarding-tools/onboarding-tools.component.html23
-rw-r--r--mod2/ui/src/app/onboarding-tools/onboarding-tools.component.spec.ts48
-rw-r--r--mod2/ui/src/app/onboarding-tools/onboarding-tools.component.ts52
-rw-r--r--mod2/ui/src/app/register/register.component.css18
-rw-r--r--mod2/ui/src/app/register/register.component.html62
-rw-r--r--mod2/ui/src/app/register/register.component.spec.ts61
-rw-r--r--mod2/ui/src/app/register/register.component.ts99
-rw-r--r--mod2/ui/src/app/reset-password/reset-password.component.css18
-rw-r--r--mod2/ui/src/app/reset-password/reset-password.component.html64
-rw-r--r--mod2/ui/src/app/reset-password/reset-password.component.spec.ts58
-rw-r--r--mod2/ui/src/app/reset-password/reset-password.component.ts78
-rw-r--r--mod2/ui/src/app/services/auth.service.spec.ts43
-rw-r--r--mod2/ui/src/app/services/auth.service.ts95
-rw-r--r--mod2/ui/src/app/services/base-microservice.service.spec.ts42
-rw-r--r--mod2/ui/src/app/services/base-microservice.service.ts35
-rw-r--r--mod2/ui/src/app/services/breadcrumb.service.ts73
-rw-r--r--mod2/ui/src/app/services/comp-spec-add.service.spec.ts42
-rw-r--r--mod2/ui/src/app/services/comp-spec-add.service.ts45
-rw-r--r--mod2/ui/src/app/services/comp-specs-service.service.spec.ts43
-rw-r--r--mod2/ui/src/app/services/comp-specs-service.service.ts43
-rw-r--r--mod2/ui/src/app/services/deployment-artifact.service.spec.ts43
-rw-r--r--mod2/ui/src/app/services/deployment-artifact.service.ts71
-rw-r--r--mod2/ui/src/app/services/download.service.spec.ts41
-rw-r--r--mod2/ui/src/app/services/download.service.ts85
-rw-r--r--mod2/ui/src/app/services/global-filters.service.spec.ts30
-rw-r--r--mod2/ui/src/app/services/global-filters.service.ts55
-rw-r--r--mod2/ui/src/app/services/jwt-interceptor.service.spec.ts30
-rw-r--r--mod2/ui/src/app/services/jwt-interceptor.service.ts40
-rw-r--r--mod2/ui/src/app/services/microservice-instance.service.spec.ts41
-rw-r--r--mod2/ui/src/app/services/microservice-instance.service.ts46
-rw-r--r--mod2/ui/src/app/services/ms-add.service.spec.ts42
-rw-r--r--mod2/ui/src/app/services/ms-add.service.ts52
-rw-r--r--mod2/ui/src/app/services/spec-validation.service.spec.ts41
-rw-r--r--mod2/ui/src/app/services/spec-validation.service.ts53
-rw-r--r--mod2/ui/src/app/services/user.service.spec.ts44
-rw-r--r--mod2/ui/src/app/services/user.service.ts53
-rw-r--r--mod2/ui/src/app/shared/shared-module.ts61
-rw-r--r--mod2/ui/src/app/user-management/user-management.component.css54
-rw-r--r--mod2/ui/src/app/user-management/user-management.component.html89
-rw-r--r--mod2/ui/src/app/user-management/user-management.component.spec.ts63
-rw-r--r--mod2/ui/src/app/user-management/user-management.component.ts149
97 files changed, 9181 insertions, 0 deletions
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 @@
+<!--
+ # ============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=========================================================
+ -->
+
+<ng4-loading-spinner></ng4-loading-spinner>
+<mat-sidenav-container style="background: linear-gradient(to top, #878C94 0%, #DCDFE3 10%, #F2F2F2 40%, #DCDFE3 80%, #878C94 110%);" class="sidenav-container" *ngIf="authService.authPass">
+ <mat-sidenav [class.mat-elevation-z2]="!isActive" [class.mat-elevation-z8]="isActive" #sideMenu class="sidenav"
+ fixedInViewport="true" [attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'"
+ [mode]="(isHandset$ | async) ? 'over' : 'side'" [opened]="(isHandset$ | async) === true">
+ <mat-toolbar></mat-toolbar>
+ <hr width=90%>
+ <mat-action-list style="margin-top: -10px;">
+ <button class="nav" mat-list-item (click)="setCrumbs('', 'reset')" [routerLink]="'/home'" [routerLinkActive]="'is-active'">Home</button>
+ <button class="nav" mat-list-item (click)="setCrumbs('User Management', 'reset')" *ngIf="authService.isAdmin" [routerLink]="'/users'" [routerLinkActive]="'is-active'">User Management</button>
+ <button class="nav" mat-list-item (click)="setCrumbs('Onboarding Tools', 'reset')" [routerLink]="'/OnboardingTools'" [skipLocationChange]="false" [routerLinkActive]="'is-active'">Onboarding Tools</button>
+ <button class="nav" mat-list-item (click)="msMenu()">
+ <i *ngIf="msMenuIconRight" class="pi pi-chevron-right"></i>
+ <i *ngIf="!msMenuIconRight" class="pi pi-chevron-down"></i>
+ &nbsp;&nbsp;Microservices</button>
+ <div *ngIf="showMsMenu" style="margin-left: 20px;">
+ <button class="subMenuItem" mat-list-item (click)="setCrumbs('Microservices', 'reset')" [routerLink]="'/base-microservices'" [routerLinkActive]="'is-active'">Microservices</button>
+ <button class="subMenuItem" mat-list-item (click)="setCrumbs('MS Instances', 'reset')" [routerLink]="'/ms-instances'" [routerLinkActive]="'is-active'">MS Instances</button>
+ <button class="subMenuItem" mat-list-item (click)="setCrumbs('Blueprints', 'reset')" [routerLink]="'/blueprints'" [routerLinkActive]="'is-active'">Blueprints</button>
+
+ <button class="nav" mat-list-item (click)="utilitiesMenu()">
+ <i *ngIf="utilitiesMenuIconRight" class="pi pi-chevron-right"></i>
+ <i *ngIf="!utilitiesMenuIconRight" class="pi pi-chevron-down"></i>
+ &nbsp;&nbsp;Utilites</button>
+
+ <div *ngIf="showUtilitiesMenu" style="margin-left: 20px;">
+ <button class="subMenuItem" mat-list-item (click)="setCrumbs('Spec Validator', 'reset')"
+ [routerLink]="'/spec-validator'" [routerLinkActive]="'is-active'">Spec Validator</button>
+ <button class="subMenuItem" mat-list-item (click)="redirectToAPIs()">MOD APIs</button>
+ </div>
+ </div>
+ <button class="nav" mat-list-item (click)='redirectTo("https://wiki.web.att.com/pages/viewpage.action?spaceKey=DPD&title=DCAE+MOD+User+Guide")'>User Guide</button>
+
+ </mat-action-list>
+ </mat-sidenav>
+ <mat-sidenav-content style="margin-top: 70px; max-height: 90%">
+ <!-- Top bar when Logged in -->
+ <mat-toolbar [class.mat-elevation-z2]="!isActive" [class.mat-elevation-z8]="isActive" color="primary"
+ fixedInViewport="true" style="background-color: #1D2329; color:#F2F2F2">
+ <button type="button" aria-label="Toggle sidenav" mat-icon-button (click)="sideMenu.toggle()" style="color:#F2F2F2">
+ <i *ngIf="!sideMenu.opened" class="pi pi-angle-double-right" style="font-size: 25px; margin-left: -30px; margin-bottom: 8px;"></i>
+ <i *ngIf="sideMenu.opened" class="pi pi-angle-double-left" style="font-size: 25px; margin-left: -30px; margin-bottom: 8px;"></i>
+ </button>
+ <span style="font-size: 22px; font-weight: 500; margin-left: 35px">MOD</span>
+ <button type="button" *ngIf="authService.authPass" (click)="handleLogout()"
+ style="float:right; margin-right: 18%; height: 32px; border: none; color:#F2F2F2; background: linear-gradient(to bottom, #1D2329 0%, #454B52 135%);
+ font-size: 16px; font-weight: 400">
+ <i class="pi pi-sign-out" style="vertical-align:text-top"></i>
+ Logout
+ </button>
+ <button type="button" *ngIf="authService.authPass" (click)="handleProfile()"
+ style="float:right; margin-right: 4%; height: 32px; border: none; color:#F2F2F2; background: linear-gradient(to bottom, #1D2329 0%, #454B52 135%);
+ font-size: 16px; font-weight: 400">
+ <i class="pi pi-user-edit" style="vertical-align:text-top"></i>
+ {{authService.getUser().username}}
+ </button>
+ <!-- BREADCRUMBS . . . . . -->
+ <ul class="breadcrumb">
+ <li *ngFor="let crumb of breadcrumbs" [routerLink]="crumb.link" (click)="setCrumbs(crumb.page, 'crumbClicked')"><a>{{crumb.page}}</a></li>
+ </ul>
+ </mat-toolbar>
+ <main [@routeAnimations]="prepareRoute(outlet)" class="content">
+ <router-outlet #outlet="outlet"></router-outlet>
+ </main>
+ </mat-sidenav-content>
+</mat-sidenav-container>
+
+<!-- Top bar when Logging in -->
+<mat-toolbar *ngIf="!authService.authPass" [class.mat-elevation-z2]="!isActive" [class.mat-elevation-z8]="isActive" color="primary"
+ fixedInViewport="true" style="background-color: #1D2329">
+ <span style="font-size: 22px; font-weight: 500">MOD</span>
+ <app-login></app-login>
+</mat-toolbar>
+
+<!-- Login Expired dialog -->
+<p-dialog header="Login Expired" [(visible)]="authService.reLoginMsg" [modal]="true" [style]="{width:'25vw'}" [baseZIndex]="10000"
+ [draggable]="false" [resizable]="false">
+ <p>Your login has expired. Please log in again.</p>
+ <p-footer>
+ <button type="button" pButton icon="pi pi-check" (click)="onConfirm()" label="OK"></button>
+ </p-footer>
+</p-dialog>
+
+<!--reset User Password dialog-->
+<p-dialog [(visible)]="resetPasswordFlag" appendTo="body" [modal]="true" [transitionOptions]="'300ms'" [style]="{width: '630px'}" [baseZIndex]="10000"
+[closable]="true" (onHide)="closeResetDialog()">
+<p-header style="display: inline-flex;">
+ Reset User Password
+</p-header>
+<form [formGroup]="resetPasswordForm">
+ <!-- * * * New Password * * * -->
+ <div class="input">
+ <div class="ui-inputgroup" >
+ <label class="inputLabel">New Password</label>
+ <span class="ui-inputgroup-addon" (click)="hidePassword=!hidePassword">
+ <i [ngClass]="hidePassword? 'pi pi-eye-slash':'pi pi-eye'"></i></span>
+ <input class="inputFieldSm" [type]="hidePassword? 'password':'text'" pInputText formControlName="password" />
+ </div>
+ </div>
+ <i *ngIf="resetPasswordForm.get('password').errors && resetPasswordForm.get('password').errors.minlength" style="width: 140px;margin-left: 20px;font-size: small;color: red;">password should be more than 5 characters</i>
+ <!-- * * * Confirm New Password * * * -->
+ <div class="input">
+ <div class="ui-inputgroup" >
+ <label class="inputLabel">Confirm Password</label>
+ <span class="ui-inputgroup-addon" (click)="hideConfirmPassword=!hideConfirmPassword">
+ <i [ngClass]="hideConfirmPassword? 'pi pi-eye-slash':'pi pi-eye'"></i></span>
+ <input class="inputFieldSm" [type]="hideConfirmPassword? 'password':'text'" pInputText formControlName="confirm_password" />
+ </div>
+ </div>
+ <i *ngIf="resetPasswordForm.errors && resetPasswordForm.errors.errMsg" style="width: 140px;margin-left: 20px;font-size: small;color: red;">{{resetPasswordForm.errors.errMsg}}</i>
+ <!-- * * * Submit and Cancel buttons * * * -->
+ <div style="margin-top: 2em; margin-left: 1.3em; margin-bottom: 2em;">
+ <button pButton type="button" (click)="closeResetDialog()" label="Cancel"></button>&nbsp;
+ <button pButton type="submit" (click)="resetPasswordForm.valid && submitReset()" class="ui-button-success" label="Submit" style="width: 70px"></button>
+ </div>
+</form>
+
+</p-dialog> \ 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<boolean> = 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<ExampleFlatNode>(
+ 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 @@
+<!--
+ # ============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=========================================================
+ -->
+
+<ng4-loading-spinner [timeout]="1000000"></ng4-loading-spinner>
+<div class="table_div" [style.visibility]="visible">
+ <!-- * * * * Table of Blueprints * * * * -->
+ <p-table #dt [columns]="cols" [(selection)]="selectedBPs" [value]="bpElements" sortMode="multiple" [paginator]="true"
+ [rows]="18" [rowsPerPageOptions]="[10,12,14,16,18,20,25,50]" (onFilter)="onTableFiltered(dt.filteredValue, $event)" dataKey="id" editMode="row">
+
+ <!-- * * * * Top caption row * * * * -->
+ <ng-template pTemplate="caption">
+
+ <div class="table_caption_header">
+ <!--Blueprints Table Header-->
+ <div>
+ <!-- * * * * Refresh * * * * -->
+ <i class="fa fa-refresh" (click)="getAllBPs()"></i>
+ <!-- * * * * Global filter * * * * -->
+ <input class="table_global_filter" type="text" pInputText size="50" placeholder="Global Filter"
+ (input)="dt.filterGlobal($event.target.value, 'contains')">
+ <i class="fa fa-search" style="margin:4px 0 0 8px"></i>
+ </div>
+
+ <h4 class="table_title"><b>Deployment Artifacts</b></h4>
+
+ </div>
+ </ng-template>
+
+ <!-- * * * * Header row with dynamic column names. Columns include microservice Name, Release, Tag, Type, Version and Status * * * * -->
+ <ng-template pTemplate="header" let-columns>
+ <tr style="text-align: center; vertical-align: bottom;">
+ <th style="width: 3em"></th>
+ <th class="ui-state-highlight" *ngFor="let col of columns" style="outline: none;" [pSortableColumn]="col.field" style="font-size: 12px; outline: none;"
+ [ngStyle]="{'width': col.width}">
+ {{col.header}}<br>
+ <p-sortIcon [field]="col.field"></p-sortIcon>
+ </th>
+ <th style="width: 7%; vertical-align: middle;">
+ Actions
+ </th>
+ </tr>
+
+ <!-- * * * * Second header row for individual column filters * * * * -->
+ <tr>
+ <th style="width: 3em"></th>
+ <!-- * * * * column filters * * * * -->
+ <th *ngFor="let col of columns" style="text-align: center;" [ngSwitch]="col.field">
+ <input *ngSwitchCase="'instanceName'" [(ngModel)]="filteredName" pInputText type="text" (input)="dt.filter($event.target.value, col.field, 'contains')" class="table_column_filter" placeholder="Filter">
+ <input *ngSwitchCase="'instanceRelease'" [(ngModel)]="filteredRelease" pInputText type="text" (input)="dt.filter($event.target.value, col.field, 'contains')" class="table_column_filter" placeholder="Filter">
+ <input *ngSwitchCase="'tag'" [(ngModel)]="filteredTag" pInputText type="text" (input)="dt.filter($event.target.value, col.field, 'contains')" class="table_column_filter" placeholder="Filter">
+ <input *ngSwitchCase="'type'" [(ngModel)]="filteredType" pInputText type="text" (input)="dt.filter($event.target.value, col.field, 'contains')" class="table_column_filter" placeholder="Filter">
+ <input *ngSwitchCase="'version'" [(ngModel)]="filteredVersion" pInputText type="text" (input)="dt.filter($event.target.value, col.field, 'contains')" class="table_column_filter" placeholder="Filter">
+ <input *ngSwitchCase="'status'" [(ngModel)]="filteredStatus" pInputText type="text" (input)="dt.filter($event.target.value, col.field, 'contains')" class="table_column_filter" placeholder="Filter">
+ </th>
+ <th>
+ <div style="text-align: center;">
+ <p-tableHeaderCheckbox style="padding-right: 5px;"></p-tableHeaderCheckbox>
+ <button pButton type="button" class="ui-button-secondary" (click)="enableButtonCheck()" [matMenuTriggerFor]="menu" #menuTrigger="matMenuTrigger"
+ style="background-color: transparent; border: none; width: 20px; height: 20px; vertical-align: middle;">
+ <i class="pi pi-ellipsis-h" style="color: grey;"></i>
+ </button>
+ <mat-menu #menu="matMenu" xPosition="before">
+ <!--<div (mouseleave)="menuTrigger.closeMenu()">-->
+
+ <div style="background-color: rgba(128, 128, 128, 0.25);">
+ <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-download"></i> Download</span>
+ </div>
+
+ <!-- * * * * Download Blueprints * * * * -->
+ <div matTooltip="No Blueprints Selected" [matTooltipDisabled]="canDownload" matTooltipPosition="left">
+ <button mat-menu-item class="table_action_item" (click)="downloadSelectedBps()" [disabled]="!canDownload">Download Selected Blueprints</button>
+ </div>
+
+ <div style="background-color: rgba(128, 128, 128, 0.25);">
+ <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-times"></i> Delete</span>
+ </div>
+
+ <!-- * * * * Delete Selected Blueprints * * * -->
+ <div [matTooltip]="deleteTooltip" [matTooltipDisabled]="canDelete" matTooltipPosition="left">
+ <button mat-menu-item (click)="warnDeleteBlueprint(null)" class="table_action_item" [disabled]="!canDelete">Delete Selected Blueprints</button>
+ </div>
+
+ <div style="background-color: rgba(128, 128, 128, 0.25);">
+ <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-pencil"></i> Update</span>
+ </div>
+
+ <!-- * * * * State Changes * * * * -->
+ <div matTooltip="No Blueprints Selected" [matTooltipDisabled]="canUpdate" matTooltipPosition="left">
+ <button *ngFor="let state of states" mat-menu-item class="table_action_item" (click)="updateSelectedStatusesCheck(state)" [disabled]="!canUpdate">{{state.label}}</button>
+ </div>
+
+ <!--</div>-->
+ </mat-menu>
+ </div>
+ </th>
+ </tr>
+ </ng-template>
+
+ <!-- * * * * dynamic rows generated from columns object and msElems object * * * * -->
+ <ng-template pTemplate="body" let-rowData let-expanded="expanded" let-bpElem>
+ <tr>
+ <!-- * * * * Column for row expand buttons * * * * -->
+ <td>
+ <a href="#" [pRowToggler]="rowData">
+ <i [ngClass]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"></i>
+ </a>
+ </td>
+
+ <td *ngFor="let col of cols">
+ <div *ngIf="col.field==='status'" style="width: -moz-max-content; width: fit-content; padding: 0px 5px 0px 5px; border-radius: 3px; font-weight: 600;"
+ [ngClass]="{
+ 'greenStatus' : bpElem[col.field] === 'DEV_COMPLETE' || bpElem[col.field] === 'PST_CERTIFIED' || bpElem[col.field] === 'ETE_CERTIFIED' || bpElem[col.field] === 'IN_PROD',
+ 'redStatus' : bpElem[col.field] === 'PST_FAILED' || bpElem[col.field] === 'ETE_FAILED' || bpElem[col.field] === 'PROD_FAILED',
+ 'blueStatus' : bpElem[col.field] === 'IN_DEV' || bpElem[col.field] === 'IN_PST' || bpElem[col.field] === 'IN_ETE',
+ 'greyStatus' : bpElem[col.field] === 'NOT_NEEDED'}">
+ {{bpElem[col.field]}}
+ </div>
+ <div *ngIf="col.field!=='status'">{{bpElem[col.field]}}</div>
+ </td>
+
+ <!-- * * * * Actions Column * * * * -->
+ <td>
+ <div style="text-align: center;">
+ <p-tableCheckbox [value]="rowData" style="padding-right: 5px;"></p-tableCheckbox>
+ <!-- * * * * Actions Button * * * * -->
+ <button #actionButton pButton type="button" #menuTrigger="matMenuTrigger" class="ui-button-secondary" style="background-color: transparent; border: none; width: 20px; height: 20px; vertical-align: middle;" [matMenuTriggerFor]="menu">
+ <i class="pi pi-ellipsis-h" style="color: grey;"></i>
+ </button>
+ <!-- * * * * Actions Menu Items * * * * -->
+ <mat-menu #menu="matMenu">
+ <div style="background-color: rgba(128, 128, 128, 0.25);">
+ <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i style="font-size: 12px;" class="pi pi-search"></i> View</span>
+ </div>
+
+ <button mat-menu-item class="table_action_item" (click)="viewBpContent(rowData)">View BP Content</button>
+
+ <div style="background-color: rgba(128, 128, 128, 0.25);">
+ <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-times"></i> Delete</span>
+ </div>
+
+ <div matTooltip='Only blueprints that are in a status of "In Dev", "Not Needed" or "Dev Complete" can be deleted' [matTooltipDisabled]="rowData.status === 'IN_DEV' || rowData.status === 'NOT_NEEDED' || rowData.status === 'DEV_COMPLETE'" matTooltipPosition="left">
+ <button mat-menu-item class="table_action_item" (click)="warnDeleteBlueprint(rowData)" [disabled]="rowData.status !== 'IN_DEV' && rowData.status !== 'NOT_NEEDED' && rowData.status !== 'DEV_COMPLETE'">Delete Blueprint</button>
+ </div>
+
+ <div style="background-color: rgba(128, 128, 128, 0.25);">
+ <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-pencil"></i> Update</span>
+ </div>
+
+ <div>
+ <div *ngFor="let state of states">
+ <button *ngIf="rowData.status !== state.field" mat-menu-item class="table_action_item" (click)="updateState(state, rowData, false)">{{state.label}}</button>
+ </div>
+ </div>
+ </mat-menu>
+ </div>
+ </td>
+ </tr>
+ </ng-template>
+
+
+ <!-- * * * * Row expand content * * * * -->
+ <ng-template pTemplate="rowexpansion" let-rowData let-columns="columns">
+ <tr>
+ <td [attr.colspan]="columns.length + 2">
+ <div class="row-expand-layout" [@rowExpansionTrigger]="'active'">
+ <!-- * * * * Audit Fields * * * * -->
+ <div class="row-expand-card" style="background-color: rgba(95, 158, 160, 0.295);">
+ <b>Created By:</b> {{rowData.metadata.createdBy}}<br>
+ <b>Created On:</b> {{rowData.metadata.createdOn}}<br>
+ <b>Updated By:</b> {{rowData.metadata.updatedBy}}<br>
+ <b>Updated On:</b> {{rowData.metadata.updatedOn}}<br>
+ </div>
+ <!-- * * * * Notes * * * * -->
+ <div class="row-expand-card" style="background-color: rgba(100, 148, 237, 0.295); white-space: pre-line;">
+ <b>Notes:</b><br>
+ <p-scrollPanel [style]="{width: '100%', height: '75px'}">
+ <div style="font-size: 12px; word-break: normal;">{{rowData.metadata.notes}}</div>
+ </p-scrollPanel>
+ </div>
+ <!-- * * * * Labels * * * * -->
+ <div class="row-expand-card" style="background-color: rgba(76, 65, 225, 0.295)">
+ <b style="padding-bottom: 5px;">Labels:</b><br>
+ <div *ngFor="let label of rowData['metadata']['labels']"
+ style="display: inline-flex; margin-top: 5px;">
+ <div style="padding: 2px 7px 3px 0px;">
+ <span style="background-color: rgba(80, 80, 80, 0.185); padding: 3px; border-radius: 3px;">{{label}}</span>
+ </div>
+ </div>
+ </div>
+ <!-- * * * * Failure Reason * * * * -->
+ <div class="row-expand-card" style="background-color: rgba(225, 65, 65, 0.295)">
+ <b>Failure Reason:</b><br>
+ <p-scrollPanel [style]="{width: '100%', height: '75px'}">
+ <div style="font-size: 12px; word-break: normal;">{{rowData.metadata.failureReason}}</div>
+ </p-scrollPanel>
+ </div>
+
+ </div>
+ </td>
+ </tr>
+ </ng-template>
+ </p-table>
+
+ <!-- * * * * download buttons for exporting table to either csv or excel file * * * * -->
+ <div class="table_export_buttons_alignment">
+ <button pButton type="button" (click)="exportTable('csv')" matTooltip="Export Table to CSV" matTooltipPosition="above" class="table_export_button" style="width: 55px;">
+ <i class="pi pi-file" style="margin-top: 3px; margin-left: 4px;"></i>
+ <label style="font-weight: 800; margin-top: 1px;">CSV</label>
+ </button>
+ <button pButton type="button" (click)="exportTable('excel')" matTooltip="Export Table to XLSX" class="table_export_button" matTooltipPosition="above" style="width: 65px; background-color: green;">
+ <i class="pi pi-file-excel" style="margin-top: 3px; margin-left: 4px;"></i>
+ <label style="font-weight: 800; margin-top: 1px">Excel</label>
+ </button>
+ </div>
+</div>
+
+<p-toast key="statusUpdate" class="ui-toast-detail" [style]="{width: '500px'}">
+ <ng-template let-message pTemplate="message">
+ <p><b>{{message.summary}}</b></p>
+ <p style="font-size: 12px;">{{message.detail}}</p>
+ </ng-template>
+</p-toast>
+<p-toast key="multipleBpReleasesSelected"></p-toast>
+
+<p-toast key="bpDeleteResponse"></p-toast>
+
+
+<!-- * * * * Confirm multiple statuses/releases update toast * * * * -->
+<p-toast position="center" key="confirmToast" (onClose)="onReject()" [baseZIndex]="5000" [style]="{width: '300px'}">
+ <ng-template let-message pTemplate="message">
+ <div style="text-align: center">
+ <i class="pi pi-exclamation-triangle" style="font-size: 3em"></i>
+ <h3>{{message.summary}}</h3>
+ <p>{{message.detail}}</p>
+ </div>
+ <div style="width: 100%; text-align: center;">
+ <button type="button" pButton (click)="onConfirm()" label="Confirm" class="ui-button-success"></button>
+ <button type="button" pButton (click)="onReject()" label="Cancel" class="ui-button-secondary" style="margin-left: 20px;"></button>
+ </div>
+ </ng-template>
+</p-toast>
+
+<!-- * * * * Confirm delete blueprint * * * * -->
+<p-toast position="center" key="confirmDeleteToast" class="ui-toast-detail" (onClose)="onReject()" [baseZIndex]="5000" [style]="{width: '300px'}">
+ <ng-template let-message pTemplate="message">
+ <div style="text-align: center">
+ <i class="pi pi-exclamation-triangle" style="font-size: 3em"></i>
+ <h3>{{message.summary}}</h3>
+ Confirm to delete blueprint(s):<br><br>
+ <p style="text-align: left; margin-left: 10%;">{{message.detail}}</p><br>
+ </div>
+ <div style="width: 100%; text-align: center;">
+ <button type="button" pButton (click)="onConfirmDelete()" label="Confirm" class="ui-button-success"></button>
+ <button type="button" pButton (click)="onRejectDelete()" label="Cancel" class="ui-button-secondary"
+ style="margin-left: 20px;"></button>
+ </div>
+ </ng-template>
+</p-toast>
+
+<!-- * * * * View BP Content Pop Up * * * * -->
+<p-dialog [(visible)]="showBpContentDialog" header="Blueprint Content" appendTo="body" [maximizable]="true" [modal]="true" [style]="{width: '80vw'}" [baseZIndex]="10000"
+ [closable]="false">
+ <pre>{{BpContentToView}}</pre>
+ <p-footer>
+ <button pButton label="Close" (click)="showBpContentDialog=false" type="button"></button>
+ <button pButton label="Download" (click)="download()" type="button"></button>
+ </p-footer>
+</p-dialog> \ 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<BlueprintsComponent>;
+
+ 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 @@
+<!--
+ # ============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=========================================================
+ -->
+
+<p-dialog *ngIf="visible" header="Component Spec ADD" [(visible)]="visible" appendTo="body" [modal]="true" [transitionOptions]="'300ms'"
+ [closeOnEscape]="false" [closable]="false" [style]="{width: '650px'}" (onHide)="closeDialog()">
+
+ <!-- * * * * * Input fields * * * * * -->
+ <form [formGroup]="csAddForm" (ngSubmit)="saveCs()" class="bg-faded">
+ <!-- * * * Type * * * -->
+ <div class="input">
+ <label class="inputLabel">Type<span style="color:red">*</span></label>
+ <p-dropdown [options]="types" placeholder="Select Type" optionLabel="type" formControlName="type"></p-dropdown>
+ </div>
+ <!-- * * * Labels * * * -->
+ <div class="input">
+ <label class="inputLabel">Labels</label>
+ <input class="inputFieldLg" type="text" pInputText formControlName="labels" />
+ </div>
+ <span style="padding: 9px 0px 0px 158px; font-size: 13px;">(Separate labels with a space)</span>
+ <!-- * * * Notes * * * -->
+ <div class="input">
+ <label class="inputLabel" style="vertical-align: top">Notes</label>
+ <textarea class="inputFieldLg" [rows]="1" [cols]="30" pInputTextarea autoResize="autoResize" formControlName="notes"></textarea>
+ </div>
+ <!-- * * * Comp Spec File Select * * * -->
+ <div class="input">
+ <label class="inputLabel">Component Spec<span style="color:red">*</span></label>
+
+ <input type="file" style="width: 460px; color:blue; font-style: italic;" (input)="onCompSpecUpload($event)" name="myfile" id="myfile" accept=".json">
+ </div>
+
+ <!-- * * * Policy File Select * * * -->
+ <div class="input">
+ <label class="inputLabel">Policy</label>
+
+ <input type="file" style="width: 460px; color:blue; font-style: italic;" (input)="onPolicyUpload($event)"
+ name="myPolicyFile" id="myPolicyFile" accept=".json">
+ </div>
+
+ <!-- * * * ADD and Cancel buttons * * * -->
+ <div style="float: right; padding: 20px 45px">
+ <button pButton type="button" (click)="closeDialog()" label="Cancel"></button>&nbsp;
+ <button pButton type="submit" class="ui-button-success" label="Add" [disabled]="!csAddForm.valid || !compSpecSelected" style="width: 70px"></button>
+ </div>
+ </form>
+
+ <p-toast key="jsonError" [style]="{width: '430px'}"></p-toast>
+
+</p-dialog>
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<CompSpecAddComponent>;
+
+ 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<any> = 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 @@
+<!--
+ # ============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=========================================================
+ -->
+
+<div style="margin-left: 2%; margin-right: 2%;">
+ <p style="font-size: 26px;">Component Specification Validation</p>
+ <div style="display: inline-flex; width: 100%; height: 100%; min-height: 450px;">
+ <div class="validator-input-card">
+ <div>
+ <mat-card class="input-section-card">
+ <span><b>Action</b></span><span style="color:red">*</span><br>
+ <div style="display: inline-flex;">
+ <div style="padding-right: 10%; width: 200px;">
+ <p-radioButton name="spec-validator-actions" value="validateSpec" [(ngModel)]="spec_validator_action" (click)="validateRadioButton()"></p-radioButton>&nbsp;&nbsp;Validate Spec File
+ </div>
+ <div>
+ <p-radioButton name="spec-validator-actions" value="downloadSchema" [(ngModel)]="spec_validator_action" (click)="downloadRadioButton()"></p-radioButton>&nbsp;&nbsp;Download Schema
+ </div>
+ </div>
+ </mat-card>
+
+ <mat-card class="input-section-card">
+ <span><b>Release</b></span><span style="color:red">*</span><br>
+ <div>
+ <div style="display: inline-flex; align-items: center;">
+ <p-radioButton name="spec-validator-release" value="2007" [(ngModel)]="release"></p-radioButton>&nbsp;&nbsp;2007+
+ </div>
+ </div>
+ </mat-card>
+
+ <mat-card class="input-section-card">
+ <span><b>Type</b></span><span style="color:red">*</span><br>
+ <div>
+ <div style="display: inline-flex; align-items: center;">
+ <p-radioButton name="spec-validator-type" value="k8s" [(ngModel)]="type"></p-radioButton>&nbsp;&nbsp;K8s
+ </div>
+ <br>
+ <div style="display: inline-flex; align-items: center;">
+ <p-radioButton name="spec-validator-type" value="docker" [(ngModel)]="type"></p-radioButton>&nbsp;&nbsp;Docker
+ </div>
+ </div>
+ </mat-card>
+
+ <mat-card *ngIf="spec_validator_action === 'validateSpec'" class="input-section-card">
+ <!-- * * * Comp Spec File Select * * * -->
+ <div>
+ <b>Component Spec File</b><span style="color:red">*</span><br>
+
+ <div style="display: inline-flex;">
+ <input #myFile type="file" style="color:blue; font-style: italic; width: fit-content"
+ (input)="onCompSpecUpload($event)" name="myfile" accept=".json">
+
+ <button pButton type="button" (click)="resetFile()"
+ style="background-color: transparent; border: none; height: 20px;"><i class="pi pi-times"
+ style="color: black;"></i></button>
+ </div>
+ </div>
+ </mat-card>
+
+ <div>
+ <div *ngIf="shouldValidate" style="background-color: rgba(128, 128, 128, 0.315); height: 2px; width: 100%; margin-top: 2%;"></div>
+ <div *ngIf="shouldDownload" style="background-color: rgba(128, 128, 128, 0.315); height: 2px; width: 100%; margin-top: 22%;"></div>
+
+ <div style="float: right; margin-top: 8px">
+ <div *ngIf="shouldValidate" matTooltip="Fill In Required Fields" [matTooltipDisabled]="!(release === '' || type === '' || compSpecContent === null)" matTooltipPosition="above">
+ <button [disabled]="release === '' || type === '' || compSpecContent === null" pButton label="Validate Spec" type="button" (click)="validateSpec()"></button>
+ </div>
+ <div *ngIf="shouldDownload" matTooltip="Fill In Required Fields" [matTooltipDisabled]="!(release === '' || type === '')" matTooltipPosition="above">
+ <button [disabled]="release === '' || type === ''" pButton label="Download Schema" type="button" (click)="downloadSchema()"></button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div *ngIf="shouldValidate" class="validator-output-card">
+ <span><b>Output:</b></span><br><br>
+ <div *ngIf="specValidated" style="width: 100%; padding: 1%; border-radius: 3px; overflow: hidden;" [ngClass]="{'greenOutput' : validCompSpec === true, 'redOutput' : validCompSpec === false}">
+ <div>
+ <span style="font-weight: 500;">{{specValidationOutputHeader}}</span>
+ <div *ngIf="specValidationOutputMessage !== ''" style="margin-top: 10px; width: 100%; height: 2px; background-color: rgba(128, 128, 128, 0.315); "></div>
+
+ <div *ngIf="specValidationOutputMessage !== ''" style="margin-top: 10px">
+ <p-scrollPanel [style]="{width: '100%', height: '50vh'}">
+ <pre style="white-space: pre-wrap;"><b>Summary:</b><br>{{specValidationOutputSummary}}</pre>
+ <pre style="white-space: pre-wrap;"><b>Message(s):</b><br>{{specValidationOutputMessage}}</pre>
+ </p-scrollPanel>
+ </div>
+
+ </div>
+ </div>
+ </div>
+ </div>
+</div> \ 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<CompSpecValidationComponent>;
+
+ 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 @@
+<!--
+ # ============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=========================================================
+ -->
+
+<div style="margin: 0px 50px 10px 20px; border: 1px solid darkslategray; min-width: 980px">
+ <p-table #dt *ngIf="loadTable" [columns]="cols" [value]="csElements" sortMode="multiple" [paginator]="true"
+ [rows]="18" [rowsPerPageOptions]="[10,12,14,16,18,20,25,50]" (onFilter)="onTableFiltered(dt.filteredValue)" dataKey="id">
+ <ng-template pTemplate="caption">
+
+ <div style="margin-left: -18%; width: 82%; max-height: 25px; display: inline-flex;">
+ <!--CS Table Header-->
+ <div style="float: left;">
+ <!--Refresh-->
+ <i class="fa fa-refresh" (click)="getAllCs()"></i>
+ <!--Global Filter-->
+ <input type="text" pInputText size="50" placeholder="Global Filter"
+ (input)="dt.filterGlobal($event.target.value, 'contains')"
+ style="width: 250px; height:25px; font-size: 12px; margin-left: 15px">
+ <i class="fa fa-search" style="margin:4px 0px 0 8px"></i>
+ </div>
+
+ <h4 style="margin-left: 15%"><b>Component Specs</b></h4>
+
+ </div>
+ </ng-template>
+
+ <ng-template pTemplate="header" let-columns>
+ <tr>
+ <th style="width: 3em"></th>
+ <th class="ui-state-highlight" *ngFor="let col of columns" [pSortableColumn]="col.field"
+ style="font-size: 12px; outline: none; vertical-align: bottom; text-align: center;" [ngStyle]="{'width': col.width}">
+ {{col.header}}<br>
+ <p-sortIcon [field]="col.field"></p-sortIcon>
+ </th>
+ <th style="font-size: 13px; width: 8%; vertical-align: top; text-align: center;">
+ Actions
+ </th>
+ </tr>
+
+ <!--Second header row for individual column filters-->
+ <tr style="text-align: center;">
+ <th style="width: 3em"></th>
+ <th *ngFor="let col of columns" [ngSwitch]="col.field">
+ <input pInputText type="text" (input)="dt.filter($event.target.value, col.field, 'contains')"
+ style="width: 100%; height: 20px; font-size: 10px;" placeholder="Filter">
+ </th>
+ <th>
+
+ </th>
+ </tr>
+ </ng-template>
+
+ <ng-template pTemplate="body" let-rowData let-csElem>
+ <tr style="font-size: 12px;">
+ <!--Column for row expand buttons-->
+ <td>
+ <a href="#" [pRowToggler]="rowData">
+ <i [ngClass]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"></i>
+ </a>
+ </td>
+
+ <td *ngFor="let col of cols">
+ <div *ngIf="col.field==='status'"
+ style="width: fit-content; width: -moz-max-content; padding: 0px 5px 0px 5px; border-radius: 3px; font-weight: 600;"
+ [ngClass]="{'greenStatus' : csElem[col.field] === 'ACTIVE', 'greyStatus' : csElem[col.field] === 'INACTIVE'}">
+ {{csElem[col.field]}}
+ </div>
+ <div *ngIf="col.field!=='status'">{{csElem[col.field]}}</div>
+ </td>
+
+ <!--Actions Column-->
+ <td>
+ <div style="text-align: center;">
+ <button pButton type="button"
+ style="background-color: transparent; border: none; width: 20px; height: 20px; vertical-align: middle;"
+ class="ui-button-secondary" [matMenuTriggerFor]="menu">
+ <i class="pi pi-ellipsis-h" style="color: grey;"></i>
+ </button>
+ <mat-menu #menu="matMenu" xPosition="before">
+
+ <div style="background-color: rgba(128, 128, 128, 0.25);">
+ <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-search" style="font-size: 10px;"></i> View</span>
+ </div>
+
+ <button mat-menu-item class="table_action_item" (click)="showViewCsDialog(rowData)">Component Spec</button>
+ <div matTooltip="Policy not included" [matTooltipDisabled]="rowData.policyJson" matTooltipPosition="left">
+ <button mat-menu-item [disabled]="!rowData.policyJson" class="table_action_item" (click)="showViewPolicyDialog(rowData)">Policy</button>
+ </div>
+ <!--
+ <div *ngIf="rowData.status !== 'ACTIVE'">
+ <div style="background-color: rgba(128, 128, 128, 0.25);">
+ <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-pencil"
+ style="font-size: 10px;"></i> Update</span>
+ </div>
+
+ <button mat-menu-item class="table_action_item" (click)="toActive(rowData)">To Active</button>
+ </div>-->
+ </mat-menu>
+ </div>
+ </td>
+ </tr>
+ </ng-template>
+
+ <!--Row expand content-->
+ <ng-template pTemplate="rowexpansion" let-rowData let-columns="columns">
+ <tr>
+ <td [attr.colspan]="columns.length + 2">
+ <div class="row-expand-layout" [@rowExpansionTrigger]="'active'">
+ <!-- Audit Fields -->
+ <div class="row-expand-card" style="background-color: rgba(95, 158, 160, 0.295)">
+ <b>Created By:</b> {{rowData.metadata.createdBy}}<br>
+ <b>Created On:</b> {{rowData.metadata.createdOn}}<br>
+ <b>Updated By:</b> {{rowData.metadata.updatedBy}}<br>
+ <b>Updated On:</b> {{rowData.metadata.updatedOn}}
+ </div>
+ <!-- Notes -->
+ <div class="row-expand-card" style="background-color: rgba(100, 148, 237, 0.219)">
+ <b>Notes:</b><br>
+ <p-scrollPanel [style]="{width: '100%', height: '62px'}">
+ <div style="font-size: 12px; word-break: normal;">{{rowData.metadata.notes}}</div>
+ </p-scrollPanel>
+ </div>
+ <!-- Labels -->
+ <div class="row-expand-card" style="background-color: rgba(76, 65, 225, 0.199)">
+ <b style="padding-bottom: 5px;">Labels:</b><br>
+ <div *ngFor="let label of rowData['metadata']['labels']"
+ style="display: inline-flex; margin-top: 5px;">
+ <div style="padding: 2px 7px 3px 0px;">
+ <span
+ style="background-color: rgba(80, 80, 80, 0.185); padding: 3px; border-radius: 3px;">{{label}}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </td>
+ </tr>
+ </ng-template>
+ </p-table>
+
+ <!--download buttons for exporting table to either csv or excel file-->
+ <div *ngIf="loadTable" style="margin-left: 10px; margin-top: -32px; float: left;">
+ <button pButton type="button" (click)="exportTable('csv')" matTooltip="Export Table to CSV"
+ matTooltipPosition="above"
+ style="border-radius: 5px; width: 65px; height: 22px; font-size: 14px; border: none; margin-top: 4px; display: inline-flex;">
+ <i class="pi pi-file" style="margin-top: 2px; margin-left: 8px;"></i>
+ <label style="font-weight: 800; vertical-align: middle;">CSV</label>
+ </button>
+ <button pButton type="button" (click)="exportTable('excel')" matTooltip="Export Table to XLSX"
+ matTooltipPosition="above"
+ style="border-radius: 5px; width: 65px; height: 22px; margin-left: 7px; font-size: 14px; background-color: green; border: none; display: inline-flex;">
+ <i class="pi pi-file-excel" style="margin-top: 2px; margin-left: 4px;"></i>
+ <label style="font-weight: 800; vertical-align: middle;">Excel</label>
+ </button>
+ </div>
+</div>
+
+<!-- * * * * View Spec Content Pop Up * * * * -->
+<p-dialog [(visible)]="showViewCs" header="Spec Content" appendTo="body" [maximizable]="true"
+ [modal]="true" [style]="{width: '80vw'}" [baseZIndex]="10000" [closable]="false">
+ <pre>{{specContentToView | json}}</pre>
+ <p-footer>
+ <button pButton label="Close" (click)="showViewCs=false" type="button"></button>
+ <button pButton label="Download" (click)="download(specContentToView, 'Component_Spec')" type="button"></button>
+ </p-footer>
+</p-dialog>
+
+<!-- * * * * View Policy JSON Pop Up * * * * -->
+<p-dialog [(visible)]="showViewPolicy" header="Policy Json" appendTo="body" [maximizable]="true" [modal]="true"
+ [style]="{width: '80vw'}" [baseZIndex]="10000" [closable]="false">
+ <pre>{{policyJsonToView | json}}</pre>
+ <p-footer>
+ <button pButton label="Close" (click)="showViewPolicy=false" type="button"></button>
+ <button pButton label="Download" (click)="download(policyJsonToView, 'Policy_Json')" type="button"></button>
+ </p-footer>
+</p-dialog>
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<CompSpecsComponent>;
+
+ 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<boolean | UrlTree> | Promise<boolean | UrlTree> | 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<boolean | UrlTree> | Promise<boolean | UrlTree> | 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<boolean | UrlTree> | Promise<boolean | UrlTree> | 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 @@
+<!--
+ # ============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=========================================================
+ -->
+
+<div style="margin: -50px 0px 0px -30px">
+ <mat-card
+ (click)="toggleMsMenu()"
+ appMaterialElevation
+ [defaultElevation]="defaultElevation"
+ raisedElevation="16"
+ class="card"
+ style="background-color: #60a4a5">
+ Microservices...
+ </mat-card>
+ <mat-card
+ (click)="navSelect('Onboarding Tools')"
+ appMaterialElevation
+ [defaultElevation]="defaultElevation"
+ raisedElevation="16"
+ class="card"
+ style="background-color: rgba(154, 135, 167, 0.904);"
+ [routerLink]="'/OnboardingTools'">
+ Onboarding Tools
+ </mat-card>
+ <mat-card *ngIf="authService.isAdmin"
+ (click)="navSelect('User Management')"
+ appMaterialElevation
+ [defaultElevation]="defaultElevation"
+ raisedElevation="16"
+ class="card"
+ style="background-color: rgba(160, 120, 83, 0.904);"
+ [routerLink]="'/users'">
+ User Management
+ </mat-card>
+</div>
+<br>
+<div *ngIf="displayMsMenu" style="margin-top: -60px">
+ <mat-card id="subMenu" (click)="navSelect('Microservices')"
+ appMaterialElevation
+ [defaultElevation]="defaultElevation"
+ raisedElevation="16"
+ class="card"
+ style="background-color: #70a7a9; position: absolute;">
+ Microservices
+ </mat-card>
+</div>
+<br>
+<div *ngIf="displayMsMenu" style="margin-top: 70px">
+ <mat-card id="subMenu" (click)="navSelect('MS Instances')"
+ appMaterialElevation
+ [defaultElevation]="defaultElevation"
+ raisedElevation="16"
+ class="card"
+ style="background-color: #8fbbbc; position: absolute;">
+ MS Instances
+ </mat-card>
+</div>
+<br>
+<div *ngIf="displayMsMenu" style="margin-top: 70px">
+ <mat-card id="subMenu" (click)="navSelect('Blueprints')"
+ appMaterialElevation
+ [defaultElevation]="defaultElevation"
+ raisedElevation="16"
+ class="card"
+ style="background-color: #afcecf; position: absolute;">
+ Blueprints
+ </mat-card>
+</div>
+<br>
+<div *ngIf="displayMsMenu" style="margin-top: 70px">
+ <mat-card id="subMenu" (click)="navSelect('MOD APIs')"
+ appMaterialElevation
+ [defaultElevation]="defaultElevation"
+ raisedElevation="16"
+ class="card"
+ style="background-color: #cfe2e2; position: absolute;">
+ MOD APIs
+ </mat-card>
+</div> \ 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 @@
+<!--
+ # ============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=========================================================
+ -->
+
+<p-card header="Log in to MOD" [style]="{margin:'100px 300px 300px 300px'}">
+<hr class="line-break">
+<form [formGroup]="form" (ngSubmit)="submit()" class="p-5 bg-faded" style="margin-left: 220px;margin-bottom: 100px;">
+ <div class="ui-g ui-fluid" >
+ <div class="ui-g-12 ui-md-4">
+ <div class="ui-inputgroup" >
+ <span class="ui-inputgroup-addon"><i class="pi pi-user" style="line-height: 1.25;"></i></span>
+ <input type="text" placeholder="ATT UID" formControlName="username">
+ </div>
+ </div>
+ </div>
+ <!-- <div class="ui-g ui-fluid" >
+ <div class="ui-g-12 ui-md-4">
+ <div class="ui-inputgroup" >
+ <span class="ui-inputgroup-addon"><i class="pi pi-user" style="line-height: 1.25;"></i></span>
+ <input type="text" placeholder="ATT UID" formControlName="uid">
+ </div>
+ </div>
+ </div> -->
+ <div class="ui-g ui-fluid">
+ <div class="ui-g-12 ui-md-4">
+ <div class="ui-inputgroup" >
+ <span class="ui-inputgroup-addon" (click)="hide=!hide">
+ <i [ngClass]="hide? 'pi pi-eye-slash':'pi pi-eye'"></i></span>
+ <input [type]="hide? 'password':'text'" placeholder="password" formControlName="password" >
+ <!-- <a routerLink="/reset-password">Forget password?</a> -->
+ </div>
+ </div>
+ </div>
+
+ <p-footer class="text-left ui-g-12">
+ <!-- <button pButton type="button" class="ui-button-info" label="Cancel" (click)="cancel()" style="margin-right: .25em"></button> -->
+ <button pButton type="submit" class="ui-button-success" label="Login" [disabled]="!form.valid"></button>
+ </p-footer>
+ <div class="text-left ui-g-12">
+ <p>Not a registered user? Contact the DCAE-MOD team at <i>dcae-mod-team@att.com</i></p>
+ <a href="mailto:dcae-mod-team@att.com?subject=account application&body=Please help open a dcae account with username:">Cick here to contact now</a>
+ </div>
+ </form>
+</p-card>
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<LoginComponent>;
+
+ 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((<HTMLElement>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 @@
+<!--
+ # ============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=========================================================
+ -->
+
+<ng4-loading-spinner [timeout]="1000000"></ng4-loading-spinner>
+<div style="margin: 0px 0px 10px 20px; width: 97%; min-width: 900px; border: 1px solid darkslategray">
+ <!--Table of base microservices-->
+ <p-table #dt *ngIf="loadTable" [columns]="cols" [value]="msElements" sortMode="multiple" [paginator]="true"
+ [rows]="18" [rowsPerPageOptions]="[10,12,14,16,18,20,25,50]" (onFilter)="onTableFiltered(dt.filteredValue)" dataKey="id">
+
+ <!--Top caption row-->
+ <ng-template pTemplate="caption">
+
+ <div style="margin-left: -5%; width: 90%; max-height: 25px; display: inline-flex;">
+ <!--Microservices Table Header-->
+ <div style="float: left;">
+ <!--Refresh-->
+ <i class="fa fa-refresh" (click)="getAllMs()"></i>
+ <!--Global Filter-->
+ <input type="text" pInputText size="50" placeholder="Global Filter"
+ (input)="dt.filterGlobal($event.target.value, 'contains')"
+ style="width: 250px; height:25px; font-size: 12px; margin-left: 15px">
+ <i class="fa fa-search" style="margin:4px 0px 0 8px"></i>
+ </div>
+
+ <h4 style="margin-left: 15%"><b>Microservices</b></h4>
+
+ </div>
+
+ <div style="float: right;">
+ <button pButton type="button" (click)="showAddChangeDialog()" matTooltip="Add Microservice" matTooltipPosition="above"
+ style="border-radius: 5px; width: 65px; height: 27px; font-size: 14px; border: none; display: inline-flex;">
+ <i class="pi pi-plus" style="margin-top: 5px; margin-left: 10px;"></i>
+ <label style="font-weight: 800; margin-top: 3px">MS</label>
+ </button>
+ </div>
+
+ </ng-template>
+
+ <!--Header row with dynamic column names. Columns include microservice Name, Type, Location and Namespace-->
+ <ng-template pTemplate="header" let-columns>
+ <tr style="text-align: center">
+ <th style="width: 3em"></th>
+ <th class="ui-state-highlight" *ngFor="let col of columns" [pSortableColumn]="col.field" style="font-size: 12px; outline: none; vertical-align: bottom;" [ngStyle]="{'width': col.width}">
+ {{col.header}}<br>
+ <p-sortIcon [field]="col.field"></p-sortIcon>
+ </th>
+ <th style="font-size: 13px; width: 6.5%; vertical-align: top;">
+ Actions
+ </th>
+ </tr>
+
+ <!--Second header row for individual column filters-->
+ <tr style="text-align: center;">
+ <th style="width: 3em"></th>
+ <th *ngFor="let col of columns" [ngSwitch]="col.field">
+ <input pInputText type="text"
+ (input)="dt.filter($event.target.value, col.field, 'contains')" style="width: 100%; height: 20px; font-size: 10px;"
+ placeholder="Filter">
+ </th>
+ <th></th>
+ </tr>
+ </ng-template>
+
+ <!--dynamic rows generated from columns object and msElems object-->
+ <ng-template pTemplate="body" let-rowData let-expanded="expanded" let-msElem>
+ <tr style="font-size: 12px;">
+ <!--Column for row expand buttons-->
+ <td>
+ <a href="#" [pRowToggler]="rowData">
+ <i [ngClass]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"></i>
+ </a>
+ </td>
+
+ <td *ngFor="let col of cols">
+ <div *ngIf="col.field==='status'"
+ style="width: fit-content; width: -moz-max-content; padding: 0px 5px 0px 5px; border-radius: 3px; font-weight: 600;"
+ [ngClass]="{'greenStatus' : msElem[col.field] === 'ACTIVE',
+ 'greyStatus' : msElem[col.field] === 'INACTIVE'}">
+ {{msElem[col.field]}}
+ </div>
+ <div *ngIf="col.field!=='status'">{{msElem[col.field]}}</div>
+ </td>
+
+ <!--Actions Column-->
+ <td>
+ <div style="text-align: center;">
+ <button pButton type="button" style="background-color: transparent; border: none; width: 20px; height: 20px; vertical-align: middle;" class="ui-button-secondary" [matMenuTriggerFor]="menu">
+ <i class="pi pi-ellipsis-h" style="color: grey;"></i>
+ </button>
+ <mat-menu #menu="matMenu" xPosition="before">
+
+ <div style="background-color: rgba(128, 128, 128, 0.25);">
+ <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-plus"
+ style="font-size: 10px;"></i> Add</span>
+ </div>
+
+ <button mat-menu-item class="table_action_item" (click)="showAddChangeMsInstanceDialog(rowData)">Add MS
+ Instance...</button>
+
+ <div style="background-color: rgba(128, 128, 128, 0.25);">
+ <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-pencil"></i> Update</span>
+ </div>
+
+ <button mat-menu-item class="table_action_item" (click)="showAddChangeDialog(rowData)">Update Microservice...</button>
+ </mat-menu>
+ </div>
+ </td>
+ </tr>
+ </ng-template>
+
+ <!--Row expand content-->
+ <ng-template pTemplate="rowexpansion" let-rowData let-columns="columns">
+ <tr>
+ <td [attr.colspan]="columns.length + 2">
+ <div class="row-expand-layout" [@rowExpansionTrigger]="'active'">
+ <!-- Audit Fields -->
+ <div class="row-expand-card" style="background-color: rgba(95, 158, 160, 0.295)">
+ <b>Created By:</b> {{rowData.metadata.createdBy}}<br>
+ <b>Created On:</b> {{rowData.metadata.createdOn}}<br>
+ <b>Updated By:</b> {{rowData.metadata.updatedBy}}<br>
+ <b>Updated On:</b> {{rowData.metadata.updatedOn}}
+ </div>
+ <!-- Notes -->
+ <div class="row-expand-card" style="background-color: rgba(100, 148, 237, 0.219)">
+ <b>Notes:</b><br>
+ <p-scrollPanel [style]="{width: '100%', height: '62px'}">
+ <div style="font-size: 12px; word-break: normal;">{{rowData.metadata.notes}}</div>
+ </p-scrollPanel>
+ </div>
+ <!-- Labels -->
+ <div class="row-expand-card" style="background-color: rgba(76, 65, 225, 0.199)">
+ <b style="padding-bottom: 5px;">Labels:</b><br>
+ <div *ngFor="let label of rowData['metadata']['labels']" style="display: inline-flex; margin-top: 5px;">
+ <div style="padding: 2px 7px 3px 0px;">
+ <span style="background-color: rgba(80, 80, 80, 0.185); padding: 3px; border-radius: 3px;">{{label}}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </td>
+ </tr>
+ </ng-template>
+ </p-table>
+
+ <!--download buttons for exporting table to either csv or excel file-->
+ <div *ngIf="loadTable" style="margin-left: 10px; margin-top: -32px; float: left;">
+ <button pButton type="button" (click)="exportTable('csv')"
+ matTooltip="Export Table to CSV" matTooltipPosition="above"
+ style="border-radius: 5px; width: 65px; height: 22px; font-size: 14px; border: none; margin-top: 4px; display: inline-flex;">
+ <i class="pi pi-file" style="margin-top: 2px; margin-left: 8px;"></i>
+ <label style="font-weight: 800; vertical-align: middle;">CSV</label>
+ </button>
+ <button pButton type="button" (click)="exportTable('excel')"
+ matTooltip="Export Table to XLSX" matTooltipPosition="above"
+ style="border-radius: 5px; width: 65px; height: 22px; margin-left: 7px; font-size: 14px; background-color: green; border: none; display: inline-flex;">
+ <i class="pi pi-file-excel" style="margin-top: 2px; margin-left: 4px;"></i>
+ <label style="font-weight: 800; vertical-align: middle;">Excel</label>
+ </button>
+ </div>
+
+ <!-- Dialog to Add or Change a MS -->
+ <app-ms-add-change *ngIf="showMsAddChangeDialog" [visible]="showMsAddChangeDialog" [currentRow]="currentRow" (handler)="addOrChangeMs($event)"></app-ms-add-change>
+
+ <!-- Dialog to Add (or Change) a MS Instance -->
+ <app-ms-instance-add *ngIf="showAddChangeMsInstance" [visible]="showAddChangeMsInstance" [msName]="addInstanceTo" [currentRow]="currentRow" (handler)="addMsInstance($event)"></app-ms-instance-add>
+
+ <!-- Shared success message -->
+ <p-toast key="addChangeSuccess"></p-toast>
+
+</div> \ 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<MicroservicesComponent>;
+
+ 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<any>(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 (<app-ms-add-change> 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 @@
+<!--
+ # ============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=========================================================
+ -->
+
+<p-dialog *ngIf="visible" [header]="guiHeader" [(visible)]="visible" appendTo="body" [modal]="true" [transitionOptions]="'300ms'"
+ [closeOnEscape]="false" [closable]="false" [style]="{width: '645px'}" (onHide)="closeDialog()">
+
+ <!-- "Add / Change MS" Error Message -->
+ <p-toast key="msAddChangeError" [style]="{width: '500px'}"></p-toast>
+
+ <!-- * * * * * Input fields * * * * * -->
+ <form [formGroup]="msAddForm" (ngSubmit)="saveMs()" class="bg-faded">
+ <!-- * * * MS Name * * * -->
+ <div class="input">
+ <label class="inputLabel">MS Name<span style="color:red">*</span></label>
+ <input class="inputFieldMed" type="text" pInputText formControlName="name">
+ </div>
+ <!-- * * * MS Tag * * * -->
+ <div class="input">
+ <label class="inputLabel">MS Tag<span style="color:red">*</span></label>
+ <input *ngIf="!currentRow" class="inputFieldMed" type="text" pInputText formControlName="tag">
+ <!-- If Updating (vs ADDing) the data, the "Tag" is Read-Only -->
+ <input *ngIf="currentRow" class="inputFieldMed" type="text" pInputText formControlName="tag" style="border:none" readonly>
+ <!-- * * * (Validation Rules Display) * * * -->
+ <div class="validationMsg" *ngIf="msAddForm.controls['tag'].invalid &&
+ !msAddForm.value['tag']=='' &&
+ msAddForm.controls['tag'].value.length < 51">
+ Format: lowercase alphanumeric with embedded dashes (5-50 characters)
+ </div>
+ <!-- * * * (Validation Rule - length > 50) * * * -->
+ <div class="validationMsg" *ngIf="msAddForm.controls['tag'].value.length > 50">
+ MS Tag cannot exceed 50 chars
+ </div>
+ </div>
+ <!-- * * * Service Short Name * * * -->
+ <div class="input">
+ <label class="inputLabel">Service Short Name</label>
+ <input class="inputFieldMed" type="text" pInputText formControlName="serviceName">
+ <!-- * * * (Validation Rules Display) * * * -->
+ <div class="validationMsg" *ngIf="msAddForm.controls['serviceName'].invalid &&
+ !msAddForm.value['serviceName']=='' &&
+ msAddForm.controls['serviceName'].value.length < 26">
+ Format: lowercase alpha with embedded dashes
+ </div>
+ <!-- * * * (Warning! Global vs Central/Edge Service Name length) * * * -->
+ <div class="validationMsgWarning" *ngIf="msAddForm.controls['serviceName'].valid &&
+ msAddForm.controls['serviceName'].value.length > 12 &&
+ msAddForm.controls['serviceName'].value.length < 26">
+ Warning! Only Global Site short names can exceed 12 chars (max 25)
+ </div>
+ <!-- * * * (Validation Rule - length > 25) * * * -->
+ <div class="validationMsg" *ngIf="msAddForm.controls['serviceName'].value.length > 25">
+ Global Site short names cannot exceed 25 chars
+ </div>
+ </div>
+ <!-- * * * Type * * * -->
+ <div class="input">
+ <label class="inputLabel">Type<span style="color:red">*</span></label>
+ <p-dropdown [options]="types" placeholder="Select Type" optionLabel="type" formControlName="type"></p-dropdown>
+ </div>
+ <!-- * * * Location
+ <div class="input">
+ <label class="inputLabel">Location<span style="color:red">*</span></label>
+ <p-dropdown [options]="locations" placeholder="Select Location" optionLabel="location" formControlName="location"></p-dropdown>
+ </div>
+ * * * -->
+ <!-- * * * Namespace * * * -->
+ <div class="input">
+ <label class="inputLabel">Namespace</label>
+ <input class="inputFieldMed" type="text" pInputText formControlName="namespace">
+ <!-- * * * (Validation Rules Display) * * * -->
+ <div class="validationMsg" *ngIf="msAddForm.controls['namespace'].invalid && !msAddForm.value['namespace']==''">
+ Format: lowercase alphanumeric with embedded dashes
+ </div>
+ </div>
+ <!-- * * * Labels * * * -->
+ <div class="input">
+ <label class="inputLabel">Labels</label>
+ <input class="inputFieldLg" type="text" pInputText formControlName="labels">
+ </div>
+ <span style="padding: 9px 0px 0px 172px; font-size: 13px;">(Separate labels with a space)</span>
+ <!-- * * * Notes * * * -->
+ <div class="input">
+ <label class="inputLabel" style="vertical-align: top">Notes</label>
+ <textarea class="inputFieldLg" [rows]="1" [cols]="30" pInputTextarea autoResize="autoResize" formControlName="notes"></textarea>
+ </div>
+ <!-- * * * ADD and CANCEL buttons * * * -->
+ <div style="float: right; padding: 20px 45px">
+ <button pButton type="button" (click)="closeDialog()" style="margin-right: 10px" label="Cancel"></button>
+ <button pButton type="submit" class="ui-button-success" style="width: 77px; text-align:center" [label]="addOrUpdate"
+ [disabled]="!msAddForm.valid || !msAddForm.value['name'].trim()"></button>
+ </div>
+ </form>
+
+</p-dialog>
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<MsAddChangeComponent>;
+
+ 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<any> = 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 @@
+<!--
+ # ============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=========================================================
+ -->
+
+<p-dialog [(visible)]="visible" [header]="guiHeader" appendTo="body" [modal]="true" [transitionOptions]="'300ms'" [style]="{width: '720px'}" [baseZIndex]="10000"
+ [closable]="false" (onHide)="closeDialog()">
+
+ <!-- "Add / Change MS Instance" Error Message -->
+ <p-toast key="instanceAddChangeError"></p-toast>
+
+ <form [formGroup]="msInstanceAddForm">
+ <!-- * * * Name * * * -->
+ <div class="input">
+ <label class="inputLabel">MS Name</label>
+ <b>{{msName}}</b>
+ </div>
+ <!-- * * * Release * * * -->
+ <div class="input">
+ <label class="inputLabel">Release<span style="color:red">*</span></label>
+ <p-dropdown [options]="msInstanceReleases" placeholder="Select Release" formControlName="release"></p-dropdown>
+ </div>
+ <!-- * * * Scrum Lead / UID * * * -->
+ <div class="input">
+ <label class="inputLabel">Scrum Lead/UID</label>
+ <input class="inputFieldLg" type="text" pInputText formControlName="scrumLead" /> / <input class="inputFieldXSm" type="text" pInputText formControlName="scrumLeadId" />
+ </div>
+ <!-- * * * Systems Engineer / UID * * * -->
+ <div class="input">
+ <label class="inputLabel">Systems Engineer/UID</label>
+ <input class="inputFieldLg" type="text" pInputText formControlName="systemsEngineer" /> / <input class="inputFieldXSm" type="text" pInputText formControlName="systemsEngineerId" />
+ </div>
+ <!-- * * * Developer / UID * * * -->
+ <div class="input">
+ <label class="inputLabel">Developer<span style="color:red">*</span>/UID<span style="color:red">*</span></label>
+ <input class="inputFieldLg" type="text" pInputText formControlName="developer" /> / <input class="inputFieldXSm" type="text" pInputText formControlName="developerId" />
+ </div>
+ <!-- * * * PST Due Date * * * -->
+ <div class="input">
+ <label class="inputLabel">PST Due Date</label>
+ <p-calendar appendTo="body" [baseZIndex]="10001" dateFormat="yy-mm-dd" formControlName="pstDueDate" [showIcon]="true"></p-calendar>
+ </div>
+ <!-- * * * PST Due Iteration * * * -->
+ <div class="input">
+ <label class="inputLabel">PST Due Iteration</label>
+ <input class="inputFieldSm" type="text" pInputText formControlName="pstDueIteration" />
+ </div>
+ <!-- * * * ETE Due Date * * * -->
+ <div class="input">
+ <label class="inputLabel">ETE Due Date</label>
+ <p-calendar appendTo="body" [baseZIndex]="10001" dateFormat="yy-mm-dd" formControlName="eteDueDate" [showIcon]="true"></p-calendar>
+ </div>
+ <!-- * * * ETE Due Iteration * * * -->
+ <div class="input">
+ <label class="inputLabel">ETE Due Iteration</label>
+ <input class="inputFieldSm" type="text" pInputText formControlName="eteDueIteration" />
+ </div>
+ <!-- * * * Labels * * * -->
+ <div class="input">
+ <label class="inputLabel">Labels</label>
+ <input class="inputFieldLg" type="text" pInputText formControlName="labels" />
+ </div>
+ <span style="padding: 0px 0px 0px 188px; font-size: 13px;">(Separate labels with a space)</span>
+ <!-- * * * Notes * * * -->
+ <div class="input">
+ <label class="inputLabel" style="vertical-align: top">Notes</label>
+ <textarea class="inputFieldLg" [rows]="1" [cols]="30" pInputTextarea autoResize="autoResize" formControlName="notes"></textarea>
+ </div>
+
+ <!-- * * * ADD and Cancel buttons * * * -->
+ <div style="float: right; padding: 10px 25px 5px;">
+ <button pButton type="button" (click)="closeDialog()" label="Cancel"></button>&nbsp;
+ <button pButton type="submit" (click)="submitMsInstance()" class="ui-button-success" [label]="addOrUpdate" style="width: 77px"
+ [disabled]="!msInstanceAddForm.valid ||
+ !msInstanceAddForm.value['developer'].trim() ||
+ !msInstanceAddForm.value['developerId'].trim()"></button>
+ </div>
+ </form>
+
+</p-dialog> \ 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<MsInstanceAddComponent>;
+
+ 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<any> = 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 @@
+<!--
+ # ============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=========================================================
+ -->
+
+<ng4-loading-spinner [timeout]="1000000"></ng4-loading-spinner>
+<div class="table_div">
+ <p-table #dt *ngIf="loadTable" [columns]="cols" [value]="msInstances" sortMode="multiple" [paginator]="true"
+ [rows]="18" [rowsPerPageOptions]="[10,12,14,16,18,20,25,50]" selectionMode="multiple"
+ [(selection)]="selectedMsInstances" (onFilter)="onTableFiltered(dt.filteredValue)" dataKey="id">
+ <ng-template pTemplate="caption">
+
+ <div class="table_caption_header">
+ <!--Microservices Table Header-->
+ <div style="float: left;">
+ <!--Refresh-->
+ <i class="fa fa-refresh" (click)="getAllInstances()"></i>
+ <!--Global Filter-->
+ <input type="text" pInputText size="50" placeholder="Global Filter"
+ (input)="dt.filterGlobal($event.target.value, 'contains')"
+ class="table_global_filter">
+ <i class="fa fa-search" style="margin:4px 0 0 8px"></i>
+ </div>
+
+ <div class="table_title">
+ <h4><b>Microservice Instances</b></h4>
+ </div>
+ </div>
+
+ </ng-template>
+
+ <!--column headers-->
+ <ng-template pTemplate="header" let-columns>
+ <tr>
+ <th style="width: 3em"></th>
+ <th class="ui-state-highlight" *ngFor="let col of columns" style="outline: none; vertical-align: bottom; text-align: center;" [pSortableColumn]="col.field" [ngStyle]="{'width': col.width}">
+ {{col.header}}<br>
+ <p-sortIcon [field]="col.field" style="font-size: 8px;"></p-sortIcon>
+ </th>
+ <!--actions column-->
+ <th style="width: 7%;">
+ Actions
+ </th>
+ </tr>
+ <!--Second header row for individual column filters-->
+ <tr>
+ <th style="width: 3em"></th>
+ <th *ngFor="let col of columns" [ngSwitch]="col.field">
+ <input pInputText type="text" (input)="dt.filter($event.target.value, col.field, 'contains')" class="table_column_filter" placeholder="Filter">
+ </th>
+ <th>
+ <div style="text-align: center;">
+ <p-tableHeaderCheckbox style="padding-right: 5px;"></p-tableHeaderCheckbox>
+ <button pButton type="button" class="ui-button-secondary" (click)="checkCanGenerateBp()" [matMenuTriggerFor]="menu"
+ style="background-color: transparent; border: none; width: 20px; height: 20px; vertical-align: middle;">
+ <i class="pi pi-ellipsis-h" style="color: grey;"></i>
+ </button>
+ <mat-menu #menu="matMenu" xPosition="before">
+
+ <div style="background-color: rgba(128, 128, 128, 0.25);">
+ <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-plus"
+ style="font-size: 10px;"></i> Add</span>
+ </div>
+
+ <div matTooltip="{{generateSelectedBPsTooltip}}" [matTooltipDisabled]="canGenerateSelectedBPs" matTooltipPosition="left">
+ <button mat-menu-item (click)="generateSelectedBlueprints()" class="table_action_item" [disabled]="!canGenerateSelectedBPs" style="margin-top: 5px;">Generate Selected Blueprints</button>
+ </div>
+ </mat-menu>
+ </div>
+ </th>
+ </tr>
+ </ng-template>
+
+ <!--row data-->
+ <ng-template pTemplate="body" let-rowData let-expanded="expanded" let-msElem>
+ <tr>
+ <!--Column for row expand buttons-->
+ <td>
+ <a href="#" [pRowToggler]="rowData">
+ <i [ngClass]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"></i>
+ </a>
+ </td>
+
+ <td *ngFor="let col of cols">
+ <div *ngIf="col.field==='status'"
+ style="width: fit-content; width: -moz-max-content; padding: 0px 5px 0px 5px; border-radius: 3px; font-weight: 600;" [ngClass]="{
+ 'greenStatus' : msElem[col.field] === 'DEV_COMPLETE' || msElem[col.field] === 'CERTIFIED' || msElem[col.field] === 'PROD_DEPLOYED',
+ 'blueStatus' : msElem[col.field] === 'NEW' || msElem[col.field] === 'IN_DEV' || msElem[col.field] === 'IN_TEST'}">
+ {{msElem[col.field]}}
+ </div>
+ <div *ngIf="col.field!=='status'">{{msElem[col.field]}}</div>
+ </td>
+
+ <td>
+ <div style="text-align: center">
+ <p-tableCheckbox [value]="rowData" style="padding-right: 5px;"></p-tableCheckbox>
+ <button pButton type="button" class="ui-button-secondary" [matMenuTriggerFor]="menu"
+ style="background-color: transparent; border: none; width: 20px; height: 20px; vertical-align: middle;">
+ <i class="pi pi-ellipsis-h" style="color: grey;"></i>
+ </button>
+ <mat-menu #menu="matMenu" xPosition="before">
+
+ <div style="background-color: rgba(128, 128, 128, 0.25);">
+ <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-plus" style="font-size: 10px;"></i> Add</span>
+ </div>
+ <!-- * * * * Add component spec * * * * -->
+ <button mat-menu-item class="table_action_item" (click)="showAddCSDialog(rowData)">Component Spec...</button>
+ <!-- * * * * Generate Blueprint * * * * -->
+ <div matTooltip="No Active Component Spec" [matTooltipDisabled]="rowData.activeSpec!==null" matTooltipPosition="left">
+ <button mat-menu-item class="table_action_item" (click)="generateBlueprints(rowData)" [disabled]="rowData.activeSpec===null">Generate Blueprint</button>
+ </div>
+
+ <div style="background-color: rgba(128, 128, 128, 0.25);">
+ <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-search" style="font-size: 10px;"></i> View</span>
+ </div>
+ <!-- * * * * Go to spec files table * * * * -->
+ <div matTooltip="No Component Specs" [matTooltipDisabled]="rowData.activeSpec!==null" matTooltipPosition="left">
+ <button mat-menu-item class="table_action_item" (click)="viewCompSpecs(rowData)" [disabled]="rowData.activeSpec===null">Component Specs</button>
+ </div>
+ <!-- * * * * Go to blueprints table * * * * -->
+ <button mat-menu-item class="table_action_item" (click)="viewBlueprints(rowData)" [disabled]="rowData.activeSpec===null">Blueprints</button>
+
+ <div style="background-color: rgba(128, 128, 128, 0.25);">
+ <span style="font-size: 12px; margin-left: 10px; font-weight: 500;"><i class="pi pi-pencil"></i> Update</span>
+ </div>
+ <!-- * * * * Update ms instance record * * * * -->
+ <button mat-menu-item class="table_action_item" (click)="showAddChangeDialog(rowData)">Update MS Instance...</button>
+ </mat-menu>
+ </div>
+ </td>
+ </tr>
+ </ng-template>
+
+ <!--Row expand content-->
+ <ng-template pTemplate="rowexpansion" let-rowData let-columns="columns">
+ <tr>
+ <td [attr.colspan]="columns.length + 2">
+ <div class="row-expand-layout" [@rowExpansionTrigger]="'active'">
+ <!-- Audit Fields -->
+ <div class="row-expand-card" style="background-color: rgba(95, 158, 160, 0.295);">
+ <b>Created By:</b> {{rowData.metadata.createdBy}}<br>
+ <b>Created On:</b> {{rowData.metadata.createdOn}}<br>
+ <b>Updated By:</b> {{rowData.metadata.updatedBy}}<br>
+ <b>Updated On:</b> {{rowData.metadata.updatedOn}}
+ </div>
+ <!-- People -->
+ <div class="row-expand-card" style="background-color: rgba(160, 159, 95, 0.295)">
+ <b>Scrum Lead: </b>{{rowData.metadata.scrumLead}}
+ <span *ngIf="rowData.metadata.scrumLeadId"> ({{rowData.metadata.scrumLeadId}})</span><br/>
+ <b>Systems Engineer: </b>{{rowData.metadata.systemsEngineer}}
+ <span *ngIf="rowData.metadata.systemsEngineerId"> ({{rowData.metadata.systemsEngineerId}})</span><br/>
+ <b>Developer: </b>{{rowData.metadata.developer}}
+ <span *ngIf="rowData.metadata.developerId"> ({{rowData.metadata.developerId}})</span>
+ </div>
+ <!-- Notes -->
+ <div class="row-expand-card" style="background-color: rgba(100, 148, 237, 0.295); white-space: pre-line;">
+ <b>Notes:</b><br>
+ <p-scrollPanel [style]="{width: '100%', height: '62px'}">
+ <div style="font-size: 12px; word-break: normal;">{{rowData.metadata.notes}}</div>
+ </p-scrollPanel>
+ </div>
+ <!-- Labels -->
+ <div class="row-expand-card" style="background-color: rgba(76, 65, 225, 0.295)">
+ <b style="padding-bottom: 5px;">Labels:</b><br>
+ <div *ngFor="let label of rowData['metadata']['labels']"
+ style="display: inline-flex; margin-top: 5px;">
+ <div style="padding: 2px 7px 3px 0px;">
+ <span style="background-color: rgba(80, 80, 80, 0.185); padding: 5px; border-radius: 3px;">{{label}}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </td>
+ </tr>
+ </ng-template>
+
+ </p-table>
+
+ <!--download buttons for exporting table to either csv or excel file-->
+ <div *ngIf="loadTable" class="table_export_buttons_alignment">
+ <button pButton type="button" class="table_export_button" (click)="exportTable('csv')" matTooltip="Export Table to CSV" matTooltipPosition="above" style="width: 55px;">
+ <i class="pi pi-file" style="margin-top: 3px; margin-left: 4px;"></i>
+ <label style="font-weight: 800; margin-top: 1px;">CSV</label>
+ </button>
+ <button pButton type="button" class="table_export_button" (click)="exportTable('excel')" matTooltip="Export Table to XLSX" matTooltipPosition="above" style="width: 65px; background-color: green;">
+ <i class="pi pi-file-excel" style="margin-top: 3px; margin-left: 4px;"></i>
+ <label style="font-weight: 800; margin-top: 1px">Excel</label>
+ </button>
+ </div>
+
+ <!-- Dialog to Change an MS Instance -->
+ <app-ms-instance-add *ngIf="showAddChangeMsInstance" [visible]="showAddChangeMsInstance" [msName]="msName" [msInstanceChange]="msInstanceChange" [currentRow]="currentRow" (handler)="addChangeMsInstance($event)"></app-ms-instance-add>
+
+ <!-- Dialog to Add a Component Spec -->
+ <app-comp-spec-add *ngIf="showCsAddDialog" [visible]="showCsAddDialog" (handler)="addNewCs($event)"></app-comp-spec-add>
+
+ <!--Pop-up for "Success" changing MS Instance-->
+ <p-toast key="changeSuccess"></p-toast>
+ <!--Pop-up for "Success" adding Component Spec-->
+ <p-toast key="compSpecAdded" [style]="{width: '400px'}"></p-toast>
+ <!--Pop-up for "Error" adding Component Spec-->
+ <p-toast class="toast-newline" key="errorOnCsAdd" [style]="{width: '700px'}"></p-toast>
+
+ <p-toast class="toast-newline" key="csViewError" [style]="{width: '700px'}"></p-toast>
+
+ <p-toast key="bpGenMessage" [style]="{width: '450px'}"></p-toast>
+
+</div> \ 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<MsInstancesComponent>;
+
+ 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<any> = new Array<any>();
+ dataSource = new MatTableDataSource<msInstance>(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 (<app-ms-instance-add> 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 @@
+<!--
+ # ============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=========================================================
+ -->
+
+<ng4-loading-spinner id=loadingSpinner [timeout]="60000"></ng4-loading-spinner>
+<div id="content">
+ <iframe width="100%" height="98.5%" [src]="video | safe" onload="document.getElementById('loadingSpinner').style.display='none';"></iframe>
+</div>
+
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<OnboardingToolsComponent>;
+
+ 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 @@
+<!--
+ # ============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=========================================================
+ -->
+
+<p-card header="Register a New User" [style]="{margin:'100px 200px 100px 200px'}" class="ui-card">
+ <hr class="line-break">
+ <form [formGroup]="form" class="p-5 bg-faded" style="margin-left: 250px;margin-bottom: 100px;">
+ <div class="ui-g ui-fluid">
+ <div class="ui-g-12 ui-md-6">
+ <div class="ui-inputgroup" >
+ <span class="ui-inputgroup-addon"><i class="pi pi-user" style="line-height: 1.25;"></i></span>
+ <input type="text" pInputText placeholder="ATT UID" formControlName="username" class="ui-inputtext">
+ </div>
+ </div>
+ </div>
+ <i *ngIf="form.get('username').errors && form.get('username').errors.minlength" style="width: 140px;margin-left: 20px;font-size: small;color: red;">username should be at least 5 characters</i>
+ <i *ngIf="form.get('username').errors && form.get('username').errors.maxlength" style="width: 140px;margin-left: 20px;font-size: small;color: red;">username should be at most 10 characters</i>
+ <div class="ui-g ui-fluid" >
+ <div class="ui-g-12 ui-md-6">
+ <div class="ui-inputgroup" >
+ <span class="ui-inputgroup-addon"><i class="pi pi-user" style="line-height: 1.25;"></i></span>
+ <input type="text" pInputText placeholder="Full Name" formControlName="fullName" class="ui-inputtext">
+ </div>
+ </div>
+ </div>
+ <div class="ui-g ui-fluid" >
+ <div class="ui-g-12 ui-md-6">
+ <div class="ui-inputgroup">
+ <button pButton type="button" icon="pi pi-refresh" class="ui-button-warn" (click)="generateNewPassword()" ></button>
+ <input type="text" pInputText formControlName="password" placeholder="Generate password" class="ui-inputtext">
+ </div>
+ </div>
+ </div>
+ <i *ngIf="form.get('password').errors && form.get('password').errors.minlength" style="width: 140px;margin-left: 20px;font-size: small;color: red;">password should be at least 6 characters</i>
+ <div class="ui-g ui-fluid" >
+ <div class="ui-g-12 ui-md-6">
+ <div class="ui-inputgroup" >
+ <span class="ui-inputgroup-addon"><i class="pi pi-users" style="line-height: 1.25; height: 1em;"></i></span>
+ <p-multiSelect [options]="rolesFromBackend" formControlName="roles" [showToggleAll]="true" [virtualScroll]="true" [filter]="false" [style]="{height:'3em'}" class="ui-multiselect"></p-multiSelect>
+ </div>
+ </div>
+ </div>
+ <p-footer class="text-left ui-g-12">
+ <button pButton type="button" class="ui-button-info" label="Cancel" (click)="cancel()" style="margin-right: .25em"></button>
+ <button pButton type="submit" class="ui-button-success" label="Register" [disabled]="!form.valid" (click)="submit()"></button>
+ </p-footer>
+ </form>
+</p-card>
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<RegisterComponent>;
+
+ 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 @@
+<!--
+ # ============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=========================================================
+ -->
+
+<p-card header="Password Reset" [style]="{margin:'100px 300px 500px 300px'}">
+ <hr class="line-break">
+ <form [formGroup]="form" (ngSubmit)="submit()" class="p-5 bg-faded" style="margin-left: 200px;margin-bottom: 100px;">
+ <div class="ui-g ui-fluid" >
+ <div class="ui-g-12 ui-md-4">
+ <div class="ui-inputgroup" >
+ <span class="ui-inputgroup-addon"><i class="pi pi-user" style="line-height: 1.25;"></i></span>
+ <input type="text" pInputText placeholder="User id" formControlName="id" >
+ </div>
+ </div>
+ </div>
+ <div *ngIf="!authService.isAdmin; else admin">
+ <div class="ui-g ui-fluid">
+ <div class="ui-g-12 ui-md-4">
+ <div class="ui-inputgroup" >
+ <span class="ui-inputgroup-addon"><i class="pi pi-key" style="line-height: 1.25;"></i></span>
+ <input type="text" pInputText placeholder="New Password" formControlName="newPassword">
+ </div>
+ </div>
+ </div>
+ <div class="ui-g ui-fluid">
+ <div class="ui-g-12 ui-md-4">
+ <div class="ui-inputgroup" >
+ <span class="ui-inputgroup-addon"><i class="pi pi-key" style="line-height: 1.25;"></i></span>
+ <input type="text" pInputText placeholder="Confirm Password" formControlName="confirm_newPassword">
+ </div>
+ </div>
+ </div>
+ </div>
+ <ng-template class="ui-g ui-fluid" #admin>
+ <div class="ui-g-12 ui-md-4">
+ <div class="ui-inputgroup">
+ <input type="text" pInputText formControlName="password" placeholder="generate password">
+ <button pButton type="button" icon="pi pi-refresh" class="ui-button-warn" (click)="generateNewPassword()" ></button>
+ </div>
+ </div>
+ </ng-template>
+ <p-footer class="text-left ui-g-12">
+ <button pButton type="button" class="ui-button-info" label="Cancel" (click)="cancel()" style="margin-right: .25em"></button>
+ <button pButton type="submit" class="ui-button-success" label="Login" [disabled]="!form.valid"></button>
+ </p-footer>
+ <div class="text-left ui-g-12">
+ <a routerLink="/register">Not a registered user? Click here to register now!</a>
+ </div>
+ </form>
+ </p-card>
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<ResetPasswordComponent>;
+
+ 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<AuthResponse> {
+ return this.http.post<AuthResponse>(`http://${environment.api_baseURL}:31003/api/auth/signup`, user);
+ // return this.http.post<AuthResponse>(`http://localhost:8082/api/auth/signup`, user);
+
+ }
+
+ login(user: User): Observable<AuthResponse> {
+ return this.http.post<AuthResponse>(`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<boolean>{
+ return this.http.post<boolean>(`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<any[]> = 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<any> {
+ let url = this.URL + msInstanceId;
+ let body = addCsJson;
+ let headers = new HttpHeaders({
+ 'Content-Type': 'application/json'
+ });
+
+ let options = {headers:headers}
+ return this.http.post<any>(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<any>(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<any>(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<any>, next: import("@angular/common/http").HttpHandler): import("rxjs").Observable<import("@angular/common/http").HttpEvent<any>> {
+ 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<any>(URL, body);
+ } else {
+ return this.http.patch<any>(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<any> {
+ 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<any>(url, body, options);
+ } else {
+ url = url + "/" + msID;
+ return this.http.patch<any>(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<User[]> {
+ return this.http.get<User[]>(`http://${environment.api_baseURL}:31003/api/users/getAll`);
+ }
+
+ editUser(username: string, user: User): Observable<any>{
+ return this.http.patch<any>(`http://${environment.api_baseURL}:31003/api/users/admin/${username}`, user);
+ }
+
+ editProfile(username: string, user: User): Observable<any>{
+ return this.http.patch<any>(`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 @@
+<!--
+ # ============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=========================================================
+ -->
+
+<div style="margin: 0px 20px 10px 20px">
+ <p-table #dt [value]="users" [rowHover]="true">
+ <ng-template pTemplate="caption">
+ <h5><strong>System User List and Management</strong><i style="font-size: smaller;"> ( for admin only )</i></h5>
+ <br/>
+ <a routerLink="/register">Want to register a new user? Click here!</a>
+ </ng-template>
+ <ng-template pTemplate="header">
+ <tr>
+ <th style="width: 15%">Username (ATT UID)</th>
+ <th>Full Name</th>
+ <th>Roles</th>
+ <!-- <th>Active Status</th> -->
+ <th style="width: 15%">Actions</th>
+ </tr>
+ </ng-template>
+ <ng-template pTemplate="body" let-user>
+ <tr>
+ <td>{{user.username}}</td>
+ <td>{{user.fullName}}</td>
+ <td>{{user.roles}}</td>
+ <!-- <td>true</td> -->
+ <td>
+ <i class="pi pi-trash" (click)="handleDelete(user.username)" pTooltip="delete user" tooltipPosition="right"></i>
+ <i class="pi pi-pencil" (click)="handleEdit(user)" pTooltip="edit user" tooltipPosition="right" style="margin-left: .5em;"></i>
+ </td>
+ </tr>
+ </ng-template>
+ </p-table>
+
+ <!--edit user information dialog-->
+ <p-dialog [(visible)]="editUserFlag" appendTo="body" [modal]="true" [transitionOptions]="'300ms'" [style]="{width: '635px'}" [baseZIndex]="10000"
+ [closable]="true" (onHide)="closeEditDialog()">
+ <p-header style="display: inline-flex;">
+ Edit User Information
+ </p-header>
+
+ <form [formGroup]="editUserForm">
+ <!-- * * * Username * * * -->
+ <div class="input">
+ <label class="inputLabel">ATT UID</label>&nbsp;
+ <b>{{editUser.username}}</b>
+ </div>
+ <!-- * * * User Full Name * * * -->
+ <div class="input">
+ <label class="inputLabel">Full Name</label>&nbsp;
+ <input class="inputFieldSm" type="text" pInputText formControlName="fullName"/>
+ </div>
+ <!-- * * * Roles * * * -->
+ <div class="input">
+ <label class="inputLabel">Roles</label>&nbsp;
+ <p-multiSelect [options]="rolesFromBackend" formControlName="roles" [showToggleAll]="true" [virtualScroll]="true" [filter]="false" [style]="{height:'3.6em', width:'200px'}"></p-multiSelect>
+ <!-- <b>{{editUser.roles}}</b> -->
+ </div>
+ <!-- * * * Re-Generate Password * * * -->
+ <div class="input">
+ <div class="ui-inputgroup">
+ <label class="inputLabel">New Password</label>&nbsp;
+ <button pButton type="button" icon="pi pi-refresh" class="ui-button-warn" (click)="generateNewPassword()" ></button>
+ <input type="text" pInputText formControlName="password" placeholder="Generate password" class="ui-inputtext" pTooltip="Password should be greater than 5 characters" tooltipPosition="right">
+ </div>
+ </div>
+ <i *ngIf="editUserForm.get('password').errors && editUserForm.get('password').errors.minlength" style="width: 140px;margin-left: 20px;font-size: small;color: red;">password should be at least 6 characters</i>
+ <!-- * * * Submit and Cancel buttons * * * -->
+ <div style="margin-top: 2em; margin-left: 1.3em; margin-bottom: 2em;">
+ <button pButton type="button" (click)="closeEditDialog()" label="Cancel"></button>&nbsp;
+ <button pButton type="submit" (click)="submitEdit(editUser)" class="ui-button-success" label="Submit" style="width: 70px"></button>
+ </div>
+ </form>
+</p-dialog>
+</div> \ 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<UserManagementComponent>;
+
+ 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<String>= [];
+
+ 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);
+ });
+ }
+
+}