Skip to content
This repository was archived by the owner on Mar 25, 2023. It is now read-only.

feat(settings): Move "keyboard layout" from VM creation to Settings #1220

Merged
merged 13 commits into from
Sep 24, 2018
3 changes: 2 additions & 1 deletion src/app/core/config/default-configuration.ts
Original file line number Diff line number Diff line change
@@ -132,7 +132,8 @@ export const nonCustomizableProperties: Readonly<NonCustomizableConfig> = {
min: 512,
max: Number.POSITIVE_INFINITY
}
}
},
keyboardLayoutForVms: 'us'
};

export const defaultConfig: Readonly<Config> = {...customizableProperties, ...nonCustomizableProperties};
Empty file.
120 changes: 64 additions & 56 deletions src/app/reducers/vm/redux/vm-creation.effects.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material';
import { Action, Store } from '@ngrx/store';
import { Actions, Effect } from '@ngrx/effects';
import { Actions, Effect, ofType } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';

import { Utils } from '../../../shared/services/utils/utils.service';
import { DialogService, ParametrizedTranslation } from '../../../dialog/dialog-service/dialog.service';
@@ -33,8 +34,10 @@ import { VmCreationSecurityGroupMode } from '../../../vm/vm-creation/security-gr
import { SecurityGroup } from '../../../security-group/sg.model';
import { VirtualMachine, VmState } from '../../../vm/shared/vm.model';
import { SnackBarService } from '../../../core/services';
import { UserTagsActions, UserTagsSelectors, configSelectors } from '../../../root-store';
import { map, catchError, switchMap, withLatestFrom } from 'rxjs/operators';
import { DefaultComputeOffering } from '../../../shared/models/config';

import { configSelectors, UserTagsActions } from '../../../root-store';
import * as fromZones from '../../zones/redux/zones.reducers';
import * as vmActions from './vm.actions';
import * as securityGroupActions from '../../security-groups/redux/sg.actions';
@@ -43,7 +46,6 @@ import * as fromSecurityGroups from '../../security-groups/redux/sg.reducers';
import * as fromTemplates from '../../templates/redux/template.reducers';
import * as fromVMs from './vm.reducers';
import * as fromVMModule from '../../../vm/selectors';
import { DefaultComputeOffering } from '../../../shared/models/config';

