diff --git a/components/automate-ui/src/app/entities/clients/client.action.ts b/components/automate-ui/src/app/entities/clients/client.action.ts index 9e963921879..f9185824302 100644 --- a/components/automate-ui/src/app/entities/clients/client.action.ts +++ b/components/automate-ui/src/app/entities/clients/client.action.ts @@ -11,7 +11,20 @@ export enum ClientActionTypes { GET_FAILURE = 'CLIENTS::GET::FAILURE', DELETE = 'CLIENTS::DELETE', DELETE_SUCCESS = 'CLIENTS::DELETE::SUCCESS', - DELETE_FAILURE = 'CLIENTS::DELETE::FAILURE' + DELETE_FAILURE = 'CLIENTS::DELETE::FAILURE', + CREATE = 'CLIENTS::CREATE', + CREATE_SUCCESS = 'CLIENTS::CREATE::SUCCESS', + CREATE_FAILURE = 'CLIENTS::CREATE::FAILURE' +} + +export interface CreateClientSuccessPayload { + name: string; + client_key: { + name: string, + public_key: string, + expiration_date: string, + private_key: string + }; } export interface ClientsSuccessPayload { @@ -58,6 +71,29 @@ export class GetClientFailure implements Action { constructor(public payload: HttpErrorResponse) { } } +export interface CreateClientPayload { + name: string; + validator: boolean; + org_id: string; + server_id: string; + create_key: boolean; +} + +export class CreateClient implements Action { + readonly type = ClientActionTypes.CREATE; + constructor(public payload: CreateClientPayload) { } +} + +export class CreateClientSuccess implements Action { + readonly type = ClientActionTypes.CREATE_SUCCESS; + constructor(public payload: CreateClientSuccessPayload) { } +} + +export class CreateClientFailure implements Action { + readonly type = ClientActionTypes.CREATE_FAILURE; + constructor(public payload: HttpErrorResponse) { } +} + export class DeleteClient implements Action { readonly type = ClientActionTypes.DELETE; constructor(public payload: { server_id: string, org_id: string, name: string }) { } @@ -80,6 +116,9 @@ export type ClientActions = | GetClient | GetClientSuccess | GetClientFailure + | CreateClient + | CreateClientSuccess + | CreateClientFailure | DeleteClient | DeleteClientSuccess | DeleteClientFailure; diff --git a/components/automate-ui/src/app/entities/clients/client.effects.ts b/components/automate-ui/src/app/entities/clients/client.effects.ts index 7434ad6594c..0500e33ea34 100644 --- a/components/automate-ui/src/app/entities/clients/client.effects.ts +++ b/components/automate-ui/src/app/entities/clients/client.effects.ts @@ -2,9 +2,10 @@ import { Injectable } from '@angular/core'; import { HttpErrorResponse } from '@angular/common/http'; import { Actions, Effect, ofType } from '@ngrx/effects'; import { of as observableOf } from 'rxjs'; -import { catchError, mergeMap, map } from 'rxjs/operators'; +import { catchError, mergeMap, map, filter } from 'rxjs/operators'; import { CreateNotification } from 'app/entities/notifications/notification.actions'; import { Type } from 'app/entities/notifications/notification.model'; +import { HttpStatus } from 'app/types/types'; import { GetClients, @@ -15,6 +16,10 @@ import { GetClient, GetClientSuccess, GetClientFailure, + CreateClient, + CreateClientSuccess, + CreateClientSuccessPayload, + CreateClientFailure, DeleteClient, DeleteClientSuccess, DeleteClientFailure @@ -69,6 +74,31 @@ export class ClientEffects { }); })); + @Effect() + createClient$ = this.actions$.pipe( + ofType(ClientActionTypes.CREATE), + mergeMap((action: CreateClient) => + this.requests.createClient(action.payload).pipe( + map((resp: CreateClientSuccessPayload) => new CreateClientSuccess(resp)), + catchError((error: HttpErrorResponse) => observableOf(new CreateClientFailure(error)))))); + + @Effect() + createClientSuccess$ = this.actions$.pipe( + ofType(ClientActionTypes.CREATE_SUCCESS), + map(({ payload: { name } }: CreateClientSuccess) => new CreateNotification({ + type: Type.info, + message: `Created client ${name}` + }))); + + @Effect() + createClientFailure$ = this.actions$.pipe( + ofType(ClientActionTypes.CREATE_FAILURE), + filter(({ payload }: CreateClientFailure) => payload.status !== HttpStatus.CONFLICT), + map(({ payload }: CreateClientFailure) => new CreateNotification({ + type: Type.error, + message: `Could not create client: ${payload.error.error || payload}` + }))); + @Effect() deleteClient$ = this.actions$.pipe( ofType(ClientActionTypes.DELETE), @@ -98,4 +128,5 @@ export class ClientEffects { message: `Could not delete client: ${msg || error}` }); })); + } diff --git a/components/automate-ui/src/app/entities/clients/client.reducer.ts b/components/automate-ui/src/app/entities/clients/client.reducer.ts index 80425cd228b..6395881070d 100644 --- a/components/automate-ui/src/app/entities/clients/client.reducer.ts +++ b/components/automate-ui/src/app/entities/clients/client.reducer.ts @@ -1,8 +1,9 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; -import { set, pipe } from 'lodash/fp'; +import { set, pipe, unset } from 'lodash/fp'; import { EntityStatus } from 'app/entities/entities'; import { ClientActionTypes, ClientActions } from './client.action'; import { Client } from './client.model'; +import { HttpErrorResponse } from '@angular/common/http'; export interface ClientEntityState extends EntityState { clientsStatus: EntityStatus; @@ -12,21 +13,29 @@ export interface ClientEntityState extends EntityState { total: number }; deleteStatus: EntityStatus; + saveStatus: EntityStatus; + saveError: HttpErrorResponse; + createClient: { + client_key: Object, + name: string + }; } const GET_ALL_STATUS = 'getAllStatus'; const DELETE_STATUS = 'deleteStatus'; +const SAVE_STATUS = 'saveStatus'; +const SAVE_ERROR = 'saveError'; export const clientEntityAdapter: EntityAdapter = createEntityAdapter({ - selectId: (client: Client) => client.name + selectId: (client: Client) => client.name }); export const ClientEntityInitialState: ClientEntityState = clientEntityAdapter.getInitialState({ - getAllStatus: EntityStatus.notLoaded, - deleteStatus: EntityStatus.notLoaded -}); + getAllStatus: EntityStatus.notLoaded, + deleteStatus: EntityStatus.notLoaded + }); export function clientEntityReducer( state: ClientEntityState = ClientEntityInitialState, @@ -38,13 +47,34 @@ export function clientEntityReducer( case ClientActionTypes.GET_ALL_SUCCESS: return pipe( + set(GET_ALL_STATUS, EntityStatus.loadingSuccess), set('clientList.items', action.payload.clients || []), set('clientList.total', action.payload.total || 0) - )(state) as ClientEntityState; + )(state) as ClientEntityState; case ClientActionTypes.GET_ALL_FAILURE: return set(GET_ALL_STATUS, EntityStatus.loadingFailure, state); + case ClientActionTypes.CREATE: { + return set(SAVE_STATUS, EntityStatus.loading, state) as ClientEntityState; + } + + case ClientActionTypes.CREATE_SUCCESS: { + return pipe( + unset(SAVE_ERROR), + set(SAVE_STATUS, EntityStatus.loadingSuccess), + set('createClient.client_key', action.payload.client_key || []), + set('createClient.name', action.payload.name || '') + )(state) as ClientEntityState; + } + + case ClientActionTypes.CREATE_FAILURE: { + return pipe( + set(SAVE_ERROR, action.payload.error), + set(SAVE_STATUS, EntityStatus.loadingFailure) + )(state) as ClientEntityState; + } + case ClientActionTypes.DELETE: return set(DELETE_STATUS, EntityStatus.loading, state); @@ -55,7 +85,7 @@ export function clientEntityReducer( return pipe( set(DELETE_STATUS, EntityStatus.loadingSuccess), set('clientList.items', clients || []), - set('clientList.total', total || 0 ) + set('clientList.total', total || 0) )(state) as ClientEntityState; case ClientActionTypes.DELETE_FAILURE: diff --git a/components/automate-ui/src/app/entities/clients/client.requests.ts b/components/automate-ui/src/app/entities/clients/client.requests.ts index 2384dd9ac00..5df8c53df93 100644 --- a/components/automate-ui/src/app/entities/clients/client.requests.ts +++ b/components/automate-ui/src/app/entities/clients/client.requests.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs'; import { environment as env } from 'environments/environment'; -import { ClientsPayload } from './client.action'; +import { ClientsPayload, CreateClientPayload } from './client.action'; import { Client } from './client.model'; import { InterceptorSkipHeader } from 'app/services/http/http-client-auth.interceptor'; @@ -13,6 +13,16 @@ export interface ClientsResponse { total: number; } +export interface CreateClientResponse { + name: string; + client_key: { + name: string, + public_key: string, + expiration_date: string, + private_key: string + }; +} + @Injectable() export class ClientRequests { @@ -36,9 +46,14 @@ export class ClientRequests { `${env.infra_proxy_url}/servers/${server_id}/orgs/${org_id}/clients/${name}`, {headers}); } + public createClient(payload: CreateClientPayload): Observable { + return this.http.post( + `${env.infra_proxy_url}/servers/${payload.server_id}/orgs/${payload.org_id}/clients`, + payload); + } + public deleteClient(server_id: string, org_id: string, name: string): Observable<{}> { return this.http.delete(`${env.infra_proxy_url}/servers/${server_id}/orgs/${org_id}/clients/${name}`, {headers}); } - } diff --git a/components/automate-ui/src/app/entities/clients/client.selectors.ts b/components/automate-ui/src/app/entities/clients/client.selectors.ts index 4f3a86fd6d7..2b4ebc66562 100644 --- a/components/automate-ui/src/app/entities/clients/client.selectors.ts +++ b/components/automate-ui/src/app/entities/clients/client.selectors.ts @@ -26,3 +26,18 @@ export const clientList = createSelector( clientState, (state) => state.clientList ); + +export const saveStatus = createSelector( + clientState, + (state) => state.saveStatus +); + +export const saveError = createSelector( + clientState, + (state) => state.saveError +); + +export const createClient = createSelector( + clientState, + (state) => state.createClient +); diff --git a/components/automate-ui/src/app/modules/infra-proxy/clients/clients.component.html b/components/automate-ui/src/app/modules/infra-proxy/clients/clients.component.html index 16a2bf66827..78e228c1695 100644 --- a/components/automate-ui/src/app/modules/infra-proxy/clients/clients.component.html +++ b/components/automate-ui/src/app/modules/infra-proxy/clients/clients.component.html @@ -1,4 +1,7 @@
+ + + Create Client
@@ -33,7 +37,7 @@ {{ client.name }} - + Delete diff --git a/components/automate-ui/src/app/modules/infra-proxy/clients/clients.component.scss b/components/automate-ui/src/app/modules/infra-proxy/clients/clients.component.scss index 066d77485a6..a9bc2ff53dd 100644 --- a/components/automate-ui/src/app/modules/infra-proxy/clients/clients.component.scss +++ b/components/automate-ui/src/app/modules/infra-proxy/clients/clients.component.scss @@ -42,6 +42,7 @@ chef-loading-spinner { background-color: $chef-white; opacity: 0.7; width: initial; + height: 100%; } .empty-section { @@ -60,3 +61,26 @@ img { .three-dot-column { text-align: right; } + +chef-button button { + line-height: 20px; + letter-spacing: 0.5px; + padding: 8px 16px; + + &:focus { + outline: none; + } + +} + +.clients-list-paging { + margin: 0 0 35px; +} + +::ng-deep { + .search-items app-infra-search-bar .search-wrapper { + width: 87%; + float: left; + } + +} diff --git a/components/automate-ui/src/app/modules/infra-proxy/clients/clients.component.spec.ts b/components/automate-ui/src/app/modules/infra-proxy/clients/clients.component.spec.ts index 818080aa786..4cdd223aa28 100644 --- a/components/automate-ui/src/app/modules/infra-proxy/clients/clients.component.spec.ts +++ b/components/automate-ui/src/app/modules/infra-proxy/clients/clients.component.spec.ts @@ -39,6 +39,7 @@ describe('ClientsComponent', () => { MockComponent({ selector: 'input', inputs: ['resetOrigin'] }), MockComponent({ selector: 'mat-select' }), MockComponent({ selector: 'mat-option' }), + MockComponent({ selector: 'app-create-client-modal', inputs: ['openEvent'] }), ClientsComponent ], providers: [ diff --git a/components/automate-ui/src/app/modules/infra-proxy/clients/clients.component.ts b/components/automate-ui/src/app/modules/infra-proxy/clients/clients.component.ts index 713b3011ab8..f737ee54fdc 100644 --- a/components/automate-ui/src/app/modules/infra-proxy/clients/clients.component.ts +++ b/components/automate-ui/src/app/modules/infra-proxy/clients/clients.component.ts @@ -8,11 +8,7 @@ import { NgrxStateAtom } from 'app/ngrx.reducers'; import { LayoutFacadeService, Sidebar } from 'app/entities/layout/layout.facade'; import { GetClients, DeleteClient } from 'app/entities/clients/client.action'; import { Client } from 'app/entities/clients/client.model'; -import { - getAllStatus, - clientList -} from 'app/entities/clients/client.selectors'; - +import { getAllStatus, clientList } from 'app/entities/clients/client.selectors'; @Component({ selector: 'app-clients', @@ -38,12 +34,12 @@ export class ClientsComponent implements OnInit, OnDestroy { public clientToDelete: Client; public deleteModalVisible = false; private isDestroyed = new Subject(); - + public openNotificationModal = new EventEmitter(); constructor( private store: Store, private layoutFacade: LayoutFacadeService - ) { } + ) {} ngOnInit() { this.layoutFacade.showSidebar(Sidebar.Infrastructure); @@ -103,6 +99,10 @@ export class ClientsComponent implements OnInit, OnDestroy { this.store.dispatch(new GetClients(payload)); } + openCreateClientModal() { + this.openNotificationModal.emit(); + } + resetKeyTabRedirection(resetLink: boolean) { this.resetKeyRedirection.emit(resetLink); } diff --git a/components/automate-ui/src/app/modules/infra-proxy/create-client-modal/create-client-modal.component.html b/components/automate-ui/src/app/modules/infra-proxy/create-client-modal/create-client-modal.component.html new file mode 100644 index 00000000000..9819ae52b4f --- /dev/null +++ b/components/automate-ui/src/app/modules/infra-proxy/create-client-modal/create-client-modal.component.html @@ -0,0 +1,40 @@ + +

Create Client

+
+
+
+ + + {{error}} + + + Validation Client + +
+
+

Client successfully created. Copy the private key below and keep it somewhere safe. It is NOT stored on + the server.

+
+ +
+
+ Cancel + Close + + + Create + Creating... + + + Download + +
+
+
+
\ No newline at end of file diff --git a/components/automate-ui/src/app/modules/infra-proxy/create-client-modal/create-client-modal.component.scss b/components/automate-ui/src/app/modules/infra-proxy/create-client-modal/create-client-modal.component.scss new file mode 100644 index 00000000000..6598b4b5990 --- /dev/null +++ b/components/automate-ui/src/app/modules/infra-proxy/create-client-modal/create-client-modal.component.scss @@ -0,0 +1,177 @@ +@import "~styles/variables"; + +chef-modal { + + [slot=title] { + font-style: normal; + font-weight: bold; + font-size: 24px; + line-height: 24px; + letter-spacing: 0.15px; + padding-bottom: 30px; + } + + .flex-container { + display: flex; + justify-content: center; + line-height: 20px; + letter-spacing: 0.25px; + + .input-margin { + margin-bottom: 30px; + } + + form { + flex-basis: 100%; + + input { + font-size: 13px; + } + + label { + font-size: 16px; + padding-bottom: 0; + } + + label .label { + font-weight: normal; + } + + textarea { + height: 300px; + text-align: left; + font-size: 12px; + text-transform: capitalize; + resize: none; + line-height: 20px; + letter-spacing: 0.25px; + } + + chef-checkbox { + margin-top: 16px; + } + + .detail { + background: $chef-success; + color: $white; + font-size: 12px; + line-height: 20px; + padding: 9px; + margin-bottom: 32px; + + .icon-wrap { + display: inline-block; + position: relative; + width: 40px; + + i { + display: block; + height: 16px; + width: 16px; + background-image: url('/assets/img/icon-check-circle.png'); + position: absolute; + top: -30px; + left: 8px; + } + + } + + p { + display: inline-block; + font-size: 12px; + line-height: 20px; + letter-spacing: 0.25px; + width: 80%; + color: $white; + text-transform: capitalize; + margin-bottom: 0; + } + + } + + } + + #button-bar { + text-align: right; + padding-top: 24px; + + chef-button { + margin-bottom: 0; + } + + #create-button-object-modal, #download-button-object-modal { + margin-right: 0; + } + + chef-loading-spinner { + position: relative; + top: 2px; + margin-right: 5px; + } + } + } +} + +::ng-deep { + chef-modal { + .modal { + box-shadow: 0px 0px 30px 0px #CCC; + outline: none; + width: 528px; + + chef-button { + &.close { + border: none; + color: $chef-black; + margin-right: 22px; + margin-top: 16px; + + &:hover, &:focus { + background: none; + outline: none; + } + } + + button { + line-height: 20px; + letter-spacing: 0.5px; + + &:focus { + outline: none; + } + } + + .cancel-button.hydrated { + background: none; + } + } + + chef-checkbox { + &[aria-checked=false] .check-wrap { + border-color: $chef-primary-bright; + } + + &:focus { + outline: none; + } + + .label-wrap { + margin-left: 8px; + } + + .check-wrap { + width: 16px; + min-width: 16px; + height: 16px; + } + } + + } + + .modal-overlay { + background-color: $chef-white; + opacity: 0.5; + } + + } +} diff --git a/components/automate-ui/src/app/modules/infra-proxy/create-client-modal/create-client-modal.component.spec.ts b/components/automate-ui/src/app/modules/infra-proxy/create-client-modal/create-client-modal.component.spec.ts new file mode 100644 index 00000000000..f8fe070821f --- /dev/null +++ b/components/automate-ui/src/app/modules/infra-proxy/create-client-modal/create-client-modal.component.spec.ts @@ -0,0 +1,66 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MockComponent } from 'ng2-mock-component'; +import { ReactiveFormsModule } from '@angular/forms'; +import { StoreModule } from '@ngrx/store'; +import { ngrxReducers, runtimeChecks } from 'app/ngrx.reducers'; +import { CreateClientModalComponent } from './create-client-modal.component'; +import { ClientKey } from 'app/entities/clients/client.model'; +import { EventEmitter } from '@angular/core'; + +describe('CreateClientModalComponent', () => { + let component: CreateClientModalComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + MockComponent({ selector: 'chef-modal', inputs: ['visible'] }), + MockComponent({ selector: 'chef-button', inputs: ['disabled'] }), + MockComponent({ selector: 'chef-error' }), + MockComponent({ selector: 'chef-form-field' }), + MockComponent({ selector: 'chef-checkbox', inputs: ['checked']}), + CreateClientModalComponent + ], + imports: [ + ReactiveFormsModule, + StoreModule.forRoot(ngrxReducers, { runtimeChecks }) + ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CreateClientModalComponent); + component = fixture.componentInstance; + component.openEvent = new EventEmitter(); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('#createClientForm', () => { + const clientKey: ClientKey = { + name: 'test_name', + public_key: 'test_public_key', + expiration_date: 'test_expiration_date', + private_key: 'test_private_key' + }; + + it('should be invalid when no fields are filled out', () => { + expect(component.createForm.valid).toBeFalsy(); + }); + + it('should be valid when all fields are filled out', () => { + component.createForm.controls['name'].setValue(clientKey.name); + expect(component.createForm.valid).toBeTruthy(); + }); + + it('opening create modal resets name to empty string', () => { + component.visible = true; + expect(component.createForm.controls['name'].value).toEqual(''); + }); + }); + +}); diff --git a/components/automate-ui/src/app/modules/infra-proxy/create-client-modal/create-client-modal.component.ts b/components/automate-ui/src/app/modules/infra-proxy/create-client-modal/create-client-modal.component.ts new file mode 100644 index 00000000000..44cd72f7151 --- /dev/null +++ b/components/automate-ui/src/app/modules/infra-proxy/create-client-modal/create-client-modal.component.ts @@ -0,0 +1,162 @@ +import { Component, EventEmitter, Input, OnDestroy, OnInit } from '@angular/core'; +import { IdMapper } from 'app/helpers/auth/id-mapper'; +import { Store } from '@ngrx/store'; +import { FormBuilder, Validators, FormGroup } from '@angular/forms'; +import { takeUntil } from 'rxjs/operators'; +import { NgrxStateAtom } from 'app/ngrx.reducers'; +import { Regex } from 'app/helpers/auth/regex'; +import { Subject, combineLatest } from 'rxjs'; +import { CreateClient, GetClients } from 'app/entities/clients/client.action'; +import { saveError, createClient } from 'app/entities/clients/client.selectors'; +import { isNil } from 'lodash/fp'; +import { saveAs } from 'file-saver'; + +@Component({ + selector: 'app-create-client-modal', + templateUrl: './create-client-modal.component.html', + styleUrls: ['./create-client-modal.component.scss'] +}) +export class CreateClientModalComponent implements OnInit, OnDestroy { + @Input() openEvent: EventEmitter; + @Input() serverId: string; + @Input() orgId: string; + + public checkedValidator = false; + public createdClient: string; + public creating = false; + public created = false; + public conflictError = false; + public createForm: FormGroup; + public client_key: any; + public conflictErrorEvent = new EventEmitter(); + public close = new EventEmitter(); + public error: string; + public privateKey: string; + public org: string; + public server: string; + public validator = false; + public visible = false; + private isDestroyed = new Subject(); + public page = 1; + public per_page = 9; + + constructor( + private fb: FormBuilder, + private store: Store + ) { + this.createForm = this.fb.group({ + name: ['', [Validators.required, Validators.pattern(Regex.patterns.NON_BLANK)]], + validator: [''] + }); + } + + ngOnInit() { + this.openEvent.pipe(takeUntil(this.isDestroyed)) + .subscribe(() => { + this.created = false; + this.conflictError = false; + this.visible = true; + this.server = this.serverId; + this.org = this.orgId; + this.error = ''; + this.privateKey = ''; + this.checkedValidator = false; + }); + + combineLatest([ + // this.store.select(getAllStatus), + this.store.select(saveError), + this.store.select(createClient) + ]).pipe( + takeUntil(this.isDestroyed)) + .subscribe(([ errorSt, createState]) => { + if ( !isNil(errorSt) ) { + this.created = false; + this.creating = false; + this.error = errorSt?.message; + } else { + this.creating = false; + this.created = true; + this.createdClient = createState?.name; + this.client_key = createState?.client_key; + this.privateKey = this.client_key?.private_key; + } + }); + } + + ngOnDestroy(): void { + this.isDestroyed.next(true); + this.isDestroyed.complete(); + } + + handleNameInput(event: KeyboardEvent): void { + if (!this.isNavigationKey(event)) { + this.conflictError = false; + this.error = ''; + this.createForm.controls.name.setValue( + IdMapper.transform(this.createForm.controls.name.value.trim())); + } + } + + public handleInput(event: KeyboardEvent): void { + if (this.isNavigationKey(event)) { + return; + } + this.conflictError = false; + } + + updateValidatorCheckbox(event: boolean): void { + this.checkedValidator = event; + this.createForm.controls.validator.setValue(this.checkedValidator); + } + + closeCreateModal(): void { + this.resetCreateModal(); + this.visible = false; + const payload = { + clientName: '', + server_id: this.serverId, + org_id: this.orgId, + page: this.page, + per_page: this.per_page + }; + this.store.dispatch(new GetClients(payload)); + } + + private resetCreateModal(): void { + this.creating = false; + this.created = false; + this.error = ''; + this.privateKey = ''; + this.createForm.reset(); + this.checkedValidator = false; + this.conflictErrorEvent.emit(false); + } + + createClient(): void { + this.creating = true; + const client = { + name: this.createForm.controls['name'].value.trim(), + validator: this.createForm.controls['validator'].value || this.checkedValidator, + server_id: this.serverId, + org_id: this.orgId, + create_key : true + }; + this.store.dispatch(new CreateClient(client)); + } + + downloadKey() { + const template = ` + Private RSA Key + + ${this.privateKey} + `; + + const blob = new Blob([template], { type: 'text/plain;charset=utf-8' }); + saveAs(blob, this.createdClient + '.pem'); + } + + private isNavigationKey(event: KeyboardEvent): boolean { + return event.key === 'Shift' || event.key === 'Tab'; + } +} diff --git a/components/automate-ui/src/app/modules/infra-proxy/data-bags-list/data-bags-list.component.html b/components/automate-ui/src/app/modules/infra-proxy/data-bags-list/data-bags-list.component.html index 1ebfa1e0681..81d1c5193eb 100644 --- a/components/automate-ui/src/app/modules/infra-proxy/data-bags-list/data-bags-list.component.html +++ b/components/automate-ui/src/app/modules/infra-proxy/data-bags-list/data-bags-list.component.html @@ -31,7 +31,7 @@ {{ dataBag.name }} - + Delete diff --git a/components/automate-ui/src/app/modules/infra-proxy/infra-proxy.module.ts b/components/automate-ui/src/app/modules/infra-proxy/infra-proxy.module.ts index 7cad566530b..7dd6b55767f 100644 --- a/components/automate-ui/src/app/modules/infra-proxy/infra-proxy.module.ts +++ b/components/automate-ui/src/app/modules/infra-proxy/infra-proxy.module.ts @@ -9,6 +9,7 @@ import { ChefServerDetailsComponent } from './chef-server-details/chef-server-de import { ChefServersListComponent } from './chef-servers-list/chef-servers-list.component'; import { ClientsComponent } from './clients/clients.component'; import { ClientDetailsComponent } from './client-details/client-details.component'; +import { CreateClientModalComponent } from './create-client-modal/create-client-modal.component'; import { CookbooksComponent } from './cookbooks/cookbooks.component'; import { CookbookDetailsComponent } from './cookbook-details/cookbook-details.component'; import { CreateChefServerModalComponent } from './create-chef-server-modal/create-chef-server-modal.component'; @@ -41,6 +42,7 @@ import { TreeTableModule } from './tree-table/tree-table.module'; CreateChefServerModalComponent, CreateOrgModalComponent, CreateDataBagModalComponent, + CreateClientModalComponent, DataBagsDetailsComponent, DataBagsListComponent, DeleteInfraObjectModalComponent, diff --git a/components/automate-ui/src/app/modules/infra-proxy/infra-roles/infra-roles.component.html b/components/automate-ui/src/app/modules/infra-proxy/infra-roles/infra-roles.component.html index 5773879d611..4aea57b8917 100644 --- a/components/automate-ui/src/app/modules/infra-proxy/infra-roles/infra-roles.component.html +++ b/components/automate-ui/src/app/modules/infra-proxy/infra-roles/infra-roles.component.html @@ -43,7 +43,7 @@ {{ role.environments.join(", ") }} - + Delete diff --git a/components/automate-ui/src/assets/img/icon-check-circle.png b/components/automate-ui/src/assets/img/icon-check-circle.png new file mode 100644 index 00000000000..49d6f5efabb Binary files /dev/null and b/components/automate-ui/src/assets/img/icon-check-circle.png differ