Skip to content
Permalink
Browse files
Update UI for Node Ideal State [helix-front] (#2162)
Enable Ideal State updates through the Helix UI.
  • Loading branch information
micahstubbs committed Jun 21, 2022
1 parent b4f8bd5 commit 3ad6c4ee29b850c59221327f4e0384d7331a97dc
Show file tree
Hide file tree
Showing 14 changed files with 225 additions and 93 deletions.
@@ -17,9 +17,25 @@
"main": "client/main.ts",
"tsConfig": "client/tsconfig.app.json",
"polyfills": "client/polyfills.ts",
"assets": ["client/assets", "client/favicon.ico"],
"assets": [
"client/assets",
"client/favicon.ico",
{
"glob": "**/*",
"input": "node_modules/ace-builds/src-min/",
"output": "/"
}
],
"styles": ["client/styles.scss", "client/theme.scss"],
"scripts": [],
"scripts": [
"node_modules/ace-builds/src-min/ace.js",
"node_modules/ace-builds/src-min/theme-eclipse.js",
"node_modules/ace-builds/src-min/theme-chrome.js",
"node_modules/ace-builds/src-min/mode-javascript.js",
"node_modules/ace-builds/src-min/mode-html.js",
"node_modules/ace-builds/src-min/mode-css.js",
"node_modules/ace-builds/src-min/mode-json.js"
],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
@@ -81,9 +97,24 @@
"tsConfig": "client/tsconfig.spec.json",
"polyfills": ["client/polyfills.ts"],
"no-cache": true,
"scripts": [],
"scripts": [
"node_modules/ace-builds/src-min/ace.js",
"node_modules/ace-builds/src-min/theme-eclipse.js",
"node_modules/ace-builds/src-min/mode-javascript.js",
"node_modules/ace-builds/src-min/mode-html.js",
"node_modules/ace-builds/src-min/mode-css.js",
"node_modules/ace-builds/src-min/mode-json.js"
],
"styles": ["client/styles.scss", "client/theme.scss"],
"assets": ["client/assets", "client/favicon.ico"]
"assets": [
"client/assets",
"client/favicon.ico",
{
"glob": "worker-html.js",
"input": "node_modules/ace-builds/src-min/",
"output": "/"
}
]
}
},
"lint": {
File renamed without changes.
@@ -47,6 +47,6 @@
fxLayout="row"
fxLayoutAlign="center center"
>
<span>&copy; 2018 Helix. All rights reserved.</span>
<span>&copy; 2022 Helix. All rights reserved.</span>
</section>
</section>
@@ -79,7 +79,7 @@ export class Resource {
for (const partitionName in externalView.mapFields) {
const partition = new Partition(partitionName);

// in FULL_ATUO mode, externalView is more important
// in FULL_AUTO mode, externalView is more important
// if preferences list exists, fetch instances from it, else whatever
if (
this.rebalanceMode != 'FULL_AUTO' &&
@@ -6,6 +6,7 @@ import * as _ from 'lodash';
import { HelixService } from '../../core/helix.service';
import { Resource } from './resource.model';
import { Cluster } from '../../cluster/shared/cluster.model';
import { Node } from '../../shared/models/node.model';

@Injectable()
export class ResourceService extends HelixService {
@@ -109,4 +110,16 @@ export class ResourceService extends HelixService {
public remove(clusterName: string, resourceName: string) {
return this.delete(`/clusters/${clusterName}/resources/${resourceName}`);
}

public setIdealState(
clusterName: string,
resourceName: string,
idealState: Node
) {
return this.post(
`/clusters/${clusterName}/resources/${resourceName}/idealState?command=update`,
JSON.stringify(idealState)
);
}
}

@@ -88,7 +88,7 @@ export class InputInlineComponent implements ControlValueAccessor, OnInit {
this.lastValue = value;
this.editing = true;
setTimeout((_) => {
this.inputControl.nativeElement.focus();
this.inputControl?.nativeElement.focus();
});
}

@@ -29,10 +29,15 @@
></mat-progress-bar>
<mat-button-toggle-group #group="matButtonToggleGroup" value="table">
<mat-button-toggle value="table"> Table View </mat-button-toggle>
<mat-button-toggle value="tree"> Tree View </mat-button-toggle>
<mat-button-toggle value="json"> JSON View </mat-button-toggle>
</mat-button-toggle-group>
<section class="viewer" [ngSwitch]="group.value" fxFlexFill>
<ngx-json-viewer *ngSwitchCase="'json'" [json]="obj"></ngx-json-viewer>
<ngx-json-viewer *ngSwitchCase="'tree'" [json]="obj"></ngx-json-viewer>
<ace-editor *ngSwitchCase="'json'" [(text)]="objString" mode="json" theme="chrome"
[options]="{useWorker: false}" style="min-height:300px;"
#editor>
</ace-editor>
<section *ngSwitchCase="'table'">
<!-- TODO vxu: use mat-simple-table when it's available -->

@@ -47,9 +52,9 @@
/>
</mat-form-field>
<span fxFlex="1 1 auto"></span>
<!-- *ngIf="unlockable" -->
<button
mat-button
*ngIf="unlockable"
(click)="editable = !editable"
[matTooltip]="
editable
@@ -28,4 +28,8 @@ describe('NodeViewerComponent', () => {
it('should be created', () => {
expect(component).toBeTruthy();
});

it('should render the locked button', () => {
expect(component).toContain('Locked');
});
});
@@ -11,21 +11,41 @@ import { ActivatedRoute } from '@angular/router';
import { MatDialog } from '@angular/material/dialog';

import * as _ from 'lodash';

// import * as ace from 'ace-builds/src-noconflict/ace';
import 'ace-builds';
import 'ace-builds/webpack-resolver';
import 'ace-builds/src-noconflict/mode-json';
import 'ace-builds/src-noconflict/theme-chrome';
import { config } from 'ace-builds';

import { ResourceService } from '../../resource/shared/resource.service';
import { HelperService } from '../../shared/helper.service';
import { Node } from '../models/node.model';
import { Settings } from '../../core/settings';
import { InputDialogComponent } from '../dialog/input-dialog/input-dialog.component';
import { ConfirmDialogComponent } from '../dialog/confirm-dialog/confirm-dialog.component';

config.set(
'basePath',
'https://cdn.jsdelivr.net/npm/ace-builds@1.6.0/src-noconflict/'
);
config.setModuleUrl(
'ace/mode/javascript_worker',
'https://cdn.jsdelivr.net/npm/ace-builds@1.6.0/src-noconflict/worker-json.js'
);
@Component({
selector: 'hi-node-viewer',
templateUrl: './node-viewer.component.html',
styleUrls: ['./node-viewer.component.scss'],
providers: [ResourceService],
// Since we are importing external styles in this component
// we will not use Shadow DOM at all to make sure the styles apply
encapsulation: ViewEncapsulation.None,
})
export class NodeViewerComponent implements OnInit {
isLoading = true;
clusterName: string;
resourceName: string;
@ViewChild('simpleTable', { static: true }) simpleTable;
@ViewChild('listTable', { static: true }) listTable;
@ViewChild('mapTable', { static: true }) mapTable;
@@ -82,14 +102,26 @@ export class NodeViewerComponent implements OnInit {
// MODE 1: use directly in components
@Input()
set obj(value: any) {
if (value != null) {
if (value !== null) {
this._obj = value;
this.node = new Node(value);
}
}
get obj(): any {
return this._obj;
}
set objString(value: string | null) {
let parsedValue = null;
if (value && value !== null) {
parsedValue = JSON.parse(value);
}

this.obj = parsedValue;
this.node = new Node(parsedValue);
}
get objString(): string {
return JSON.stringify(this.obj, null, 2);
}
set editable(value: boolean) {
this._editable = value;
this.columns.simpleConfigs[1].editable = this._editable;
@@ -138,7 +170,12 @@ export class NodeViewerComponent implements OnInit {
: [];
}

constructor(protected dialog: MatDialog, protected route: ActivatedRoute) {}
public constructor(
protected dialog: MatDialog,
protected route: ActivatedRoute,
protected resourceService: ResourceService,
protected helper: HelperService
) {}

ngOnInit() {
// MODE 2: use in router
@@ -152,6 +189,15 @@ export class NodeViewerComponent implements OnInit {
// try self data then
this.obj = _.get(this.route.snapshot.data, path);
}

this.objString = JSON.stringify(this.obj, null, 2);
}

if (this.route.parent) {
this.clusterName =
this.route.parent.snapshot.params.name ||
this.route.parent.snapshot.params.cluster_name;
this.resourceName = this.route.parent.snapshot.params.resource_name;
}
}

@@ -254,29 +300,24 @@ export class NodeViewerComponent implements OnInit {
case 'list':
if (key) {
const entry = _.find(this.node.listFields, { name: key });
// Property 'value' does not exist on type 'number | ListFieldObject | ((searchElement: ListFieldObject, fromIndex?: number) => boolean) | ((...'.
// Property 'value' does not exist on type 'number'.ts(2339)
// @ts-ignore

entry.value.push({
name: '',
value: data.value.value,
});
// Argument of type 'number | ListFieldObject | ((searchElement: ListFieldObject, fromIndex?: number) => boolean) | ((...' is not assignable to parameter of type 'ListFieldObject'.
// Type 'number' is not assignable to type 'ListFieldObject'.ts(2345)
// @ts-ignore

newNode.listFields.push(entry);
}
break;

case 'map':
if (key) {
const entry = _.find(this.node.mapFields, { name: key });
// Property 'value' does not exist on type 'number | MapFieldObject | ((searchElement: MapFieldObject, fromIndex?: number) => boolean) | (() ...'.
// Property 'value' does not exist on type 'number'.ts(2339)
// @ts-ignore

_.forEach(entry.value, (item: any) => {
newNode.appendMapField(key, item.name, item.value);
});

newNode.appendMapField(key, data.name.value, data.value.value);
}
break;
@@ -300,24 +341,14 @@ export class NodeViewerComponent implements OnInit {
case 'list':
if (key) {
const entry = _.find(this.node.listFields, { name: key });
// Property 'value' does not exist on type 'number | ListFieldObject | ((searchElement: ListFieldObject, fromIndex?: number) => boolean) | ((...'.
// Property 'value' does not exist on type 'number'.ts(2339)
// @ts-ignore
const index = _.findIndex(entry.value, { value: row.value });

if (isDeleting) {
// Property 'value' does not exist on type 'number | ListFieldObject | ((searchElement: ListFieldObject, fromIndex?: number) => boolean) | ((...'.
// Property 'value' does not exist on type 'number'.ts(2339)
// @ts-ignore
entry.value.splice(index, 1);
} else {
// Property 'value' does not exist on type 'number | ListFieldObject | ((searchElement: ListFieldObject, fromIndex?: number) => boolean) | ((...'.
// Property 'value' does not exist on type 'number'.ts(2339)
// @ts-ignore
entry.value[index].value = value;
}
// Argument of type 'number | ListFieldObject | ((searchElement: ListFieldObject, fromIndex?: number) => boolean) | ((...' is not assignable to parameter of type 'ListFieldObject'.
// Type 'number' is not assignable to type 'ListFieldObject'.ts(2345)
// @ts-ignore

newNode.listFields.push(entry);
}
break;
@@ -328,9 +359,6 @@ export class NodeViewerComponent implements OnInit {
const entry = _.find(this.node.mapFields, { name: key });
newNode.mapFields = [{ name: key, value: [] }];

// Property 'value' does not exist on type 'number | MapFieldObject | ((searchElement: MapFieldObject, fromIndex?: number) => boolean) | (() ...'.
// Property 'value' does not exist on type 'number'.ts(2339)
// @ts-ignore
_.forEach(entry.value, (item: any) => {
if (item.name === row.name) {
if (!isDeleting) {
@@ -344,6 +372,26 @@ export class NodeViewerComponent implements OnInit {
break;
}

const path = this?.route?.snapshot?.data?.path;
if (path && path === 'idealState') {
const observer = this.resourceService.setIdealState(
this.clusterName,
this.resourceName,
newNode
);

if (observer) {
this.isLoading = true;
observer.subscribe(
() => {
this.helper.showSnackBar('Ideal State updated!');
},
(error) => this.helper.showError(error),
() => (this.isLoading = false)
);
}
}

this.change.emit(newNode);
}
}
@@ -6,6 +6,7 @@ import { FormsModule } from '@angular/forms';

import { NgxDatatableModule } from '@swimlane/ngx-datatable';
import { NgxJsonViewerModule } from 'ngx-json-viewer';
import { AceEditorModule } from 'ng2-ace-editor';

import { MaterialModule } from './material.module';
import { HelperService } from './helper.service';
@@ -33,6 +34,7 @@ import { DisabledLabelComponent } from './disabled-label/disabled-label.componen
FormsModule,
NgxDatatableModule,
NgxJsonViewerModule,
AceEditorModule
],
declarations: [
InputDialogComponent,

0 comments on commit 3ad6c4e

Please sign in to comment.