FEATURE displaying data tree
diff --git a/frontend/config/config.component.css b/frontend/config/config.component.css
index 30ccea5..c2336b0 100644
--- a/frontend/config/config.component.css
+++ b/frontend/config/config.component.css
@@ -13,6 +13,15 @@
padding-right: 0.5em;
}
+.tab-icon {
+ vertical-align: middle;
+ height: 1em;
+}
+
+.tab-add {
+ filter: invert(100%);
+}
+
.tab-add:hover {
color: green;
}
@@ -23,14 +32,14 @@
color: blue;
}
-nav {
+#confignav {
position: fixed;
width: 100%;
background-color: #3B4257;
padding-left: 1em;
}
-nav a {
+#confignav a {
cursor: pointer;
text-decoration: none;
display: inline-block;
@@ -38,16 +47,18 @@
color: #FAFAFA;
}
-nav a:visited, a:link {
+#confignav a:visited,
+#confignav a:link {
color: inherited;
}
-nav a:hover, nav a.active {
+#confignav a:hover,
+#confignav a.active {
background-color: #FAFAFA;
- color: #000
+ color: #000;
}
-nav a.active:hover {
+#confignav a.active:hover {
cursor: default;
}
@@ -67,11 +78,14 @@
}
#config-data {
- padding-left: 11em;
- display: inline-block;
width: 100%;
}
+.item_action_expand,
+.item_action_collapse {
+ height: 1em;
+}
+
.item_action_collapse:hover {
color:red;
}
diff --git a/frontend/config/config.component.html b/frontend/config/config.component.html
index 654b94c..8b398e5 100644
--- a/frontend/config/config.component.html
+++ b/frontend/config/config.component.html
@@ -1,20 +1,34 @@
-<nav #navbar>
- <a *ngFor="let session of sessionsService.sessions" [class.active]="session.key==activeSession.key"
- (click)="changeActiveSession(session.key)">{{session.device.hostname}}:{{session.device.port}}
- <span *ngIf="session.key==activeSession.key" class="tab-reload tab-action-first" (click)="reloadData(session.key)">o</span>
- <span class="tab-close tab-action-last" (click)="disconnect(session.key)">x</span>
- </a><a (click)="addSession()"><span class="tab-add tab-action-first tab-action-last">+</span></a>
+<nav #confignav id="confignav">
+ <a *ngFor="let session of sessionsService.sessions" [class.active]="session.key==activeSession.key"
+ (click)="changeActiveSession(session.key)">{{session.device.hostname}}:{{session.device.port}}
+ <!--<span *ngIf="session.key==activeSession.key" class="tab-reload tab-action-first" (click)="reloadData(session.key)">o</span>-->
+ <img *ngIf="session.key==activeSession.key" class="tab-icon tab-reload tab-action-first" src="assets/netopeer/icons/reload.svg" alt="o" title="reload"
+ onmouseover="this.src='assets/netopeer/icons/reload_active.svg'"
+ onmouseout="this.src='assets/netopeer/icons/reload.svg'" (click)="reloadData()"/>
+ <img class="tab-icon tab-close tab-action-last" src="assets/netopeer/icons/close.svg" alt="x" title="disconnect"
+ onmouseover="this.src='assets/netopeer/icons/close_active.svg'"
+ onmouseout="this.src='assets/netopeer/icons/close.svg'" (click)="disconnect(session.key)"/>
+ </a><a (click)="addSession()" onmouseout="getElementById('tabadd').style.filter='invert(100%)'" onmouseover="getElementById('tabadd').style.filter='invert(0%)'">
+ <img id="tabadd" class="tab-icon tab-add tab-action-last" src="assets/netopeer/icons/add.svg" alt="+" title="add connection"
+ onmouseover="this.src='assets/netopeer/icons/add_active.svg'"
+ onmouseout="this.src='assets/netopeer/icons/add.svg'"/>
+ </a>
</nav>
-<div class="netopeer-content" [style.padding-top]="'calc(' + navbar.offsetHeight + 'px - -0.7em)'">
+<div class="netopeer-content" [style.padding-top]="'calc(' + confignav.offsetHeight + 'px - -0.7em)'">
<div *ngIf="sessionsService.activeSession">
- <p class="msg-failure msg-rounded" *ngIf="err_msg"><span class="msg-close" (click)="err_msg=''">x</span>{{err_msg}}</p>
-
+ <p class="msg-failure msg-rounded" *ngIf="err_msg"><img class="msg-close" (click)="err_msg=''" src="assets/netopeer/icons/close_active.svg" alt="x" title="close"/>{{err_msg}}</p>
<table class="items">
<tr class="item_header">
<th class="item_left item_actions" [ngSwitch]="activeSession.cpbltsVisibility">
- <span *ngSwitchCase="false" class="item_action_expand" (click)="getCapabilities(activeSession.key);activeSession.cpbltsVisibility=true">v</span>
- <span *ngSwitchCase="true" class="item_action_collapse" (click)="activeSession.cpbltsVisibility=false">x</span>
+ <img *ngSwitchCase="false" class="item_action_expand" (click)="getCapabilities(activeSession.key);activeSession.cpbltsVisibility=true"
+ src="assets/netopeer/icons/show.svg" alt="v" title="show"
+ onmouseover="this.src='assets/netopeer/icons/show_active.svg'"
+ onmouseout="this.src='assets/netopeer/icons/show.svg'"/>
+ <img *ngSwitchCase="true" class="item_action_collapse" (click)="activeSession.cpbltsVisibility=false"
+ src="assets/netopeer/icons/close.svg" alt="x" title="close"
+ onmouseover="this.src='assets/netopeer/icons/close_active.svg'"
+ onmouseout="this.src='assets/netopeer/icons/close.svg'"/>
</th>
<th>Capability / Module</th>
<th class="item_right">Version</th>
@@ -30,24 +44,29 @@
<table class="items">
<tr class="item_header">
- <th class="item_left item_actions" [ngSwitch]="activeSession.dataVisibility">
- <span *ngSwitchCase="false" class="item_action_expand" (click)="rpcGet(activeSession.key);activeSession.dataVisibility=true">v</span>
- <span *ngSwitchCase="true" class="item_action_collapse" (click)="activeSession.dataVisibility=false">x</span>
- </th>
- <th class="item_right">Data</th>
+ <th class="item_left item_actions" style="cursor: default;"> </th>
+ <th class="item_right" >Data</th>
</tr>
</table>
- <ng-container *ngIf="activeSession.dataVisibility==true && activeSession.data">
-<!--
- <div id="config-toc">
- <div class="config-toc-item" [ngSwitch]="activeSession.dataVisibility">
- <a *ngSwitchCase="false" (click)="rpcGet(activeSession.key);activeSession.dataVisibility=true">show data</a>
- <a *ngSwitchCase="true" (click)="activeSession.dataVisibility=false">hide data</a>
- </div>
- </div>
--->
+ <div>
+ <img *ngIf="activeSession.dataVisibility!='root'" class="icon_action" (click)="rpcGet(false);activeSession.dataVisibility='root'"
+ src="assets/netopeer/icons/show_children.svg" alt="v" title="expand roots"
+ onmouseover="this.src='assets/netopeer/icons/show_children_active.svg'"
+ onmouseout="this.src='assets/netopeer/icons/show_children.svg'"/>
+ <img *ngIf="activeSession.dataVisibility!='all'" class="icon_action" (click)="rpcGet(true);activeSession.dataVisibility='all'"
+ src="assets/netopeer/icons/show_all.svg" alt="w" title="expand all"
+ onmouseover="this.src='assets/netopeer/icons/show_all_active.svg'"
+ onmouseout="this.src='assets/netopeer/icons/show_all.svg'"/>
+ <img *ngIf="activeSession.dataVisibility!='none'" class="icon_action" (click)="activeSession.dataVisibility='none'"
+ src="assets/netopeer/icons/close.svg" alt="x" title="close"
+ onmouseover="this.src='assets/netopeer/icons/close_active.svg'"
+ onmouseout="this.src='assets/netopeer/icons/close.svg'"/>
+ <span><input type="checkbox" name="statusVisibility" [checked]="activeSession.statusVisibility" (change)="activeSession.statusVisibility = !activeSession.statusVisibility"/> include status data</span>
+ </div><br/>
+ <ng-container *ngIf="activeSession.dataVisibility!='none' && activeSession.data">
<div id="config-data">
- <pre >{{activeSession.data | json}}</pre>
+ <tree-view [treeData]="activeSession.data"></tree-view>
+ <!--<pre>{{activeSession.data | json}}</pre>-->
</div>
</ng-container>
</div>
diff --git a/frontend/config/config.component.ts b/frontend/config/config.component.ts
index 0618771..7adf96d 100644
--- a/frontend/config/config.component.ts
+++ b/frontend/config/config.component.ts
@@ -3,12 +3,11 @@
import {SessionsService} from './sessions.service';
import {Session} from './session';
-import {Device} from '../inventory/device';
@Component({
selector: 'netopeer-config',
templateUrl: './config.component.html',
- styleUrls: ['../netopeer.css', './config.component.css', '../inventory/inventory.component.css']
+ styleUrls: ['../netopeer.css', './config.component.css', './tree.component.css', '../inventory/inventory.component.css']
})
export class ConfigComponent implements OnInit, OnDestroy {
@@ -16,15 +15,20 @@
activeSession: Session;
err_msg = "";
+ objectKeys = Object.keys;
constructor(private sessionsService: SessionsService, private router: Router) {}
addSession() {
this.router.navigateByUrl('/netopeer/inventory/devices');
}
- reloadData(key: string) {
+ reloadData() {
this.activeSession.data = null;
- this.rpcGet(key);
+ if (this.activeSession.dataVisibility == 'all') {
+ this.rpcGet(true);
+ } else if(this.activeSession.dataVisibility == 'root') {
+ this.rpcGet(false);
+ }
}
disconnect(key: string) {
@@ -45,9 +49,10 @@
}
this.sessionsService.getCpblts(key).subscribe(result => {
if (result['success']) {
- this.activeSession.cpblts = result['capabilities']
+ this.activeSession.cpblts = result['capabilities'];
} else {
- this.err_msg = result['error-msg']
+ this.activeSession.cpbltsVisibility = false;
+ this.err_msg = result['error-msg'];
}
});
}
@@ -105,17 +110,23 @@
return version;
}
- rpcGet(key: string) {
+ rpcGet(all: boolean) {
if (this.activeSession.data) {
- return;
+ if ((all && this.activeSession.dataVisibility == 'all') ||
+ (!all && this.activeSession.dataVisibility == 'root')) {
+ return;
+ }
}
- this.sessionsService.rpcGet(key).subscribe(result => {
+ this.sessionsService.rpcGetSubtree(this.activeSession.key, all).subscribe(result => {
if (result['success']) {
- this.activeSession.data = result['data']
- } else if ('error-msg' in result) {
- this.err_msg = result['error-msg']
+ this.activeSession.data = result['data'];
} else {
- this.err_msg = result['error'][0]['message']
+ this.activeSession.dataVisibility = 'none';
+ if ('error-msg' in result) {
+ this.err_msg = result['error-msg'];
+ } else {
+ this.err_msg = result['error'][0]['message'];
+ }
}
});
}
diff --git a/frontend/config/session.ts b/frontend/config/session.ts
index 1ca2c3c..9f56289 100644
--- a/frontend/config/session.ts
+++ b/frontend/config/session.ts
@@ -4,9 +4,10 @@
constructor (
public key: string,
public device: Device,
- public data: string = "",
+ public data = null,
public cpblts: string = "",
- public dataVisibility: boolean = false,
- public cpbltsVisibility: boolean = false
+ public dataVisibility: string = 'none',
+ public statusVisibility: boolean = true,
+ public cpbltsVisibility: boolean = false,
) {}
}
diff --git a/frontend/config/sessions.service.ts b/frontend/config/sessions.service.ts
index a947a4f..83d99f9 100644
--- a/frontend/config/sessions.service.ts
+++ b/frontend/config/sessions.service.ts
@@ -84,9 +84,13 @@
.catch((err: Response | any) => Observable.throw(err));
}
- rpcGet(key: string): Observable<string[]> {
+ rpcGetSubtree(key: string, all: boolean, path: string = ""): Observable<string[]> {
let params = new URLSearchParams();
params.set('key', key);
+ params.set('recursive', String(all));
+ if (path.length) {
+ params.set('path', path);
+ }
let options = new RequestOptions({ search: params });
return this.http.get('/netopeer/session/rpcGet', options)
.map((resp: Response) => resp.json())
@@ -127,7 +131,7 @@
.map((resp: Response) => resp.json())
.do(resp => {
if (resp['success']) {
- this.sessions.push(new Session(resp['session-key'], dev, "", "", false, false));
+ this.sessions.push(new Session(resp['session-key'], dev));
this.activeSession = resp['session-key'];
localStorage.setItem('sessions', JSON.stringify(this.sessions));
localStorage.setItem('activeSession', this.activeSession);
diff --git a/frontend/config/tree.component.css b/frontend/config/tree.component.css
new file mode 100644
index 0000000..9534da8
--- /dev/null
+++ b/frontend/config/tree.component.css
@@ -0,0 +1,46 @@
+.subtree {
+ cursor: default;
+ display: block;
+ width: 100%;
+}
+
+.node {
+ width: 100%;
+}
+.node:hover {
+ background-color: #e1e1e1
+}
+.node div {
+ display: inline-block;
+}
+
+.status {
+ color: grey;
+}
+
+.icon,
+.icon_action {
+ font-size: xx-small;
+ height: 2em;
+}
+
+.icon_action {
+ cursor: pointer;
+}
+
+.module_name {
+ float: right;
+}
+
+.children {
+ padding-left: 0em;
+}
+
+.value {
+ display: inline-block;
+ padding-left: 1.5em;
+}
+
+.indentation {
+ height: 1.7em;
+}
\ No newline at end of file
diff --git a/frontend/config/tree.component.html b/frontend/config/tree.component.html
new file mode 100644
index 0000000..22e7b75
--- /dev/null
+++ b/frontend/config/tree.component.html
@@ -0,0 +1,44 @@
+<ng-container *ngFor="let node of treeData">
+<div class="subtree" *ngIf="node['info']['config'] || activeSession.statusVisibility" [class.status]="!node['info']['config']">
+ <div class="node">
+ <ng-container *ngFor="let indent of indentation">
+ <img *ngIf="!indent" class="indentation" src="assets/netopeer/icons/tree_cont.svg"/>
+ <img *ngIf="indent" class="indentation" src="assets/netopeer/icons/tree_empty.svg"/>
+ </ng-container>
+ <img *ngIf="node['last']" class="indentation" src="assets/netopeer/icons/tree_last_branch.svg"/>
+ <img *ngIf="!node['last']" class="indentation" src="assets/netopeer/icons/tree_branch.svg"/>
+ <ng-container [ngSwitch]="node['info']['type']">
+ <img *ngSwitchCase="1" class="icon" src="assets/netopeer/icons/container.svg" alt="container" title="container"/>
+ <img *ngSwitchCase="4" class="icon" src="assets/netopeer/icons/leaf.svg" alt="leaf" title="leaf"/>
+ <img *ngSwitchCase="8" class="icon" src="assets/netopeer/icons/leaflist.svg" alt="leaf-list" title="leaf-list"/>
+ <img *ngSwitchCase="16" class="icon" src="assets/netopeer/icons/container.svg" alt="list" title="list"/>
+ <img *ngSwitchCase="32800" class="icon" src="assets/netopeer/icons/anydata.svg" alt="anydata" title="anydata"/>
+ </ng-container>
+ <img class="icon" src="assets/netopeer/icons/info.svg"
+ onmouseover="this.src='assets/netopeer/icons/info_active.svg'"
+ onmouseout="this.src='assets/netopeer/icons/info.svg'" alt="info" title="{{node['info']['dsc']}}"/>
+ <ng-container *ngIf="expandable(node)">
+ <img *ngIf="hasHiddenChild(node)" (click)="getSubtree(node, true)"
+ class="icon_action" src="assets/netopeer/icons/show_all.svg"
+ onmouseover="this.src='assets/netopeer/icons/show_all_active.svg'"
+ onmouseout="this.src='assets/netopeer/icons/show_all.svg'" alt="show-all" title="expand subtree"/>
+ <img *ngIf="!node['children']" (click)="getSubtree(node, false)"
+ class="icon_action" src="assets/netopeer/icons/show_children.svg"
+ onmouseover="this.src='assets/netopeer/icons/show_children_active.svg'"
+ onmouseout="this.src='assets/netopeer/icons/show_children.svg'" alt="show-children" title="expand children"/>
+ <img *ngIf="node['children']" (click)="collapse(node)"
+ class="icon_action" src="assets/netopeer/icons/collapse.svg" alt="collapse" title="collapse"
+ onmouseover="this.src='assets/netopeer/icons/collapse_active.svg'"
+ onmouseout="this.src='assets/netopeer/icons/collapse.svg'"/>
+ </ng-container>
+ <div>{{node['info']['name']}}</div>
+ <ng-container *ngIf="node['value']">
+ :<div class="value" >{{node['value']}}</div>
+ </ng-container>
+ <div class="module_name">{{node['info']['module']}}</div>
+ </div>
+ <div class="children" *ngIf="node['children']">
+ <tree-view [treeData]="node['children']" [indentation]="inheritIndentation(node)"></tree-view>
+ </div>
+</div>
+</ng-container>
\ No newline at end of file
diff --git a/frontend/config/tree.component.ts b/frontend/config/tree.component.ts
new file mode 100644
index 0000000..99b6cec
--- /dev/null
+++ b/frontend/config/tree.component.ts
@@ -0,0 +1,92 @@
+import {Component, Input, OnInit} from '@angular/core';
+
+import {Session} from './session';
+import {SessionsService} from './sessions.service';
+
+@Component({
+ selector: 'tree-view',
+ templateUrl: './tree.component.html',
+ styleUrls: ['../netopeer.css', './tree.component.css']
+})
+
+export class TreeView implements OnInit {
+ @Input() treeData;
+ @Input() indentation;
+ c = 1; i = 1;
+ activeSession: Session;
+ objectKeys = Object.keys;
+ constructor(private sessionsService: SessionsService) {}
+
+ ngOnInit(): void {
+ this.activeSession = this.sessionsService.getActiveSession(this.sessionsService.activeSession);
+ }
+
+ getSubtree(node, all: boolean) {
+ this.sessionsService.rpcGetSubtree(this.activeSession.key, all, node['path']).subscribe(result => {
+ if (result['success']) {
+ node['children'] = result['data']['children'];
+ }
+ });
+ }
+
+ getType(object) {
+ let result = 'data';
+ if (typeof object == 'object') {
+ if (object instanceof Array) {
+ result = 'array';
+ } else {
+ result = 'object';
+ }
+ }
+ return result;
+ }
+
+ expandable(node): boolean {
+ if (node['info']['type'] == 1 || /* container */
+ node['info']['type'] == 16) { /* list */
+ return true;
+ }
+ return false;
+ }
+
+ hasHiddenChild(node, clean=false): boolean {
+ if (!clean && 'hasHiddenChild' in node) {
+ return node['hasHiddenChild'];
+ }
+ node['hasHiddenChild'] = false;
+ if (!node['children']) {
+ node['hasHiddenChild'] = true;
+ } else {
+ for (let child of node['children']) {
+ if (!('children' in child) || this.hasHiddenChild(child, clean)) {
+ node['hasHiddenChild'] = true;
+ break;
+ }
+ }
+ }
+ return node['hasHiddenChild'];
+ }
+
+ inheritIndentation(node) {
+ let newIndent;
+ if (node['last']) {
+ newIndent = [true];
+ } else {
+ newIndent = [false];
+ }
+
+ if (!this.indentation) {
+ return newIndent;
+ } else {
+ return this.indentation.concat(newIndent);
+ }
+ }
+
+ collapse(node) {
+ node['children'] = null;
+ this.activeSession.dataVisibility = 'mixed';
+ for (let iter of this.activeSession.data) {
+ this.hasHiddenChild(iter, true);
+ }
+ }
+}