interface VmCreationParams {
affinityGroupNames?: string;
@@ -247,58 +249,64 @@ export class VirtualMachineCreationEffects {

@Effect()
deploying$ = this.actions$
.ofType(vmActions.VM_DEPLOYMENT_REQUEST)
.switchMap((action: vmActions.DeploymentRequest) => {
.pipe(
ofType(vmActions.VM_DEPLOYMENT_REQUEST),
withLatestFrom(this.store.select(UserTagsSelectors.getKeyboardLayout)),
switchMap(([action, keyboard]: [vmActions.DeploymentRequest, string]) => {
return this.doCreateAffinityGroup(action.payload)
.switchMap(() => this.doCreateSecurityGroup(action.payload)
.switchMap((securityGroups) => {
if (action.payload.securityGroupData.mode === VmCreationSecurityGroupMode.Builder) {
this.store.dispatch(new securityGroupActions.CreateSecurityGroupsSuccess(securityGroups));
}
this.store.dispatch(new vmActions.DeploymentChangeStatus({
stage: VmDeploymentStage.SG_GROUP_CREATION_FINISHED
}));

this.handleDeploymentMessages({ stage: VmDeploymentStage.VM_CREATION_IN_PROGRESS });
const params = this.getVmCreationParams(action.payload, securityGroups);
let deployResponse;

return this.vmService.deploy(params)
.switchMap(response => {
deployResponse = response;
return this.vmService.get(deployResponse.id);
})
.switchMap(vm => {
const temporaryVm = vm;

if (action.payload.instanceGroup && action.payload.instanceGroup.name) {
temporaryVm.instanceGroup = action.payload.instanceGroup;
}

temporaryVm.state = VmState.Deploying;
this.handleDeploymentMessages({ stage: VmDeploymentStage.TEMP_VM });

this.store.dispatch(new UserTagsActions.IncrementLastVMId());
return this.vmService.registerVmJob(deployResponse);
})
.switchMap((deployedVm: VirtualMachine) => {
this.handleDeploymentMessages({ stage: VmDeploymentStage.VM_DEPLOYED });

return this.doCreateInstanceGroup(deployedVm, action.payload)
.switchMap((virtualMachine) => this.doCopyTags(virtualMachine, action.payload));
})
.map((vmWithTags) => {
if (action.payload.doStartVm) {
vmWithTags.state = VmState.Running;
}
return new vmActions.DeploymentRequestSuccess(vmWithTags);
})
.catch((error) => Observable.of(new vmActions.DeploymentRequestError(error)));
})
.catch((error) => Observable.of(new vmActions.DeploymentRequestError(error))));
});

@Effect({ dispatch: false })
.pipe(
switchMap(() => this.doCreateSecurityGroup(action.payload)
.pipe(
switchMap((securityGroups) => {
if (action.payload.securityGroupData.mode === VmCreationSecurityGroupMode.Builder) {
this.store.dispatch(new securityGroupActions.CreateSecurityGroupsSuccess(securityGroups));
}
this.store.dispatch(new vmActions.DeploymentChangeStatus({
stage: VmDeploymentStage.SG_GROUP_CREATION_FINISHED
}));

this.handleDeploymentMessages({stage: VmDeploymentStage.VM_CREATION_IN_PROGRESS});
const params = this.getVmCreationParams(action.payload, keyboard, securityGroups);
let deployResponse;

return this.vmService.deploy(params)
.pipe(
switchMap(response => {
deployResponse = response;
return this.vmService.get(deployResponse.id);
}),
switchMap(vm => {
const temporaryVm = vm;

if (action.payload.instanceGroup && action.payload.instanceGroup.name) {
temporaryVm.instanceGroup = action.payload.instanceGroup;
}

temporaryVm.state = VmState.Deploying;
this.handleDeploymentMessages({stage: VmDeploymentStage.TEMP_VM});

this.store.dispatch(new UserTagsActions.IncrementLastVMId());
return this.vmService.registerVmJob(deployResponse);
}),
switchMap((deployedVm: VirtualMachine) => {
this.handleDeploymentMessages({stage: VmDeploymentStage.VM_DEPLOYED});

return this.doCreateInstanceGroup(deployedVm, action.payload).pipe(
switchMap((virtualMachine) => this.doCopyTags(virtualMachine, action.payload))
)
}),
map((vmWithTags) => {
if (action.payload.doStartVm) {
vmWithTags.state = VmState.Running;
}
return new vmActions.DeploymentRequestSuccess(vmWithTags);
}),
catchError((error) => of(new vmActions.DeploymentRequestError(error))));
}),
catchError((error) => of(new vmActions.DeploymentRequestError(error))))));
}));

@Effect({dispatch: false})
changeStatusOfDeployment$ = this.actions$
.ofType(vmActions.VM_DEPLOYMENT_CHANGE_STATUS)
.do((action: vmActions.DeploymentChangeStatus) => {
@@ -610,15 +618,15 @@ export class VirtualMachineCreationEffects {
});
}

private getVmCreationParams(state: VmCreationState, securityGroups?: SecurityGroup[]) {
private getVmCreationParams(state: VmCreationState, keyboard: string, securityGroups?: SecurityGroup[]) {
const params: VmCreationParams = {};

if (state.affinityGroup) {
params.affinityGroupNames = state.affinityGroup.name;
}

params.startVm = state.doStartVm.toString();
params.keyboard = state.keyboard;
params.keyboard = keyboard;
params.name = state.displayName;
params.serviceOfferingId = state.serviceOffering.id;
params.templateId = state.template.id;
2 changes: 0 additions & 2 deletions src/app/reducers/vm/redux/vm.reducers.ts
Original file line number Diff line number Diff line change
@@ -9,7 +9,6 @@ import { VmCreationSecurityGroupData } from '../../../vm/vm-creation/security-gr
import { Rules } from '../../../shared/components/security-group-builder/rules';
import { Utils } from '../../../shared/services/utils/utils.service';
import { VmCreationState } from '../../../vm/vm-creation/data/vm-creation-state';
import { KeyboardLayout } from '../../../vm/vm-creation/keyboards/keyboards.component';
// tslint:disable-next-line
import {
ProgressLoggerMessage,
@@ -372,7 +371,6 @@ export const initialFormState: FormState = {
displayName: '',
doStartVm: true,
instanceGroup: null,
keyboard: KeyboardLayout.us,
rootDiskSize: null,
rootDiskMinSize: 0,
securityGroupData: VmCreationSecurityGroupData.fromRules(new Rules()),
3 changes: 2 additions & 1 deletion src/app/root-store/config/config.selectors.ts
Original file line number Diff line number Diff line change
@@ -35,7 +35,8 @@ export const getDefaultUserTags = createSelector(
{ key: userTagKeys.showSystemTags, value: `${config.showSystemTags}` },
{ key: userTagKeys.timeFormat, value: config.defaultTimeFormat },
{ key: userTagKeys.theme, value: config.defaultThemeName },
{ key: userTagKeys.navigationOrder, value: config.navigationOrder }
{ key: userTagKeys.navigationOrder, value: config.navigationOrder },
{ key: userTagKeys.keyboardLayoutForVms, value: config.keyboardLayoutForVms }
] : [];
}
);
30 changes: 30 additions & 0 deletions src/app/root-store/server-data/user-tags/user-tags.actions.ts
Original file line number Diff line number Diff line change
@@ -56,6 +56,10 @@ export enum UserTagsActionTypes {
UpdateNavigationOrderSuccess = '[Resource tags API] Update "csui.user.navigation-order" tag success',
UpdateNavigationOrderError = '[Resource tags API] Update "csui.user.navigation-order" tag error',

UpdateKeyboardLayoutForVms = '[Settings Page] Update "csui.user.vm-keyboard-layout" tag',
UpdateKeyboardLayoutForVmsSuccess = '[Resource tags API] Update "csui.user.vm-keyboard-layout" tag success',
UpdateKeyboardLayoutForVmsError = '[Resource tags API] Update "csui.user.vm-keyboard-layout" tag error',

SetSPFAVM = '[Dialog] Set "csui.user.save-password-for-all-vms" tag',
SetSPFAVMSuccess = '[Resource tags API] Set "csui.user.save-password-for-all-vms" tag success',
SetSPFAVMError = '[Resource tags API] Set "csui.user.save-password-for-all-vms" tag error',
@@ -359,6 +363,29 @@ export class UpdateNavigationOrderError implements Action {
}
}

// Keyboard

export class UpdateKeyboardLayoutForVms implements Action {
readonly type = UserTagsActionTypes.UpdateKeyboardLayoutForVms;

constructor(readonly payload: { value: string }) {
}
}

export class UpdateKeyboardLayoutForVmsSuccess implements Action {
readonly type = UserTagsActionTypes.UpdateKeyboardLayoutForVmsSuccess;

constructor(readonly payload: { key: string, value: string }) {
}
}

export class UpdateKeyboardLayoutForVmsError implements Action {
readonly type = UserTagsActionTypes.UpdateKeyboardLayoutForVmsError;

constructor(readonly payload: { error: Error }) {
}
}

// Save password for all VMs

export class SetSavePasswordForAllVMs implements Action {
@@ -458,6 +485,9 @@ export type UserTagsActionsUnion =
| UpdateNavigationOrder
| UpdateNavigationOrderSuccess
| UpdateNavigationOrderError
| UpdateKeyboardLayoutForVms
| UpdateKeyboardLayoutForVmsSuccess
| UpdateKeyboardLayoutForVmsError
| SetSavePasswordForAllVMs
| SetSavePasswordForAllVMsSuccess
| SetSavePasswordForAllVMsError
16 changes: 16 additions & 0 deletions src/app/root-store/server-data/user-tags/user-tags.effects.ts
Original file line number Diff line number Diff line change
@@ -30,6 +30,9 @@ import {
UpdateInterfaceLanguage,
UpdateInterfaceLanguageError,
UpdateInterfaceLanguageSuccess,
UpdateKeyboardLayoutForVms,
UpdateKeyboardLayoutForVmsError,
UpdateKeyboardLayoutForVmsSuccess,
UpdateLastVMId,
UpdateLastVMIdError,
UpdateLastVMIdSuccess,
@@ -257,6 +260,19 @@ export class UserTagsEffects {
})
);

@Effect()
UpdateKeyboardLayoutForVms$: Observable<Action> = this.actions$.pipe(
ofType<UpdateKeyboardLayoutForVms>(UserTagsActionTypes.UpdateKeyboardLayoutForVms),
map(action => action.payload.value),
mergeMap((value: string) => {
const key = userTagKeys.keyboardLayoutForVms;
return this.upsertTag(key, value).pipe(
map(() => new UpdateKeyboardLayoutForVmsSuccess({ key, value })),
catchError((error) => of(new UpdateKeyboardLayoutForVmsError({ error })))
)
})
);

// We omit the result of setting the value on the server, because we have already changed the value in the store
// This is required so that the UI reacts instantly and does not wait until an answer comes from the server.
// Downsides: if the tag is not set, the user selected state will not be saved
Original file line number Diff line number Diff line change
@@ -63,6 +63,7 @@ export function reducer(state = initialState, action: UserTagsActionsUnion): Use
case UserTagsActionTypes.UpdateThemeSuccess:
case UserTagsActionTypes.UpdateNavigationOrderSuccess:
case UserTagsActionTypes.SetSPFAVMSuccess:
case UserTagsActionTypes.UpdateKeyboardLayoutForVmsSuccess:
case UserTagsActionTypes.IncrementLastVMIdSuccess: {
const update: Update<Tag> = { id: action.payload.key, changes: action.payload };
return adapter.updateOne(update, state);
Original file line number Diff line number Diff line change
@@ -113,3 +113,8 @@ export const getServiceOfferingParamTags = createSelector(
return tags.filter(tag => tag.key.startsWith(userTagKeys.computeOfferingParam));
}
);

export const getKeyboardLayout = createSelector(
getUserTagsEntities,
(entities): string => entities[userTagKeys.keyboardLayoutForVms].value
);
4 changes: 4 additions & 0 deletions src/app/root-store/server-data/user-tags/user-tags.state.ts
Original file line number Diff line number Diff line change
@@ -76,6 +76,10 @@ const initialEntities = {
[userTagKeys.navigationOrder]: {
key: userTagKeys.navigationOrder,
value: defaultConfig.navigationOrder
},
[userTagKeys.keyboardLayoutForVms]: {
key: userTagKeys.keyboardLayoutForVms,
value: defaultConfig.keyboardLayoutForVms
}
};

1 change: 1 addition & 0 deletions src/app/settings/components/index.ts
Original file line number Diff line number Diff line change
@@ -4,3 +4,4 @@ export * from './interface-settings/interface-settings.component';
export * from './theme-selector/theme-selector.component';
export * from './password-update-form/password-update-form.component';
export * from './session-timeout/session-timeout.component';
export * from './vm-settings/vm-settings.component';
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<h3 class="no-margin">{{ 'SETTINGS.SECURITY.CHANGE_PASSWORD' | translate }}</h3>
<form [formGroup]="passwordForm" (ngSubmit)="onPasswordUpdate()">
<form [formGroup]="passwordForm" (ngSubmit)="onPasswordChange()">

<mat-form-field>
<input
Original file line number Diff line number Diff line change
@@ -39,17 +39,17 @@ export class PasswordUpdateFormComponent {
// We need manually reset FormGroupDirective via resetForm() method otherwise,
// the form will be invalid and errors are shown
@ViewChild(FormGroupDirective) myForm;
@Output() updatePassword = new EventEmitter<string>();
@Output() passwordChange = new EventEmitter<string>();
public passwordForm: FormGroup;
public errorStateMatcher = new PasswordErrorStateMatcher();

constructor(private fb: FormBuilder) {
this.createForm();
}

public onPasswordUpdate() {
public onPasswordChange() {
const password = this.passwordForm.get('password').value;
this.updatePassword.emit(password);
this.passwordChange.emit(password);
this.myForm.resetForm();
}

Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ <h2 class="settings-section-name">{{ 'SETTINGS.SECURITY.TITLE' | translate }}</h
<div class="settings-section-content">

<cs-password-update-form
(updatePassword)="updatePassword.emit($event)"
(updatePassword)="passwordChange.emit($event)"
></cs-password-update-form>

<cs-session-timeout
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ import { SettingsViewModel } from '../../view-models';

export class SecuritySettingsComponent {
@Input() settings: SettingsViewModel;
@Output() updatePassword = new EventEmitter<string>();
@Output() passwordChange = new EventEmitter<string>();
@Output() sessionTimeoutChange = new EventEmitter<number>();
@Output() isSavePasswordForVMsChange = new EventEmitter<boolean>();
}
15 changes: 15 additions & 0 deletions src/app/settings/components/vm-settings/vm-settings.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<section>
<h2 class="settings-section-name">{{ 'SETTINGS.VM_PREFERENCES.TITLE' | translate }}</h2>
<div class="settings-section-content">
<div class="settings-control">
<h3 class="no-margin">{{ 'SETTINGS.VM_PREFERENCES.KEYBOARD_LAYOUT' | translate }}</h3>
<div>
<cs-keyboards
name="keyboard"
[keyboardLayout]="settings.keyboardLayout"
(keyboardChange)="keyboardLayoutChange.emit($event)"
></cs-keyboards>
</div>
</div>
</div>
</section>
14 changes: 14 additions & 0 deletions src/app/settings/components/vm-settings/vm-settings.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { SettingsViewModel } from '../../view-models';

@Component({
selector: 'cs-vm-settings',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './vm-settings.component.html',
styleUrls: ['../../styles/settings-section.scss']
})

export class VmSettingsComponent {
@Input() settings: SettingsViewModel;
@Output() keyboardLayoutChange = new EventEmitter<string>();
}
Loading