summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/crushmap/crushmap.component.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/crushmap/crushmap.component.ts')
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/crushmap/crushmap.component.ts122
1 files changed, 122 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/crushmap/crushmap.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/crushmap/crushmap.component.ts
new file mode 100644
index 000000000..e3a9ce578
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/crushmap/crushmap.component.ts
@@ -0,0 +1,122 @@
+import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
+
+import {
+ ITreeOptions,
+ TreeComponent,
+ TreeModel,
+ TreeNode,
+ TREE_ACTIONS
+} from '@circlon/angular-tree-component';
+import { Observable, Subscription } from 'rxjs';
+
+import { CrushRuleService } from '~/app/shared/api/crush-rule.service';
+import { Icons } from '~/app/shared/enum/icons.enum';
+import { TimerService } from '~/app/shared/services/timer.service';
+
+@Component({
+ selector: 'cd-crushmap',
+ templateUrl: './crushmap.component.html',
+ styleUrls: ['./crushmap.component.scss']
+})
+export class CrushmapComponent implements OnDestroy, OnInit {
+ private sub = new Subscription();
+
+ @ViewChild('tree') tree: TreeComponent;
+
+ icons = Icons;
+ loadingIndicator = true;
+ nodes: any[] = [];
+ treeOptions: ITreeOptions = {
+ useVirtualScroll: true,
+ nodeHeight: 22,
+ actionMapping: {
+ mouse: {
+ click: this.onNodeSelected.bind(this)
+ }
+ }
+ };
+
+ metadata: any;
+ metadataTitle: string;
+ metadataKeyMap: { [key: number]: any } = {};
+ data$: Observable<object>;
+
+ constructor(private crushRuleService: CrushRuleService, private timerService: TimerService) {}
+
+ ngOnInit() {
+ this.sub = this.timerService
+ .get(() => this.crushRuleService.getInfo(), 5000)
+ .subscribe((data: any) => {
+ this.loadingIndicator = false;
+ this.nodes = this.abstractTreeData(data);
+ });
+ }
+
+ ngOnDestroy() {
+ this.sub.unsubscribe();
+ }
+
+ private abstractTreeData(data: any): any[] {
+ const nodes = data.nodes || [];
+ const rootNodes = data.roots || [];
+ const treeNodeMap: { [key: number]: any } = {};
+
+ if (0 === nodes.length) {
+ return [
+ {
+ name: 'No nodes!'
+ }
+ ];
+ }
+
+ const roots: any[] = [];
+ nodes.reverse().forEach((node: any) => {
+ if (rootNodes.includes(node.id)) {
+ roots.push(node.id);
+ }
+ treeNodeMap[node.id] = this.generateTreeLeaf(node, treeNodeMap);
+ });
+
+ const children = roots.map((id) => {
+ return treeNodeMap[id];
+ });
+
+ return children;
+ }
+
+ private generateTreeLeaf(node: any, treeNodeMap: any) {
+ const cdId = node.id;
+ this.metadataKeyMap[cdId] = node;
+
+ const name: string = node.name + ' (' + node.type + ')';
+ const status: string = node.status;
+
+ const children: any[] = [];
+ const resultNode = { name, status, cdId, type: node.type };
+ if (node.children) {
+ node.children.sort().forEach((childId: any) => {
+ children.push(treeNodeMap[childId]);
+ });
+
+ resultNode['children'] = children;
+ }
+
+ return resultNode;
+ }
+
+ onNodeSelected(tree: TreeModel, node: TreeNode) {
+ TREE_ACTIONS.ACTIVATE(tree, node, true);
+ if (node.data.cdId !== undefined) {
+ const { name, type, status, ...remain } = this.metadataKeyMap[node.data.cdId];
+ this.metadata = remain;
+ this.metadataTitle = name + ' (' + type + ')';
+ } else {
+ delete this.metadata;
+ delete this.metadataTitle;
+ }
+ }
+
+ onUpdateData() {
+ this.tree.treeModel.expandAll();
+ }
+}