You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@helix.apache.org by jx...@apache.org on 2018/08/18 00:20:01 UTC

[01/14] helix git commit: Workflow: job dag for generic workflows

Repository: helix
Updated Branches:
  refs/heads/master 48e56fa76 -> f1c503712


Workflow: job dag for generic workflows


Project: http://git-wip-us.apache.org/repos/asf/helix/repo
Commit: http://git-wip-us.apache.org/repos/asf/helix/commit/b486d784
Tree: http://git-wip-us.apache.org/repos/asf/helix/tree/b486d784
Diff: http://git-wip-us.apache.org/repos/asf/helix/diff/b486d784

Branch: refs/heads/master
Commit: b486d7849a017fe4a5c1df6fcc7ebb18a6c646d1
Parents: 48e56fa
Author: Vivo Xu <vx...@linkedin.com>
Authored: Thu Feb 1 18:13:46 2018 -0800
Committer: Vivo Xu <vx...@linkedin.com>
Committed: Wed Aug 8 15:06:37 2018 -0700

----------------------------------------------------------------------
 .../app/workflow/shared/workflow.model.ts       |  18 +--
 .../workflow-dag/workflow-dag.component.html    |  53 +++++++++
 .../workflow-dag/workflow-dag.component.scss    |  13 +++
 .../workflow-dag/workflow-dag.component.spec.ts |  33 ++++++
 .../workflow-dag/workflow-dag.component.ts      |  63 ++++++++++
 .../workflow-detail.component.html              | 115 +++++++++----------
 .../workflow-detail.component.ts                |   7 +-
 .../client/app/workflow/workflow.module.ts      |  10 +-
 helix-front/package.json                        |   3 +
 9 files changed, 245 insertions(+), 70 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/helix/blob/b486d784/helix-front/client/app/workflow/shared/workflow.model.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/shared/workflow.model.ts b/helix-front/client/app/workflow/shared/workflow.model.ts
index 096af32..363c8a7 100644
--- a/helix-front/client/app/workflow/shared/workflow.model.ts
+++ b/helix-front/client/app/workflow/shared/workflow.model.ts
@@ -9,18 +9,22 @@ export class Job {
   readonly rawName: string;
   readonly startTime: string;
   readonly state: string;
+  readonly parents: string[];
 
   constructor(
     rawName: string,
     workflowName: string,
     startTime: string,
-    state: string
+    state: string,
+    parents: string[]
   ) {
     this.rawName = rawName;
     // try to reduce the name
     this.name = _.replace(rawName, workflowName + '_', '');
     this.startTime = startTime;
     this.state = state;
+    // try to reduce parent names
+    this.parents = _.map(parents, parent => _.replace(parent, workflowName + '_', ''));
   }
 }
 
@@ -28,9 +32,8 @@ export class Workflow {
   readonly name: string;
   readonly config: any;
   readonly jobs: Job[];
-  // TODO vxu: will use a better structure for this
-  readonly parentJobs: any[];
   readonly context: any;
+  readonly json: any;
 
   get isJobQueue(): boolean {
     return this.config && this.config.IsJobQueue.toLowerCase() == 'true';
@@ -41,14 +44,14 @@ export class Workflow {
   }
 
   constructor(obj: any) {
+    this.json = obj;
     this.name = obj.id;
     this.config = obj.WorkflowConfig;
     this.context = obj.WorkflowContext;
-    this.jobs = this.parseJobs(obj.Jobs);
-    this.parentJobs = obj.ParentJobs;
+    this.jobs = this.parseJobs(obj.Jobs, obj.ParentJobs);
   }
 
-  protected parseJobs(list: string[]): Job[] {
+  protected parseJobs(list: string[], parents: any): Job[] {
     let result: Job[] = [];
 
     _.forEach(list, jobName => {
@@ -56,7 +59,8 @@ export class Workflow {
         jobName,
         this.name,
         _.get(this.context, ['StartTime', jobName]),
-        _.get(this.context, ['JOB_STATES', jobName])
+        _.get(this.context, ['JOB_STATES', jobName]),
+        parents[jobName]
       ));
     });
 

http://git-wip-us.apache.org/repos/asf/helix/blob/b486d784/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.html
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.html b/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.html
new file mode 100644
index 0000000..c44d369
--- /dev/null
+++ b/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.html
@@ -0,0 +1,53 @@
+<ngx-graph
+  #graph
+  class="chart-container"
+  [view]="[graph.graphDims.width * 2 / 3, graph.graphDims.height]"
+  [links]="data.links"
+  [nodes]="data.nodes"
+  [curve]="curve"
+  orientation="TB"
+  (select)="select($event)"
+  [autoZoom]="false"
+  [panningEnabled]="false"
+  [draggingEnabled]="false"
+  [minZoomLevel]="1"
+  [maxZoomLevel]="1">
+
+  <ng-template #defsTemplate>
+    <svg:marker id="arrow" viewBox="0 -5 10 10" refX="8" refY="0" markerWidth="4" markerHeight="4" orient="auto">
+      <svg:path d="M0,-5L10,0L0,5" class="arrow-head" />
+    </svg:marker>
+  </ng-template>
+
+  <ng-template #nodeTemplate let-node>
+    <svg:g class="node"
+      ngx-tooltip
+      [tooltipPlacement]="'top'"
+      [tooltipType]="'tooltip'"
+      [tooltipTitle]="node.description">
+      <svg:rect [attr.width]="node.width" [attr.height]="node.height" [attr.fill]="node.options.color" />
+      <svg:text alignment-baseline="central" [attr.x]="10" [attr.y]="node.height / 2">{{ node.label }}</svg:text>
+      <svg:text alignment-baseline="hanging" [attr.x]="node.width - 100" [attr.y]="node.height + 10" [ngClass]="'state-default state-' + node.state">{{ node.state }}</svg:text>
+    </svg:g>
+  </ng-template>
+
+  <ng-template #linkTemplate let-link>
+    <svg:g class="edge">
+      <svg:path
+        class="line"
+        stroke-width="2"
+        marker-end="url(#arrow)" >
+      </svg:path>
+      <svg:text class="edge-label" text-anchor="middle">
+        <textPath
+          class="text-path"
+          [attr.href]="'#' + link.id"
+          [style.dominant-baseline]="link.dominantBaseline"
+          startOffset="50%">
+          {{link.label}}
+        </textPath>
+      </svg:text>
+    </svg:g>
+  </ng-template>
+
+</ngx-graph>

http://git-wip-us.apache.org/repos/asf/helix/blob/b486d784/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.scss
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.scss b/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.scss
new file mode 100644
index 0000000..87fd120
--- /dev/null
+++ b/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.scss
@@ -0,0 +1,13 @@
+@import '~@angular/material/theming';
+
+.state-default {
+  fill: mat-color(mat-palette($mat-deep-orange));
+}
+
+.state-COMPLETED {
+  fill: mat-color(mat-palette($mat-green));
+}
+
+.state-PENDING {
+  fill: mat-color(mat-palette($mat-grey));
+}

http://git-wip-us.apache.org/repos/asf/helix/blob/b486d784/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.spec.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.spec.ts b/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.spec.ts
new file mode 100644
index 0000000..40b600c
--- /dev/null
+++ b/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.spec.ts
@@ -0,0 +1,33 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+
+import { TestingModule } from '../../../testing/testing.module';
+import { WorkflowDagComponent } from './workflow-dag.component';
+
+describe('WorkflowDagComponent', () => {
+  let component: WorkflowDagComponent;
+  let fixture: ComponentFixture<WorkflowDagComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [ TestingModule ],
+      declarations: [ WorkflowDagComponent ],
+      schemas: [
+        /* avoid importing modules */
+        NO_ERRORS_SCHEMA
+      ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(WorkflowDagComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  xit('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

http://git-wip-us.apache.org/repos/asf/helix/blob/b486d784/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.ts b/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.ts
new file mode 100644
index 0000000..e94d913
--- /dev/null
+++ b/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.ts
@@ -0,0 +1,63 @@
+import { Component, OnInit, Input, ElementRef, AfterViewInit, ViewChild } from '@angular/core';
+
+import * as shape from 'd3-shape';
+import * as _ from 'lodash';
+
+import { Workflow, Job } from '../shared/workflow.model';
+
+@Component({
+  selector: 'hi-workflow-dag',
+  templateUrl: './workflow-dag.component.html',
+  styleUrls: ['./workflow-dag.component.scss']
+})
+export class WorkflowDagComponent implements OnInit, AfterViewInit {
+
+  @Input()
+  workflow: Workflow;
+  curve: any = shape.curveLinear;
+  view = [800, 600];
+  data = {
+    nodes: [],
+    links: []
+  };
+  jobNameToId = {};
+
+  @ViewChild('graph')
+  graph;
+
+  constructor(protected el:ElementRef) { }
+
+  ngOnInit() {
+    this.loadJobs();
+  }
+
+  ngAfterViewInit() {
+    // console.log(this.el);
+    // console.log(this.graph);
+  }
+
+  protected loadJobs() {
+    // process nodes
+    _.forEach(this.workflow.jobs, (job: Job) => {
+      const newId = (this.data.nodes.length + 1).toString();
+
+      this.data.nodes.push({
+        id: newId,
+        label: job.name,
+        description: job.rawName,
+        state: job.state
+      });
+      this.jobNameToId[job.name] = newId;
+    });
+
+    // process edges/links
+    _.forEach(this.workflow.jobs, (job: Job) => {
+      _.forEach(job.parents, parentName => {
+        this.data.links.push({
+          source: this.jobNameToId[parentName],
+          target: this.jobNameToId[job.name]
+        });
+      });
+    });
+  }
+}

http://git-wip-us.apache.org/repos/asf/helix/blob/b486d784/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.html
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.html b/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.html
index 0791995..5a940d3 100644
--- a/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.html
+++ b/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.html
@@ -18,69 +18,68 @@
 <section fxLayout="column" fxLayoutAlign="center center">
   <mat-spinner *ngIf="isLoading"></mat-spinner>
   <section *ngIf="!isLoading" class="content" fxLayout="column" fxLayoutAlign="center center" fxLayoutGap="10px" fxFlexFill>
-    <mat-button-toggle-group #group="matButtonToggleGroup" value="queue">
-      <mat-button-toggle value="queue">
-        Queue View
+    <mat-button-toggle-group #group="matButtonToggleGroup" [value]="workflow.isJobQueue ? 'list' : 'graph'">
+      <mat-button-toggle *ngIf="!workflow.isJobQueue" value="graph">
+        Graph View
+      </mat-button-toggle>
+      <mat-button-toggle value="list">
+        List 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>
-      <section *ngSwitchCase="'queue'">
-        <section *ngIf="workflow.isJobQueue">
-          <ngx-datatable
-            #jobsTable
-            class="material"
-            [headerHeight]="headerHeight"
-            [rowHeight]="rowHeight"
-            columnMode="force"
-            [footerHeight]="rowHeight"
-            [rows]="workflow.jobs"
-            selectionType="single"
-            [sorts]="sorts"
-            (select)="onSelect($event)"
-            [messages]="messages">
-            <ngx-datatable-column
-              name="Start Time"
-              [width]="200"
-              [resizeable]="false"
-              [draggable]="false"
-              [canAutoResize]="false">
-              <ng-template let-value="value" ngx-datatable-cell-template>
-                <span *ngIf="value" [matTooltip]="value | date:'medium'">
-                  {{ parseTime(value) }}
-                </span>
-                <span *ngIf="!value">-</span>
-              </ng-template>
-            </ngx-datatable-column>
-            <ngx-datatable-column name="Job Name" prop="name">
-              <ng-template let-row="row" let-value="value" ngx-datatable-cell-template>
-                <span [matTooltip]="row.rawName">
-                  ...{{ value }}
-                </span>
-              </ng-template>
-            </ngx-datatable-column>
-            <ngx-datatable-column
-              name="State"
-              [width]="120"
-              [resizeable]="false"
-              [draggable]="false"
-              [canAutoResize]="false">
-              <ng-template let-value="value" ngx-datatable-cell-template>
-                <span *ngIf="value" class="state-default state-{{ value }}">
-                  {{ value }}
-                </span>
-                <span *ngIf="!value" class="state-PENDING">PENDING</span>
-              </ng-template>
-            </ngx-datatable-column>
-          </ngx-datatable>
-        </section>
-        <section *ngIf="!workflow.isJobQueue">
-          {{ workflow | json }}
-        </section>
-      </section>
-      <ngx-json-viewer *ngSwitchCase="'json'" [json]="workflow"></ngx-json-viewer>
+    <section class="viewer" [ngSwitch]="group.value" fxLayout="column" fxLayoutAlign="center center" fxFill>
+      <hi-workflow-dag *ngSwitchCase="'graph'" [workflow]="workflow"></hi-workflow-dag>
+      <ngx-datatable
+        *ngSwitchCase="'list'"
+        #jobsTable
+        class="material"
+        [headerHeight]="headerHeight"
+        [rowHeight]="rowHeight"
+        columnMode="force"
+        [footerHeight]="rowHeight"
+        [rows]="workflow.jobs"
+        selectionType="single"
+        [sorts]="sorts"
+        (select)="onSelect($event)"
+        [messages]="messages"
+        fxFill>
+        <ngx-datatable-column
+          name="Start Time"
+          [width]="200"
+          [resizeable]="false"
+          [draggable]="false"
+          [canAutoResize]="false">
+          <ng-template let-value="value" ngx-datatable-cell-template>
+            <span *ngIf="value" [matTooltip]="value | date:'medium'">
+              {{ parseTime(value) }}
+            </span>
+            <span *ngIf="!value">-</span>
+          </ng-template>
+        </ngx-datatable-column>
+        <ngx-datatable-column name="Job Name" prop="name">
+          <ng-template let-row="row" let-value="value" ngx-datatable-cell-template>
+            <span [matTooltip]="row.rawName">
+              ...{{ value }}
+            </span>
+          </ng-template>
+        </ngx-datatable-column>
+        <ngx-datatable-column
+          name="State"
+          [width]="120"
+          [resizeable]="false"
+          [draggable]="false"
+          [canAutoResize]="false">
+          <ng-template let-value="value" ngx-datatable-cell-template>
+            <span *ngIf="value" class="state-default state-{{ value }}">
+              {{ value }}
+            </span>
+            <span *ngIf="!value" class="state-PENDING">PENDING</span>
+          </ng-template>
+        </ngx-datatable-column>
+      </ngx-datatable>
+      <ngx-json-viewer *ngSwitchCase="'json'" [json]="workflow.json" fxFill></ngx-json-viewer>
     </section>
   </section>
 </section>

http://git-wip-us.apache.org/repos/asf/helix/blob/b486d784/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.ts b/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.ts
index 8979d13..f1f04ad 100644
--- a/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.ts
+++ b/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.ts
@@ -2,9 +2,11 @@ import { Component, OnInit } from '@angular/core';
 import { ActivatedRoute } from '@angular/router';
 
 import * as moment from 'moment';
+import * as shape from 'd3-shape';
+import * as _ from 'lodash';
 
 import { Settings } from '../../core/settings';
-import { Workflow } from '../shared/workflow.model';
+import { Workflow, Job } from '../shared/workflow.model';
 import { WorkflowService } from '../shared/workflow.service';
 
 @Component({
@@ -25,7 +27,7 @@ export class WorkflowDetailComponent implements OnInit {
     { prop: 'name', dir: 'asc'}
   ];
   messages = {
-    emptyMessage: 'The queue is empty.',
+    emptyMessage: 'The list is empty.',
     totalMessage: 'total',
     selectedMessage: 'selected'
   };
@@ -58,5 +60,4 @@ export class WorkflowDetailComponent implements OnInit {
     const row = selected[0];
     // this.table.rowDetail.toggleExpandRow(row);
   }
-
 }

http://git-wip-us.apache.org/repos/asf/helix/blob/b486d784/helix-front/client/app/workflow/workflow.module.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/workflow.module.ts b/helix-front/client/app/workflow/workflow.module.ts
index e218928..7ff9868 100644
--- a/helix-front/client/app/workflow/workflow.module.ts
+++ b/helix-front/client/app/workflow/workflow.module.ts
@@ -2,24 +2,30 @@ import { NgModule } from '@angular/core';
 import { CommonModule } from '@angular/common';
 
 import { NgxDatatableModule } from '@swimlane/ngx-datatable';
+import { NgxChartsModule } from '@swimlane/ngx-charts';
+import { NgxGraphModule } from '@swimlane/ngx-graph';
 
 import { WorkflowListComponent } from './workflow-list/workflow-list.component';
 import { WorkflowService } from './shared/workflow.service';
 import { SharedModule } from '../shared/shared.module';
 import { WorkflowDetailComponent } from './workflow-detail/workflow-detail.component';
+import { WorkflowDagComponent } from './workflow-dag/workflow-dag.component';
 
 @NgModule({
   imports: [
     CommonModule,
     SharedModule,
-    NgxDatatableModule
+    NgxDatatableModule,
+    NgxChartsModule,
+    NgxGraphModule
   ],
   providers: [
     WorkflowService
   ],
   declarations: [
     WorkflowListComponent,
-    WorkflowDetailComponent
+    WorkflowDetailComponent,
+    WorkflowDagComponent
   ]
 })
 export class WorkflowModule { }

http://git-wip-us.apache.org/repos/asf/helix/blob/b486d784/helix-front/package.json
----------------------------------------------------------------------
diff --git a/helix-front/package.json b/helix-front/package.json
index bd5fdc9..8c8d1b4 100644
--- a/helix-front/package.json
+++ b/helix-front/package.json
@@ -33,10 +33,13 @@
     "@angular/platform-browser": "^5.1.1",
     "@angular/platform-browser-dynamic": "^5.1.1",
     "@angular/router": "^5.1.1",
+    "@swimlane/ngx-charts": "^7.0.1",
     "@swimlane/ngx-datatable": "^11.1.7",
+    "@swimlane/ngx-graph": "^4.0.2",
     "angulartics2": "^2.2.1",
     "body-parser": "^1.17.2",
     "core-js": "^2.4.1",
+    "d3-shape": "^1.2.0",
     "dotenv": "^4.0.0",
     "express": "^4.15.3",
     "express-session": "^1.15.6",


[11/14] helix git commit: use ldapjs lib instead of ldap-client

Posted by jx...@apache.org.
use ldapjs lib instead of ldap-client


Project: http://git-wip-us.apache.org/repos/asf/helix/repo
Commit: http://git-wip-us.apache.org/repos/asf/helix/commit/5f248c3d
Tree: http://git-wip-us.apache.org/repos/asf/helix/tree/5f248c3d
Diff: http://git-wip-us.apache.org/repos/asf/helix/diff/5f248c3d

Branch: refs/heads/master
Commit: 5f248c3d0b823c59ffc7855e291eedf62b2d5a6f
Parents: d60d51b
Author: Vivo Xu <vx...@linkedin.com>
Authored: Tue Apr 3 12:57:22 2018 -0700
Committer: Vivo Xu <vx...@linkedin.com>
Committed: Wed Aug 8 15:42:11 2018 -0700

----------------------------------------------------------------------
 helix-front/package.json               |  2 +-
 helix-front/server/controllers/user.ts | 14 +++-----------
 2 files changed, 4 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/helix/blob/5f248c3d/helix-front/package.json
----------------------------------------------------------------------
diff --git a/helix-front/package.json b/helix-front/package.json
index 844980b..b52563b 100644
--- a/helix-front/package.json
+++ b/helix-front/package.json
@@ -44,7 +44,7 @@
     "express": "^4.15.3",
     "express-session": "^1.15.6",
     "hammerjs": "^2.0.8",
-    "ldap-client": "^3.1.3",
+    "ldapjs": "^1.0.2",
     "lodash": "^4.17.4",
     "moment": "^2.22.2",
     "morgan": "^1.8.2",

http://git-wip-us.apache.org/repos/asf/helix/blob/5f248c3d/helix-front/server/controllers/user.ts
----------------------------------------------------------------------
diff --git a/helix-front/server/controllers/user.ts b/helix-front/server/controllers/user.ts
index 609b9f8..1cd5288 100644
--- a/helix-front/server/controllers/user.ts
+++ b/helix-front/server/controllers/user.ts
@@ -1,7 +1,7 @@
 import { Request, Response, Router } from 'express';
 
 import * as request from 'request';
-import * as LdapClient from 'ldap-client';
+import * as LdapClient from 'ldapjs';
 
 import { LDAP, CheckAdmin } from '../config';
 
@@ -48,16 +48,8 @@ export class UserCtrl {
     }
 
     // check LDAP
-    const ldap = new LdapClient({ uri: LDAP.uri, base: LDAP.base }, err => {
-      if (err) {
-        res.status(500).json(err);
-      }
-    });
-
-    ldap.bind({
-      binddn: credential.username + LDAP.principalSuffix,
-      password: credential.password
-    }, err => {
+    const ldap = LdapClient.createClient({ url: LDAP.uri });
+    ldap.bind(credential.username + LDAP.principalSuffix, credential.password, err => {
       if (err) {
         res.status(401).json(false);
       } else {


[12/14] helix git commit: Skip admin check if called authorize

Posted by jx...@apache.org.
http://git-wip-us.apache.org/repos/asf/helix/blob/f1c50371/helix-front/server/controllers/user.ts
----------------------------------------------------------------------
diff --git a/helix-front/server/controllers/user.ts b/helix-front/server/controllers/user.ts
index 1cd5288..21e1b0f 100644
--- a/helix-front/server/controllers/user.ts
+++ b/helix-front/server/controllers/user.ts
@@ -17,16 +17,14 @@ export class UserCtrl {
   // please rewrite this function to support your own authorization logic
   protected authorize(req: Request, res: Response) {
     if (req.query.name) {
+      // since it's bypass mode, skip admin check
       req.session.username = req.query.name;
-      CheckAdmin(req.session.username, (isAdmin: boolean) => {
-        req.session.isAdmin = isAdmin;
 
-        if (req.query.url) {
-          res.redirect(req.query.url);
-        } else {
-          res.redirect('/');
-        }
-      });
+      if (req.query.url) {
+        res.redirect(req.query.url);
+      } else {
+        res.redirect('/');
+      }
     } else {
       res.status(401).send('Unauthorized');
     }


[10/14] helix git commit: add ldap support

Posted by jx...@apache.org.
add ldap support


Project: http://git-wip-us.apache.org/repos/asf/helix/repo
Commit: http://git-wip-us.apache.org/repos/asf/helix/commit/d60d51b2
Tree: http://git-wip-us.apache.org/repos/asf/helix/tree/d60d51b2
Diff: http://git-wip-us.apache.org/repos/asf/helix/diff/d60d51b2

Branch: refs/heads/master
Commit: d60d51b24c20b73a9b3fba906674479f4fba17e0
Parents: e94b82a
Author: Vivo Xu <vx...@linkedin.com>
Authored: Fri Mar 9 15:18:01 2018 -0800
Committer: Vivo Xu <vx...@linkedin.com>
Committed: Wed Aug 8 15:39:33 2018 -0700

----------------------------------------------------------------------
 helix-front/client/app/app.component.html       |  2 +-
 helix-front/client/app/app.component.ts         | 41 +++++++++++++++++++-
 helix-front/client/app/core/user.service.ts     | 10 +++++
 .../input-dialog/input-dialog.component.html    | 14 +++++--
 helix-front/package.json                        |  1 +
 helix-front/server/config.ts                    |  6 +++
 helix-front/server/controllers/user.ts          | 36 ++++++++++++++++-
 7 files changed, 104 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/helix/blob/d60d51b2/helix-front/client/app/app.component.html
----------------------------------------------------------------------
diff --git a/helix-front/client/app/app.component.html b/helix-front/client/app/app.component.html
index d30b162..b199228 100644
--- a/helix-front/client/app/app.component.html
+++ b/helix-front/client/app/app.component.html
@@ -5,7 +5,7 @@
       <span class="helix-title">Helix</span>
     </a>
     <span fxFlex="1 1 auto"></span>
-    <a mat-button>
+    <a mat-button (click)="login()">
       <mat-icon>person</mat-icon>
       {{ currentUser | async }}
     </a>

http://git-wip-us.apache.org/repos/asf/helix/blob/d60d51b2/helix-front/client/app/app.component.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/app.component.ts b/helix-front/client/app/app.component.ts
index 31de982..13d7426 100644
--- a/helix-front/client/app/app.component.ts
+++ b/helix-front/client/app/app.component.ts
@@ -7,11 +7,14 @@ import {
   NavigationCancel,
   NavigationError
 } from '@angular/router';
+import { MatDialog } from '@angular/material';
 
 import { Angulartics2Piwik } from 'angulartics2';
 
 import { environment } from '../environments/environment';
 import { UserService } from './core/user.service';
+import { InputDialogComponent } from './shared/dialog/input-dialog/input-dialog.component';
+import { HelperService } from './shared/helper.service';
 
 @Component({
   selector: 'hi-root',
@@ -30,7 +33,9 @@ export class AppComponent implements OnInit {
     protected route: ActivatedRoute,
     protected router: Router,
     protected angulartics: Angulartics2Piwik,
-    protected service: UserService
+    protected dialog: MatDialog,
+    protected service: UserService,
+    protected helper: HelperService
   ) {
     router.events.subscribe(event => {
       if (event instanceof NavigationStart) {
@@ -57,4 +62,38 @@ export class AppComponent implements OnInit {
       }
     });
   }
+
+  login() {
+    this.dialog
+      .open(InputDialogComponent, {
+        data: {
+          title: 'Login',
+          message: 'Please enter your LDAP username and password to continue:',
+          values: {
+            username: {
+              label: 'Username'
+            },
+            password: {
+              label: 'Password',
+              type: 'password'
+            }
+          }
+        }
+      })
+      .afterClosed()
+      .subscribe(result => {
+        if (result && result.username.value && result.password.value) {
+          this.service
+            .login(result.username.value, result.password.value)
+            .subscribe(
+              isAuthroized => {
+                if (isAuthroized) {
+                  location.reload();
+                }
+              },
+              error => this.helper.showError(error)
+            );
+        }
+      });
+  }
 }

http://git-wip-us.apache.org/repos/asf/helix/blob/d60d51b2/helix-front/client/app/core/user.service.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/core/user.service.ts b/helix-front/client/app/core/user.service.ts
index 3336c1f..21676af 100644
--- a/helix-front/client/app/core/user.service.ts
+++ b/helix-front/client/app/core/user.service.ts
@@ -20,6 +20,16 @@ export class UserService {
       .catch(_ => _);
   }
 
+  public login(username: string, password: string): Observable<boolean> {
+    return this.http
+      .post(
+        `${ Settings.userAPI }/login`,
+        { username: username, password: password },
+        { headers: this.getHeaders() }
+      )
+      .map(response => response.json());
+  }
+
   protected getHeaders() {
     let headers = new Headers();
     headers.append('Accept', 'application/json');

http://git-wip-us.apache.org/repos/asf/helix/blob/d60d51b2/helix-front/client/app/shared/dialog/input-dialog/input-dialog.component.html
----------------------------------------------------------------------
diff --git a/helix-front/client/app/shared/dialog/input-dialog/input-dialog.component.html b/helix-front/client/app/shared/dialog/input-dialog/input-dialog.component.html
index bf4cd78..153dda3 100644
--- a/helix-front/client/app/shared/dialog/input-dialog/input-dialog.component.html
+++ b/helix-front/client/app/shared/dialog/input-dialog/input-dialog.component.html
@@ -4,8 +4,8 @@
     <section>
       {{ message }}
     </section>
-    <section *ngFor="let name of getKeys(values)">
-      <section *ngIf="values[name].type === 'boolean'">
+    <section *ngFor="let name of getKeys(values)" [ngSwitch]="values[name].type">
+      <section *ngSwitchCase="'boolean'">
         {{ values[name].label }}:
         <mat-slide-toggle
           [name]="name"
@@ -13,7 +13,15 @@
           {{ values[name].value ? 'True' : 'False' }}
         </mat-slide-toggle>
       </section>
-      <mat-form-field *ngIf="values[name].type !== 'boolean'">
+      <mat-form-field *ngSwitchCase="'password'">
+        <input matInput
+          type="password"
+          [name]="name"
+          [(ngModel)]="values[name].value"
+          [placeholder]="values[name].label"
+          required>
+      </mat-form-field>
+      <mat-form-field *ngSwitchDefault>
         <input matInput
           [name]="name"
           [(ngModel)]="values[name].value"

http://git-wip-us.apache.org/repos/asf/helix/blob/d60d51b2/helix-front/package.json
----------------------------------------------------------------------
diff --git a/helix-front/package.json b/helix-front/package.json
index 846ff78..844980b 100644
--- a/helix-front/package.json
+++ b/helix-front/package.json
@@ -44,6 +44,7 @@
     "express": "^4.15.3",
     "express-session": "^1.15.6",
     "hammerjs": "^2.0.8",
+    "ldap-client": "^3.1.3",
     "lodash": "^4.17.4",
     "moment": "^2.22.2",
     "morgan": "^1.8.2",

http://git-wip-us.apache.org/repos/asf/helix/blob/d60d51b2/helix-front/server/config.ts
----------------------------------------------------------------------
diff --git a/helix-front/server/config.ts b/helix-front/server/config.ts
index 1f49dd2..1a4265b 100644
--- a/helix-front/server/config.ts
+++ b/helix-front/server/config.ts
@@ -14,6 +14,12 @@ export const SSL = {
   cafiles: []
 };
 
+export const LDAP = {
+  uri: 'ldap://example.com',
+  base: 'DC=example,DC=com',
+  principalSuffix: '@example.com'
+};
+
 export function CheckAdmin(username: string, callback: (boolean) => void) {
   callback(username === 'root');
 }

http://git-wip-us.apache.org/repos/asf/helix/blob/d60d51b2/helix-front/server/controllers/user.ts
----------------------------------------------------------------------
diff --git a/helix-front/server/controllers/user.ts b/helix-front/server/controllers/user.ts
index 84d2c11..609b9f8 100644
--- a/helix-front/server/controllers/user.ts
+++ b/helix-front/server/controllers/user.ts
@@ -1,13 +1,15 @@
 import { Request, Response, Router } from 'express';
 
 import * as request from 'request';
+import * as LdapClient from 'ldap-client';
 
-import { CheckAdmin } from '../config';
+import { LDAP, CheckAdmin } from '../config';
 
 export class UserCtrl {
 
   constructor(router: Router) {
     router.route('/user/authorize').get(this.authorize);
+    router.route('/user/login').post(this.login.bind(this));
     router.route('/user/current').get(this.current);
     router.route('/user/can').get(this.can);
   }
@@ -37,4 +39,36 @@ export class UserCtrl {
   protected can(req: Request, res: Response) {
     res.json(req.session.isAdmin ? true : false);
   }
+
+  protected login(req: Request, res: Response) {
+    const credential = req.body;
+    if (!credential.username || !credential.password) {
+      res.status(401).json(false);
+      return;
+    }
+
+    // check LDAP
+    const ldap = new LdapClient({ uri: LDAP.uri, base: LDAP.base }, err => {
+      if (err) {
+        res.status(500).json(err);
+      }
+    });
+
+    ldap.bind({
+      binddn: credential.username + LDAP.principalSuffix,
+      password: credential.password
+    }, err => {
+      if (err) {
+        res.status(401).json(false);
+      } else {
+        // authroized
+        req.session.username = credential.username;
+        CheckAdmin(req.session.username, (isAdmin: boolean) => {
+          req.session.isAdmin = isAdmin;
+          res.json(true);
+        });
+      }
+    });
+  }
+
 }


[14/14] helix git commit: Skip admin check if called authorize

Posted by jx...@apache.org.
Skip admin check if called authorize


Project: http://git-wip-us.apache.org/repos/asf/helix/repo
Commit: http://git-wip-us.apache.org/repos/asf/helix/commit/f1c50371
Tree: http://git-wip-us.apache.org/repos/asf/helix/tree/f1c50371
Diff: http://git-wip-us.apache.org/repos/asf/helix/diff/f1c50371

Branch: refs/heads/master
Commit: f1c503712f7d555da024a0be186ea2ddd730990b
Parents: 5f248c3
Author: Vivo Xu <vx...@linkedin.com>
Authored: Wed Jun 27 14:12:34 2018 -0700
Committer: Vivo Xu <vx...@linkedin.com>
Committed: Wed Aug 8 15:45:49 2018 -0700

----------------------------------------------------------------------
 helix-front/package-lock.json          | 4250 +++++++++++++++------------
 helix-front/server/controllers/user.ts |   14 +-
 2 files changed, 2335 insertions(+), 1929 deletions(-)
----------------------------------------------------------------------



[07/14] helix git commit: Introduce Helix cluster dashboard

Posted by jx...@apache.org.
Introduce Helix cluster dashboard


Project: http://git-wip-us.apache.org/repos/asf/helix/repo
Commit: http://git-wip-us.apache.org/repos/asf/helix/commit/a97f1580
Tree: http://git-wip-us.apache.org/repos/asf/helix/tree/a97f1580
Diff: http://git-wip-us.apache.org/repos/asf/helix/diff/a97f1580

Branch: refs/heads/master
Commit: a97f15809efe6e325b17ff38f040b693f9ad94ad
Parents: d192afc
Author: Vivo Xu <vx...@linkedin.com>
Authored: Thu Dec 21 15:38:07 2017 -0800
Committer: Vivo Xu <vx...@linkedin.com>
Committed: Wed Aug 8 15:36:53 2018 -0700

----------------------------------------------------------------------
 helix-front/client/app/app-routing.module.ts    |   5 +
 helix-front/client/app/app.module.ts            |   4 +-
 .../cluster-detail/cluster-detail.component.ts  |   1 +
 .../app/dashboard/dashboard.component.html      |  29 ++
 .../app/dashboard/dashboard.component.scss      |  43 +++
 .../app/dashboard/dashboard.component.spec.ts   |  31 +++
 .../client/app/dashboard/dashboard.component.ts | 274 +++++++++++++++++++
 .../client/app/dashboard/dashboard.module.ts    |  21 ++
 helix-front/client/styles.scss                  |   3 +
 helix-front/package.json                        |   4 +-
 10 files changed, 413 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/helix/blob/a97f1580/helix-front/client/app/app-routing.module.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/app-routing.module.ts b/helix-front/client/app/app-routing.module.ts
index f32a793..3e34485 100644
--- a/helix-front/client/app/app-routing.module.ts
+++ b/helix-front/client/app/app-routing.module.ts
@@ -16,6 +16,7 @@ import { InstanceDetailComponent } from './instance/instance-detail/instance-det
 import { WorkflowListComponent } from './workflow/workflow-list/workflow-list.component';
 import { WorkflowDetailComponent } from './workflow/workflow-detail/workflow-detail.component';
 import { HelixListComponent } from './chooser/helix-list/helix-list.component';
+import { DashboardComponent } from './dashboard/dashboard.component';
 
 const HELIX_ROUTES: Routes = [
   {
@@ -55,6 +56,10 @@ const HELIX_ROUTES: Routes = [
           {
             path: 'workflows',
             component: WorkflowListComponent
+          },
+          {
+            path: 'dashboard',
+            component: DashboardComponent
           }
         ]
       },

http://git-wip-us.apache.org/repos/asf/helix/blob/a97f1580/helix-front/client/app/app.module.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/app.module.ts b/helix-front/client/app/app.module.ts
index 0b5fd25..b781eee 100644
--- a/helix-front/client/app/app.module.ts
+++ b/helix-front/client/app/app.module.ts
@@ -17,6 +17,7 @@ import { HistoryModule } from './history/history.module';
 import { AppComponent } from './app.component';
 import { WorkflowModule } from './workflow/workflow.module';
 import { ChooserModule } from './chooser/chooser.module';
+import { DashboardModule } from './dashboard/dashboard.module';
 
 @NgModule({
   declarations: [
@@ -37,7 +38,8 @@ import { ChooserModule } from './chooser/chooser.module';
     ControllerModule,
     HistoryModule,
     WorkflowModule,
-    ChooserModule
+    ChooserModule,
+    DashboardModule
   ],
   providers: [],
   bootstrap: [AppComponent]

http://git-wip-us.apache.org/repos/asf/helix/blob/a97f1580/helix-front/client/app/cluster/cluster-detail/cluster-detail.component.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/cluster/cluster-detail/cluster-detail.component.ts b/helix-front/client/app/cluster/cluster-detail/cluster-detail.component.ts
index 1308b4d..d2a3bb3 100644
--- a/helix-front/client/app/cluster/cluster-detail/cluster-detail.component.ts
+++ b/helix-front/client/app/cluster/cluster-detail/cluster-detail.component.ts
@@ -18,6 +18,7 @@ import { InputDialogComponent } from '../../shared/dialog/input-dialog/input-dia
 export class ClusterDetailComponent implements OnInit {
 
   readonly tabLinks = [
+    { label: 'Dashboard (beta)', link: 'dashboard' },
     { label: 'Resources', link: 'resources' },
     { label: 'Workflows', link: 'workflows' },
     { label: 'Instances', link: 'instances' },

http://git-wip-us.apache.org/repos/asf/helix/blob/a97f1580/helix-front/client/app/dashboard/dashboard.component.html
----------------------------------------------------------------------
diff --git a/helix-front/client/app/dashboard/dashboard.component.html b/helix-front/client/app/dashboard/dashboard.component.html
new file mode 100644
index 0000000..85989f1
--- /dev/null
+++ b/helix-front/client/app/dashboard/dashboard.component.html
@@ -0,0 +1,29 @@
+<section fxLayout="column" fxFlex>
+  <section class="info" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="10px">
+    <div class="oval">Resource</div>
+    <div class="rectangle">Instance</div>
+    <div class="hint">
+      (Scroll to zoom; Drag to move; Click for details)
+    </div>
+    <span fxFlex="1 1 auto"></span>
+    <button mat-button (click)="updateResources()">
+      <mat-icon>refresh</mat-icon>
+      Refresh Status
+    </button>
+    <button
+      mat-button
+      *ngIf="selectedResource || selectedInstance"
+      color="accent"
+      [routerLink]="['../', selectedResource ? 'resources' : 'instances', selectedResource || selectedInstance]">
+      {{ selectedResource || selectedInstance }}
+      <mat-icon>arrow_forward</mat-icon>
+    </button>
+  </section>
+  <section
+    class="cluster-dashboard"
+    [visNetwork]="visNetwork"
+    [visNetworkData]="visNetworkData"
+    [visNetworkOptions]="visNetworkOptions"
+    (initialized)="networkInitialized()">
+  </section>
+</section>

http://git-wip-us.apache.org/repos/asf/helix/blob/a97f1580/helix-front/client/app/dashboard/dashboard.component.scss
----------------------------------------------------------------------
diff --git a/helix-front/client/app/dashboard/dashboard.component.scss b/helix-front/client/app/dashboard/dashboard.component.scss
new file mode 100644
index 0000000..dd14878
--- /dev/null
+++ b/helix-front/client/app/dashboard/dashboard.component.scss
@@ -0,0 +1,43 @@
+:host {
+  width: 100%;
+  height: 100%;
+}
+
+.cluster-dashboard {
+  width: 800px;
+  height: 600px;
+}
+
+.info {
+  font-size: 12px;
+  height: 36px;
+  background-color: #fff;
+  border-bottom: 1px solid #ccc;
+  text-align: center;
+  vertical-align: middle;
+  line-height: 24px;
+}
+
+.oval {
+  width: 80px;
+  height: 24px;
+  background-color: #7FCAC3;
+  border: 1px solid #65A19C;
+  -moz-border-radius: 80px / 24px;
+  -webkit-border-radius: 80px / 24px;
+  border-raius: 80px / 24px;
+}
+
+.rectangle {
+  width: 80px;
+  height: 24px;
+  background-color: #90CAF9;
+  border: 1px solid #73A1C7;
+  -moz-border-radius: 5px;
+  -webkit-border-radius: 5px;
+  border-raius: 5px;
+}
+
+.hint {
+  color: gray;
+}

http://git-wip-us.apache.org/repos/asf/helix/blob/a97f1580/helix-front/client/app/dashboard/dashboard.component.spec.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/dashboard/dashboard.component.spec.ts b/helix-front/client/app/dashboard/dashboard.component.spec.ts
new file mode 100644
index 0000000..2c1c53a
--- /dev/null
+++ b/helix-front/client/app/dashboard/dashboard.component.spec.ts
@@ -0,0 +1,31 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+
+import { VisModule } from 'ngx-vis';
+
+import { TestingModule } from '../../testing/testing.module';
+import { DashboardComponent } from './dashboard.component';
+
+describe('DashboardComponent', () => {
+  let component: DashboardComponent;
+  let fixture: ComponentFixture<DashboardComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [ TestingModule, VisModule ],
+      schemas: [ NO_ERRORS_SCHEMA ],
+      declarations: [ DashboardComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(DashboardComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should be created', () => {
+    expect(component).toBeTruthy();
+  });
+});

http://git-wip-us.apache.org/repos/asf/helix/blob/a97f1580/helix-front/client/app/dashboard/dashboard.component.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/dashboard/dashboard.component.ts b/helix-front/client/app/dashboard/dashboard.component.ts
new file mode 100644
index 0000000..dbe32a3
--- /dev/null
+++ b/helix-front/client/app/dashboard/dashboard.component.ts
@@ -0,0 +1,274 @@
+import {
+  Component,
+  ElementRef,
+  OnInit,
+  AfterViewInit,
+  OnDestroy
+} from '@angular/core';
+import { Router, ActivatedRoute } from '@angular/router';
+import { Observable, Subscription } from 'rxjs';
+
+import * as _ from 'lodash';
+import { VisNode, VisNodes, VisEdges, VisNetworkService, VisNetworkData, VisNetworkOptions } from 'ngx-vis';
+
+import { ResourceService } from '../resource/shared/resource.service';
+import { InstanceService } from '../instance/shared/instance.service';
+import { HelperService } from '../shared/helper.service';
+
+class DashboardNetworkData implements VisNetworkData {
+    public nodes: VisNodes;
+    public edges: VisEdges;
+}
+
+@Component({
+  selector: 'hi-dashboard',
+  templateUrl: './dashboard.component.html',
+  styleUrls: ['./dashboard.component.scss'],
+  providers: [ResourceService, InstanceService]
+})
+export class DashboardComponent implements OnInit, AfterViewInit, OnDestroy {
+
+  visNetwork = 'cluster-dashboard';
+  visNetworkData: DashboardNetworkData;
+  visNetworkOptions: VisNetworkOptions;
+
+  clusterName: string;
+  isLoading = false;
+  resourceToId = {};
+  instanceToId = {};
+  selectedResource: any;
+  selectedInstance: any;
+  updateSubscription: Subscription;
+  updateInterval = 3000;
+
+  constructor(
+    private el:ElementRef,
+    private route: ActivatedRoute,
+    protected visService: VisNetworkService,
+    protected resourceService: ResourceService,
+    protected instanceService: InstanceService,
+    protected helper: HelperService
+  ) { }
+
+  networkInitialized() {
+    this.visService.on(this.visNetwork, 'click');
+    this.visService.on(this.visNetwork, 'zoom');
+
+    this.visService.click
+      .subscribe((eventData: any[]) => {
+        if (eventData[0] === this.visNetwork) {
+          // clear the edges first
+          this.visNetworkData.edges.clear();
+          this.selectedResource = null;
+          this.selectedInstance = null;
+
+          //
+          if (eventData[1].nodes.length) {
+            const id = eventData[1].nodes[0];
+            this.onNodeSelected(id);
+          }
+        }
+      });
+
+    this.visService.zoom
+      .subscribe((eventData: any) => {
+        if (eventData[0] === this.visNetwork) {
+          const scale = eventData[1].scale;
+          if (scale == 10) {
+            // too big
+          } else if (scale < 0.3) {
+            // small enough
+          }
+        }
+      });
+  }
+
+  ngOnInit() {
+    const nodes = new VisNodes();
+    const edges = new VisEdges();
+    this.visNetworkData = { nodes, edges };
+
+    this.visNetworkOptions = {
+      interaction: {
+        navigationButtons: true,
+        keyboard: true
+      },
+      layout: {
+        // layout will be the same every time the nodes are settled
+        randomSeed: 7
+      },
+      physics: {
+        // default is barnesHut
+        solver: 'forceAtlas2Based',
+        forceAtlas2Based: {
+          // default: -50
+          gravitationalConstant: -30,
+          // default: 0
+          // avoidOverlap: 0.3
+        }
+      },
+      groups: {
+        resource: {
+          color: '#7FCAC3',
+          shape: 'ellipse',
+          widthConstraint: { maximum: 140 }
+        },
+        instance: {
+          color: '#90CAF9',
+          shape: 'box',
+          widthConstraint: { maximum: 140 }
+        },
+        instance_bad: {
+          color: '#CA7F86',
+          shape: 'box',
+          widthConstraint: { maximum: 140 }
+        },
+        partition: {
+          color: '#98D4B1',
+          shape: 'ellipse',
+          widthConstraint: { maximum: 140 }
+        }
+      }
+    };
+  }
+
+  initDashboard() {
+    // resize container according to the parent
+    let width = this.el.nativeElement.offsetWidth;
+    let height = this.el.nativeElement.offsetHeight - 36;
+    let dashboardDom = this.el.nativeElement.getElementsByClassName(this.visNetwork)[0];
+    dashboardDom.style.width = `${ width }px`;
+    dashboardDom.style.height = `${ height }px`;
+
+    // load data
+    this.route.parent.params
+      .map(p => p.name)
+      .subscribe(name => {
+        this.clusterName = name;
+        this.fetchResources();
+        // this.updateResources();
+      });
+  }
+
+  ngAfterViewInit() {
+    setTimeout(_ => this.initDashboard());
+  }
+
+  ngOnDestroy(): void {
+    if (this.updateSubscription) {
+      this.updateSubscription.unsubscribe();
+    }
+
+    this.visService.off(this.visNetwork, 'zoom');
+    this.visService.off(this.visNetwork, 'click');
+  }
+
+  protected fetchResources() {
+    this.isLoading = true;
+
+    this.resourceService
+      .getAll(this.clusterName)
+      .subscribe(
+        result => {
+          _.forEach(result, (resource) => {
+            const newId = this.visNetworkData.nodes.getLength() + 1;
+            this.resourceToId[resource.name] = newId;
+            this.visNetworkData.nodes.add({
+              id: newId,
+              label: resource.name,
+              group: 'resource',
+              title: JSON.stringify(resource)
+            });
+          });
+
+          this.visService.fit(this.visNetwork);
+        },
+        error => this.helper.showError(error),
+        () => this.isLoading = false
+      );
+
+    this.instanceService
+      .getAll(this.clusterName)
+      .subscribe(
+        result => {
+          _.forEach(result, (instance) => {
+            const newId = this.visNetworkData.nodes.getLength() + 1;
+            this.instanceToId[instance.name] = newId;
+            this.visNetworkData.nodes.add({
+              id: newId,
+              label: instance.name,
+              group: instance.healthy ? 'instance' : 'instance_bad',
+              title: JSON.stringify(instance),
+            });
+          });
+
+          this.visService.fit(this.visNetwork);
+        },
+        error => this.helper.showError(error),
+        () => this.isLoading = false
+      );
+  }
+
+  updateResources() {
+    /* disable auto-update for now
+    this.updateSubscription = Observable
+      .interval(this.updateInterval)
+      .flatMap(i => this.instanceService.getAll(this.clusterName))*/
+    this.instanceService.getAll(this.clusterName)
+      .subscribe(
+        result => {
+          _.forEach(result, instance => {
+            const id = this.instanceToId[instance.name];
+            this.visNetworkData.nodes.update([{
+              id: id,
+              group: instance.healthy ? 'instance' : 'instance_bad'
+            }]);
+          });
+        }
+      );
+  }
+
+  protected onNodeSelected(id) {
+    const instanceName = _.findKey(this.instanceToId, value => value === id);
+    if (instanceName) {
+      this.selectedInstance = instanceName;
+      // fetch relationships
+      this.resourceService
+        .getAllOnInstance(this.clusterName, instanceName)
+        .subscribe(
+          resources => {
+            _.forEach(resources, (resource) => {
+              this.visNetworkData.edges.add({
+                from: id,
+                to: this.resourceToId[resource.name]
+              });
+            });
+          },
+          error => this.helper.showError(error)
+        );
+    } else {
+      const resourceName = _.findKey(this.resourceToId, value => value === id);
+      if (resourceName) {
+        this.selectedResource = resourceName;
+        this.resourceService
+          .get(this.clusterName, resourceName)
+          .subscribe(
+            resource => {
+              _(resource.partitions)
+                .flatMap('replicas')
+                .unionBy('instanceName')
+                .map('instanceName')
+                .forEach((instanceName) => {
+                  this.visNetworkData.edges.add({
+                    from: this.instanceToId[instanceName],
+                    to: this.resourceToId[resourceName]
+                  });
+                });
+            },
+            error => this.helper.showError(error)
+          );
+      }
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/helix/blob/a97f1580/helix-front/client/app/dashboard/dashboard.module.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/dashboard/dashboard.module.ts b/helix-front/client/app/dashboard/dashboard.module.ts
new file mode 100644
index 0000000..e048327
--- /dev/null
+++ b/helix-front/client/app/dashboard/dashboard.module.ts
@@ -0,0 +1,21 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+import { VisModule } from 'ngx-vis';
+import { NgxChartsModule } from '@swimlane/ngx-charts';
+
+import { SharedModule } from '../shared/shared.module';
+import { DashboardComponent } from './dashboard.component';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    SharedModule,
+    VisModule,
+    NgxChartsModule
+  ],
+  declarations: [
+    DashboardComponent
+  ]
+})
+export class DashboardModule { }

http://git-wip-us.apache.org/repos/asf/helix/blob/a97f1580/helix-front/client/styles.scss
----------------------------------------------------------------------
diff --git a/helix-front/client/styles.scss b/helix-front/client/styles.scss
index 9a64f47..820de3a 100644
--- a/helix-front/client/styles.scss
+++ b/helix-front/client/styles.scss
@@ -2,6 +2,9 @@
 @import url('https://fonts.googleapis.com/icon?family=Material+Icons');
 @import url('https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic');
 
+// ngx-vis styles (vis styles)
+@import '~vis/dist/vis-network.min.css';
+
 // ngx-datatable styles
 @import '~@swimlane/ngx-datatable/release/index.css';
 @import '~@swimlane/ngx-datatable/release/themes/material.css';

http://git-wip-us.apache.org/repos/asf/helix/blob/a97f1580/helix-front/package.json
----------------------------------------------------------------------
diff --git a/helix-front/package.json b/helix-front/package.json
index 626fedd..846ff78 100644
--- a/helix-front/package.json
+++ b/helix-front/package.json
@@ -27,7 +27,7 @@
     "@angular/common": "^5.1.1",
     "@angular/compiler": "^5.1.1",
     "@angular/core": "^5.1.1",
-    "@angular/flex-layout": "^2.0.0-beta.12",
+    "@angular/flex-layout": "2.0.0-beta.12",
     "@angular/forms": "^5.1.1",
     "@angular/http": "^5.1.1",
     "@angular/material": "^5.0.1",
@@ -50,9 +50,11 @@
     "ngx-clipboard": "^9.0.0",
     "ngx-dag": "0.0.2",
     "ngx-json-viewer": "^2.3.0",
+    "ngx-vis": "^0.1.0",
     "node-sass": "4.5.3",
     "request": "^2.81.0",
     "rxjs": "^5.5.5",
+    "vis": "^4.21.0",
     "zone.js": "^0.8.4"
   },
   "devDependencies": {


[03/14] helix git commit: Fix the building issue

Posted by jx...@apache.org.
Fix the building issue


Project: http://git-wip-us.apache.org/repos/asf/helix/repo
Commit: http://git-wip-us.apache.org/repos/asf/helix/commit/4739d4af
Tree: http://git-wip-us.apache.org/repos/asf/helix/tree/4739d4af
Diff: http://git-wip-us.apache.org/repos/asf/helix/diff/4739d4af

Branch: refs/heads/master
Commit: 4739d4afb401a9196b31d0db19aa600c5f2f38da
Parents: 9da7592
Author: Vivo Xu <vx...@linkedin.com>
Authored: Mon Feb 5 11:57:02 2018 -0800
Committer: Vivo Xu <vx...@linkedin.com>
Committed: Wed Aug 8 15:31:23 2018 -0700

----------------------------------------------------------------------
 .../client/app/workflow/workflow-dag/workflow-dag.component.html  | 1 -
 helix-front/package.json                                          | 3 ++-
 2 files changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/helix/blob/4739d4af/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.html
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.html b/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.html
index c44d369..f75a53b 100644
--- a/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.html
+++ b/helix-front/client/app/workflow/workflow-dag/workflow-dag.component.html
@@ -6,7 +6,6 @@
   [nodes]="data.nodes"
   [curve]="curve"
   orientation="TB"
-  (select)="select($event)"
   [autoZoom]="false"
   [panningEnabled]="false"
   [draggingEnabled]="false"

http://git-wip-us.apache.org/repos/asf/helix/blob/4739d4af/helix-front/package.json
----------------------------------------------------------------------
diff --git a/helix-front/package.json b/helix-front/package.json
index e611637..626fedd 100644
--- a/helix-front/package.json
+++ b/helix-front/package.json
@@ -18,7 +18,8 @@
     "prod": "npm run build && node dist/server/app.js",
     "test": "ng test",
     "lint": "ng lint",
-    "e2e": "ng e2e"
+    "e2e": "ng e2e",
+    "smoke": "ng lint && ng test -sr && npm run build"
   },
   "dependencies": {
     "@angular/animations": "^5.1.1",


[05/14] helix git commit: Optimize resource list fetching

Posted by jx...@apache.org.
Optimize resource list fetching


Project: http://git-wip-us.apache.org/repos/asf/helix/repo
Commit: http://git-wip-us.apache.org/repos/asf/helix/commit/3c3f289b
Tree: http://git-wip-us.apache.org/repos/asf/helix/tree/3c3f289b
Diff: http://git-wip-us.apache.org/repos/asf/helix/diff/3c3f289b

Branch: refs/heads/master
Commit: 3c3f289be18e5ea585170937be8507c6ada22fd5
Parents: 12f24c5
Author: Vivo Xu <vx...@linkedin.com>
Authored: Wed Feb 21 13:55:42 2018 -0800
Committer: Vivo Xu <vx...@linkedin.com>
Committed: Wed Aug 8 15:31:47 2018 -0700

----------------------------------------------------------------------
 .../resource-list/resource-list.component.ts    | 25 +++++++-------------
 1 file changed, 8 insertions(+), 17 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/helix/blob/3c3f289b/helix-front/client/app/resource/resource-list/resource-list.component.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/resource/resource-list/resource-list.component.ts b/helix-front/client/app/resource/resource-list/resource-list.component.ts
index fb9db0a..a7be734 100644
--- a/helix-front/client/app/resource/resource-list/resource-list.component.ts
+++ b/helix-front/client/app/resource/resource-list/resource-list.component.ts
@@ -71,38 +71,29 @@ export class ResourceListComponent implements OnInit {
   // since resource list contains also workflows and jobs
   // need to subtract them from original resource list
   // to obtain all jobs list, need to go through every workflow
-  // and perform get request for each
+  // and perform get request for each.
+  // However, it has huge performance issue when there are thousands of
+  // workflows. We are using a smart way here: to remove resources whose
+  // prefix is a workflow name
   protected fetchResources() {
-    let jobs = [];
-
     this.isLoading = true;
     this.resources = null;
 
     this.workflowService
       .getAll(this.clusterName)
-      .concatMap(workflows => Observable.from(workflows))
-      .mergeMap(workflow => {
-        const name = workflow as string;
-        jobs.push(name);
-        return this.workflowService.get(this.clusterName, name);
-      })
-      .map(workflow => workflow.jobs)
       .subscribe(
-        list => {
-          jobs = jobs.concat(list);
-        },
-        error => this.helper.showError(error),
-        () => {
+        workflows => {
           this.service
             .getAll(this.clusterName)
             .subscribe(
               result => {
-                this.resources = _.differenceWith(result, jobs, (resource: Resource, name) => resource.name === name);
+                this.resources = _.differenceWith(result, workflows, (resource: Resource, prefix: string) => _.startsWith(resource.name, prefix));
               },
               error => this.helper.showError(error),
               () => this.isLoading = false
             );
-        }
+        },
+        error => this.helper.showError(error)
       );
   }
 


[04/14] helix git commit: Fix issue sometimes not able to delete map config items

Posted by jx...@apache.org.
Fix issue sometimes not able to delete map config items


Project: http://git-wip-us.apache.org/repos/asf/helix/repo
Commit: http://git-wip-us.apache.org/repos/asf/helix/commit/12f24c5c
Tree: http://git-wip-us.apache.org/repos/asf/helix/tree/12f24c5c
Diff: http://git-wip-us.apache.org/repos/asf/helix/diff/12f24c5c

Branch: refs/heads/master
Commit: 12f24c5cf388f28aabf6ed5bbf635639ae9d6741
Parents: 4739d4a
Author: Vivo Xu <vx...@linkedin.com>
Authored: Tue Feb 20 13:51:03 2018 -0800
Committer: Vivo Xu <vx...@linkedin.com>
Committed: Wed Aug 8 15:31:35 2018 -0700

----------------------------------------------------------------------
 helix-front/client/app/shared/node-viewer/node-viewer.component.ts | 2 ++
 1 file changed, 2 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/helix/blob/12f24c5c/helix-front/client/app/shared/node-viewer/node-viewer.component.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/shared/node-viewer/node-viewer.component.ts b/helix-front/client/app/shared/node-viewer/node-viewer.component.ts
index 316cbbf..1d52b99 100644
--- a/helix-front/client/app/shared/node-viewer/node-viewer.component.ts
+++ b/helix-front/client/app/shared/node-viewer/node-viewer.component.ts
@@ -284,6 +284,8 @@ export class NodeViewerComponent implements OnInit {
         if (key) {
           // have to fetch all other configs under this key
           const entry = _.find(this.node.mapFields, {'name': key});
+          newNode.mapFields =  [{ name: key, value: [] }];
+
           _.forEach(entry.value, (item: any) => {
             if (item.name === row.name) {
               if (!isDeleting) {


[08/14] helix git commit: remove tracking code from index.html

Posted by jx...@apache.org.
remove tracking code from index.html


Project: http://git-wip-us.apache.org/repos/asf/helix/repo
Commit: http://git-wip-us.apache.org/repos/asf/helix/commit/813b8c60
Tree: http://git-wip-us.apache.org/repos/asf/helix/tree/813b8c60
Diff: http://git-wip-us.apache.org/repos/asf/helix/diff/813b8c60

Branch: refs/heads/master
Commit: 813b8c60fe11d5afed62da0714e079311bac5114
Parents: a97f158
Author: Vivo Xu <vx...@linkedin.com>
Authored: Tue Mar 6 17:43:56 2018 -0800
Committer: Vivo Xu <vx...@linkedin.com>
Committed: Wed Aug 8 15:37:35 2018 -0700

----------------------------------------------------------------------
 helix-front/client/index.html | 15 ---------------
 1 file changed, 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/helix/blob/813b8c60/helix-front/client/index.html
----------------------------------------------------------------------
diff --git a/helix-front/client/index.html b/helix-front/client/index.html
index 5d1cfc6..459ed2f 100644
--- a/helix-front/client/index.html
+++ b/helix-front/client/index.html
@@ -9,20 +9,5 @@
 </head>
 <body>
   <hi-root>Loading Helix UI ...</hi-root>
-  <!-- Piwik -->
-  <script type="text/javascript">
-    var _paq = _paq || [];
-    _paq.push(['trackPageView']);
-    _paq.push(['enableLinkTracking']);
-    (function() {
-      var u='//piwik.corp.linkedin.com/piwik/';
-      _paq.push(['setTrackerUrl', u+'piwik.php']);
-      _paq.push(['setSiteId', '99']);
-      var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
-      g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
-    })();
-  </script>
-  <noscript><p><img src="//piwik.corp.linkedin.com/piwik/piwik.php?idsite=99" style="border:0;" alt="" /></p></noscript>
-  <!-- End Piwik Code -->
 </body>
 </html>


[06/14] helix git commit: Simple workflow pause/resume

Posted by jx...@apache.org.
Simple workflow pause/resume


Project: http://git-wip-us.apache.org/repos/asf/helix/repo
Commit: http://git-wip-us.apache.org/repos/asf/helix/commit/d192afcc
Tree: http://git-wip-us.apache.org/repos/asf/helix/tree/d192afcc
Diff: http://git-wip-us.apache.org/repos/asf/helix/diff/d192afcc

Branch: refs/heads/master
Commit: d192afccfd950092ab902d9be042aa8a73a4c80e
Parents: 3c3f289
Author: Vivo Xu <vx...@linkedin.com>
Authored: Wed Feb 28 13:46:55 2018 -0800
Committer: Vivo Xu <vx...@linkedin.com>
Committed: Wed Aug 8 15:32:21 2018 -0700

----------------------------------------------------------------------
 .../app/workflow/shared/workflow.service.ts     | 10 ++++++
 .../workflow-detail.component.html              | 14 ++++++++
 .../workflow-detail.component.ts                | 38 +++++++++++++++++---
 3 files changed, 57 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/helix/blob/d192afcc/helix-front/client/app/workflow/shared/workflow.service.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/shared/workflow.service.ts b/helix-front/client/app/workflow/shared/workflow.service.ts
index 9be5337..0602a15 100644
--- a/helix-front/client/app/workflow/shared/workflow.service.ts
+++ b/helix-front/client/app/workflow/shared/workflow.service.ts
@@ -17,4 +17,14 @@ export class WorkflowService extends HelixService {
       .request(`/clusters/${ clusterName }/workflows/${ workflowName }`)
       .map(data => new Workflow(data, clusterName));
   }
+
+  public stop(clusterName: string, workflowName: string) {
+    return this
+      .post(`/clusters/${ clusterName }/workflows/${ workflowName }?command=stop`, null);
+  }
+
+  public resume(clusterName: string, workflowName: string) {
+    return this
+      .post(`/clusters/${ clusterName }/workflows/${ workflowName }?command=resume`, null);
+  }
 }

http://git-wip-us.apache.org/repos/asf/helix/blob/d192afcc/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.html
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.html b/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.html
index 276e116..e6c816a 100644
--- a/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.html
+++ b/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.html
@@ -13,6 +13,20 @@
       <hi-key-value-pair name="Failure Threshold" prop="config.FailureThreshold"></hi-key-value-pair>
       <hi-key-value-pair name="Expiry" prop="config.Expiry"></hi-key-value-pair>
     </hi-key-value-pairs>
+    <span fxFlex="1 1 auto"></span>
+    <button mat-mini-fab *ngIf="can" [matMenuTriggerFor]="menu">
+      <mat-icon>menu</mat-icon>
+    </button>
+    <mat-menu #menu="matMenu">
+      <button mat-menu-item (click)="stopWorkflow()">
+        <mat-icon>pause_circle_outline</mat-icon>
+        <span>Pause this Workflow</span>
+      </button>
+      <button mat-menu-item (click)="resumeWorkflow()">
+        <mat-icon>play_circle_outline</mat-icon>
+        <span>Resume this workflow</span>
+      </button>
+    </mat-menu>
   </mat-toolbar-row>
 </mat-toolbar>
 <section fxLayout="column" fxLayoutAlign="center center">

http://git-wip-us.apache.org/repos/asf/helix/blob/d192afcc/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.ts b/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.ts
index a8adcfa..e90af41 100644
--- a/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.ts
+++ b/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.ts
@@ -15,6 +15,8 @@ export class WorkflowDetailComponent implements OnInit {
   isLoading = true;
   workflow: Workflow;
   clusterName: string;
+  workflowName: string;
+  can = false;
 
   constructor(
     protected route: ActivatedRoute,
@@ -24,12 +26,38 @@ export class WorkflowDetailComponent implements OnInit {
 
   ngOnInit() {
     this.clusterName = this.route.snapshot.params['cluster_name'];
+    this.workflowName = this.route.snapshot.params['workflow_name'];
 
-    this.service
-      .get(
-        this.route.snapshot.params['cluster_name'],
-        this.route.snapshot.params['workflow_name']
-      )
+    this.service.can().subscribe(data => this.can = data);
+
+    this.loadWorkflow();
+  }
+
+  stopWorkflow() {
+    this.service.stop(this.clusterName, this.workflowName)
+      .subscribe(
+        () => {
+          this.helper.showSnackBar('Pause command sent.');
+          this.loadWorkflow();
+        },
+        error => this.helper.showError(error)
+      );
+  }
+
+  resumeWorkflow() {
+    this.service.resume(this.clusterName, this.workflowName)
+      .subscribe(
+        () => {
+          this.helper.showSnackBar('Resume command sent.');
+          this.loadWorkflow();
+        },
+        error => this.helper.showError(error)
+      );
+  }
+
+  protected loadWorkflow() {
+    this.isLoading = true;
+    this.service.get(this.clusterName, this.workflowName)
       .subscribe(
         workflow => this.workflow = workflow,
         error => this.helper.showError(error),


[02/14] helix git commit: Support simple job detail view

Posted by jx...@apache.org.
Support simple job detail view


Project: http://git-wip-us.apache.org/repos/asf/helix/repo
Commit: http://git-wip-us.apache.org/repos/asf/helix/commit/9da75925
Tree: http://git-wip-us.apache.org/repos/asf/helix/tree/9da75925
Diff: http://git-wip-us.apache.org/repos/asf/helix/diff/9da75925

Branch: refs/heads/master
Commit: 9da759256b408dc18e8e9827f99545b252daac73
Parents: b486d78
Author: Vivo Xu <vx...@linkedin.com>
Authored: Fri Feb 2 16:42:14 2018 -0800
Committer: Vivo Xu <vx...@linkedin.com>
Committed: Wed Aug 8 15:30:45 2018 -0700

----------------------------------------------------------------------
 .../job-detail/job-detail.component.html        | 10 ++++
 .../job-detail/job-detail.component.scss        |  0
 .../job-detail/job-detail.component.spec.ts     | 43 ++++++++++++++
 .../workflow/job-detail/job-detail.component.ts | 33 +++++++++++
 .../workflow/job-list/job-list.component.html   | 62 ++++++++++++++++++++
 .../workflow/job-list/job-list.component.scss   |  0
 .../job-list/job-list.component.spec.ts         | 33 +++++++++++
 .../app/workflow/job-list/job-list.component.ts | 47 +++++++++++++++
 .../app/workflow/shared/job.service.spec.ts     | 18 ++++++
 .../client/app/workflow/shared/job.service.ts   | 17 ++++++
 .../app/workflow/shared/workflow.model.ts       | 17 +++++-
 .../workflow/shared/workflow.service.spec.ts    |  6 +-
 .../app/workflow/shared/workflow.service.ts     |  2 +-
 .../workflow-detail.component.html              | 49 +---------------
 .../workflow-detail.component.ts                | 36 ++----------
 .../client/app/workflow/workflow.module.ts      | 12 +++-
 helix-front/package.json                        |  2 +-
 17 files changed, 299 insertions(+), 88 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/helix/blob/9da75925/helix-front/client/app/workflow/job-detail/job-detail.component.html
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/job-detail/job-detail.component.html b/helix-front/client/app/workflow/job-detail/job-detail.component.html
new file mode 100644
index 0000000..3eb5416
--- /dev/null
+++ b/helix-front/client/app/workflow/job-detail/job-detail.component.html
@@ -0,0 +1,10 @@
+<mat-tab-group>
+  <mat-tab label="Job Config">
+    <mat-spinner *ngIf="isLoading"></mat-spinner>
+    <ngx-json-viewer [json]="job?.config"></ngx-json-viewer>
+  </mat-tab>
+  <mat-tab label="Job Context">
+    <mat-spinner *ngIf="isLoading"></mat-spinner>
+    <ngx-json-viewer [json]="job?.context"></ngx-json-viewer>
+  </mat-tab>
+</mat-tab-group>

http://git-wip-us.apache.org/repos/asf/helix/blob/9da75925/helix-front/client/app/workflow/job-detail/job-detail.component.scss
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/job-detail/job-detail.component.scss b/helix-front/client/app/workflow/job-detail/job-detail.component.scss
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/helix/blob/9da75925/helix-front/client/app/workflow/job-detail/job-detail.component.spec.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/job-detail/job-detail.component.spec.ts b/helix-front/client/app/workflow/job-detail/job-detail.component.spec.ts
new file mode 100644
index 0000000..b720650
--- /dev/null
+++ b/helix-front/client/app/workflow/job-detail/job-detail.component.spec.ts
@@ -0,0 +1,43 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+
+import { TestingModule } from '../../../testing/testing.module';
+import { JobDetailComponent } from './job-detail.component';
+import { JobService } from '../shared/job.service';
+
+describe('JobDetailComponent', () => {
+  let component: JobDetailComponent;
+  let fixture: ComponentFixture<JobDetailComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [ TestingModule ],
+      providers: [
+        {
+          provide: JobService,
+          useValue: {
+            get: job => Observable.of()
+          }
+        }
+      ],
+      declarations: [ JobDetailComponent ],
+      schemas: [
+        /* avoid importing modules */
+        NO_ERRORS_SCHEMA
+      ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(JobDetailComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

http://git-wip-us.apache.org/repos/asf/helix/blob/9da75925/helix-front/client/app/workflow/job-detail/job-detail.component.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/job-detail/job-detail.component.ts b/helix-front/client/app/workflow/job-detail/job-detail.component.ts
new file mode 100644
index 0000000..97bd890
--- /dev/null
+++ b/helix-front/client/app/workflow/job-detail/job-detail.component.ts
@@ -0,0 +1,33 @@
+import { Component, OnInit, Input } from '@angular/core';
+
+import { Job } from '../shared/workflow.model';
+import { JobService } from '../shared/job.service';
+import { HelperService } from '../../shared/helper.service';
+
+@Component({
+  selector: 'hi-job-detail',
+  templateUrl: './job-detail.component.html',
+  styleUrls: ['./job-detail.component.scss']
+})
+export class JobDetailComponent implements OnInit {
+
+  @Input()
+  job: Job;
+
+  isLoading = true;
+
+  constructor(
+    protected service: JobService,
+    protected helper: HelperService
+  ) { }
+
+  ngOnInit() {
+    this.service.get(this.job)
+      .subscribe(
+        data => this.isLoading = false,
+        error => this.helper.showError(error),
+        () => this.isLoading = false
+      )
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/helix/blob/9da75925/helix-front/client/app/workflow/job-list/job-list.component.html
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/job-list/job-list.component.html b/helix-front/client/app/workflow/job-list/job-list.component.html
new file mode 100644
index 0000000..2592c1f
--- /dev/null
+++ b/helix-front/client/app/workflow/job-list/job-list.component.html
@@ -0,0 +1,62 @@
+<ngx-datatable
+  #jobsTable
+  class="material"
+  [headerHeight]="headerHeight"
+  [rowHeight]="rowHeight"
+  columnMode="force"
+  [footerHeight]="rowHeight"
+  [rows]="jobs"
+  selectionType="single"
+  [sorts]="sorts"
+  (select)="onSelect($event)"
+  [messages]="messages"
+  fxFill>
+  <ngx-datatable-column
+   [width]="50"
+   [resizeable]="false"
+   [sortable]="false"
+   [draggable]="false"
+   [canAutoResize]="false">
+   <ng-template let-expanded="expanded" ngx-datatable-cell-template>
+     <mat-icon>{{ expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>
+   </ng-template>
+  </ngx-datatable-column>
+  <ngx-datatable-column
+    name="Start Time"
+    [width]="200"
+    [resizeable]="false"
+    [draggable]="false"
+    [canAutoResize]="false">
+    <ng-template let-value="value" ngx-datatable-cell-template>
+      <span *ngIf="value" [matTooltip]="value | date:'medium'">
+        {{ parseTime(value) }}
+      </span>
+      <span *ngIf="!value">-</span>
+    </ng-template>
+  </ngx-datatable-column>
+  <ngx-datatable-column name="Job Name" prop="name">
+    <ng-template let-row="row" let-value="value" ngx-datatable-cell-template>
+      <span [matTooltip]="row.rawName">
+        ...{{ value }}
+      </span>
+    </ng-template>
+  </ngx-datatable-column>
+  <ngx-datatable-column
+    name="State"
+    [width]="120"
+    [resizeable]="false"
+    [draggable]="false"
+    [canAutoResize]="false">
+    <ng-template let-value="value" ngx-datatable-cell-template>
+      <span *ngIf="value" class="state-default state-{{ value }}">
+        {{ value }}
+      </span>
+      <span *ngIf="!value" class="state-PENDING">PENDING</span>
+    </ng-template>
+  </ngx-datatable-column>
+  <ngx-datatable-row-detail rowHeight="auto">
+    <ng-template let-row="row" ngx-datatable-row-detail-template>
+      <hi-job-detail [job]="row"></hi-job-detail>
+    </ng-template>
+  </ngx-datatable-row-detail>
+</ngx-datatable>

http://git-wip-us.apache.org/repos/asf/helix/blob/9da75925/helix-front/client/app/workflow/job-list/job-list.component.scss
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/job-list/job-list.component.scss b/helix-front/client/app/workflow/job-list/job-list.component.scss
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/helix/blob/9da75925/helix-front/client/app/workflow/job-list/job-list.component.spec.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/job-list/job-list.component.spec.ts b/helix-front/client/app/workflow/job-list/job-list.component.spec.ts
new file mode 100644
index 0000000..d9f75ac
--- /dev/null
+++ b/helix-front/client/app/workflow/job-list/job-list.component.spec.ts
@@ -0,0 +1,33 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+
+import { TestingModule } from '../../../testing/testing.module';
+import { JobListComponent } from './job-list.component';
+
+describe('JobListComponent', () => {
+  let component: JobListComponent;
+  let fixture: ComponentFixture<JobListComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [ TestingModule ],
+      declarations: [ JobListComponent ],
+      schemas: [
+        /* avoid importing modules */
+        NO_ERRORS_SCHEMA
+      ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(JobListComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

http://git-wip-us.apache.org/repos/asf/helix/blob/9da75925/helix-front/client/app/workflow/job-list/job-list.component.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/job-list/job-list.component.ts b/helix-front/client/app/workflow/job-list/job-list.component.ts
new file mode 100644
index 0000000..ef85de8
--- /dev/null
+++ b/helix-front/client/app/workflow/job-list/job-list.component.ts
@@ -0,0 +1,47 @@
+import { Component, OnInit, Input, ViewChild } from '@angular/core';
+
+import * as moment from 'moment';
+
+import { Settings } from '../../core/settings';
+import { Job } from '../shared/workflow.model';
+
+@Component({
+  selector: 'hi-job-list',
+  templateUrl: './job-list.component.html',
+  styleUrls: ['./job-list.component.scss']
+})
+export class JobListComponent implements OnInit {
+
+  @Input()
+  jobs: Job[];
+
+  @ViewChild('jobsTable')
+  table: any;
+
+  rowHeight = Settings.tableRowHeight;
+  headerHeight = Settings.tableHeaderHeight;
+  sorts = [
+    { prop: 'startTime', dir: 'desc'},
+    { prop: 'name', dir: 'asc'}
+  ];
+  messages = {
+    emptyMessage: 'The list is empty.',
+    totalMessage: 'total',
+    selectedMessage: 'selected'
+  };
+
+  constructor() { }
+
+  ngOnInit() {
+  }
+
+  parseTime(rawTime: string): string {
+    return moment(parseInt(rawTime)).fromNow();
+  }
+
+  onSelect({ selected }) {
+    const row = selected[0];
+
+    this.table.rowDetail.toggleExpandRow(row);
+  }
+}

http://git-wip-us.apache.org/repos/asf/helix/blob/9da75925/helix-front/client/app/workflow/shared/job.service.spec.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/shared/job.service.spec.ts b/helix-front/client/app/workflow/shared/job.service.spec.ts
new file mode 100644
index 0000000..ecc42b6
--- /dev/null
+++ b/helix-front/client/app/workflow/shared/job.service.spec.ts
@@ -0,0 +1,18 @@
+import { TestBed, inject } from '@angular/core/testing';
+
+import { TestingModule } from '../../../testing/testing.module';
+
+import { JobService } from './job.service';
+
+describe('JobService', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      imports: [TestingModule],
+      providers: [JobService]
+    });
+  });
+
+  it('should be created', inject([JobService], (service: JobService) => {
+    expect(service).toBeTruthy();
+  }));
+});

http://git-wip-us.apache.org/repos/asf/helix/blob/9da75925/helix-front/client/app/workflow/shared/job.service.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/shared/job.service.ts b/helix-front/client/app/workflow/shared/job.service.ts
new file mode 100644
index 0000000..6f044c9
--- /dev/null
+++ b/helix-front/client/app/workflow/shared/job.service.ts
@@ -0,0 +1,17 @@
+import { Injectable } from '@angular/core';
+
+import { Job } from './workflow.model';
+import { HelixService } from '../../core/helix.service';
+
+@Injectable()
+export class JobService extends HelixService {
+
+  public get(job: Job) {
+    return this
+      .request(`/clusters/${ job.clusterName }/workflows/${ job.workflowName }/jobs/${ job.rawName }`)
+      .map(data => {
+        job.config = data.JobConfig;
+        job.context = data.JobContext;
+      });
+  }
+}

http://git-wip-us.apache.org/repos/asf/helix/blob/9da75925/helix-front/client/app/workflow/shared/workflow.model.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/shared/workflow.model.ts b/helix-front/client/app/workflow/shared/workflow.model.ts
index 363c8a7..4a1e42f 100644
--- a/helix-front/client/app/workflow/shared/workflow.model.ts
+++ b/helix-front/client/app/workflow/shared/workflow.model.ts
@@ -11,9 +11,17 @@ export class Job {
   readonly state: string;
   readonly parents: string[];
 
+  readonly workflowName: string;
+  readonly clusterName: string;
+
+  // will load later
+  config: any;
+  context: any;
+
   constructor(
     rawName: string,
     workflowName: string,
+    clusterName: string,
     startTime: string,
     state: string,
     parents: string[]
@@ -21,6 +29,8 @@ export class Job {
     this.rawName = rawName;
     // try to reduce the name
     this.name = _.replace(rawName, workflowName + '_', '');
+    this.workflowName = workflowName;
+    this.clusterName = clusterName;
     this.startTime = startTime;
     this.state = state;
     // try to reduce parent names
@@ -30,22 +40,24 @@ export class Job {
 
 export class Workflow {
   readonly name: string;
+  readonly clusterName: string;
   readonly config: any;
   readonly jobs: Job[];
   readonly context: any;
   readonly json: any;
 
   get isJobQueue(): boolean {
-    return this.config && this.config.IsJobQueue.toLowerCase() == 'true';
+    return this.config && this.config.IsJobQueue && this.config.IsJobQueue.toLowerCase() == 'true';
   }
 
   get state(): string {
     return this.context.STATE || 'NOT STARTED';
   }
 
-  constructor(obj: any) {
+  constructor(obj: any, clusterName: string) {
     this.json = obj;
     this.name = obj.id;
+    this.clusterName = clusterName;
     this.config = obj.WorkflowConfig;
     this.context = obj.WorkflowContext;
     this.jobs = this.parseJobs(obj.Jobs, obj.ParentJobs);
@@ -58,6 +70,7 @@ export class Workflow {
       result.push(new Job(
         jobName,
         this.name,
+        this.clusterName,
         _.get(this.context, ['StartTime', jobName]),
         _.get(this.context, ['JOB_STATES', jobName]),
         parents[jobName]

http://git-wip-us.apache.org/repos/asf/helix/blob/9da75925/helix-front/client/app/workflow/shared/workflow.service.spec.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/shared/workflow.service.spec.ts b/helix-front/client/app/workflow/shared/workflow.service.spec.ts
index eed01ca..e065079 100644
--- a/helix-front/client/app/workflow/shared/workflow.service.spec.ts
+++ b/helix-front/client/app/workflow/shared/workflow.service.spec.ts
@@ -1,13 +1,13 @@
 import { TestBed, inject } from '@angular/core/testing';
-import { HttpModule } from '@angular/http';
-import { RouterTestingModule } from '@angular/router/testing';
+
+import { TestingModule } from '../../../testing/testing.module';
 
 import { WorkflowService } from './workflow.service';
 
 describe('WorkflowService', () => {
   beforeEach(() => {
     TestBed.configureTestingModule({
-      imports: [HttpModule, RouterTestingModule],
+      imports: [TestingModule],
       providers: [WorkflowService]
     });
   });

http://git-wip-us.apache.org/repos/asf/helix/blob/9da75925/helix-front/client/app/workflow/shared/workflow.service.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/shared/workflow.service.ts b/helix-front/client/app/workflow/shared/workflow.service.ts
index 38cec74..9be5337 100644
--- a/helix-front/client/app/workflow/shared/workflow.service.ts
+++ b/helix-front/client/app/workflow/shared/workflow.service.ts
@@ -15,6 +15,6 @@ export class WorkflowService extends HelixService {
   public get(clusterName: string, workflowName: string) {
     return this
       .request(`/clusters/${ clusterName }/workflows/${ workflowName }`)
-      .map(data => new Workflow(data));
+      .map(data => new Workflow(data, clusterName));
   }
 }

http://git-wip-us.apache.org/repos/asf/helix/blob/9da75925/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.html
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.html b/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.html
index 5a940d3..276e116 100644
--- a/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.html
+++ b/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.html
@@ -31,54 +31,7 @@
     </mat-button-toggle-group>
     <section class="viewer" [ngSwitch]="group.value" fxLayout="column" fxLayoutAlign="center center" fxFill>
       <hi-workflow-dag *ngSwitchCase="'graph'" [workflow]="workflow"></hi-workflow-dag>
-      <ngx-datatable
-        *ngSwitchCase="'list'"
-        #jobsTable
-        class="material"
-        [headerHeight]="headerHeight"
-        [rowHeight]="rowHeight"
-        columnMode="force"
-        [footerHeight]="rowHeight"
-        [rows]="workflow.jobs"
-        selectionType="single"
-        [sorts]="sorts"
-        (select)="onSelect($event)"
-        [messages]="messages"
-        fxFill>
-        <ngx-datatable-column
-          name="Start Time"
-          [width]="200"
-          [resizeable]="false"
-          [draggable]="false"
-          [canAutoResize]="false">
-          <ng-template let-value="value" ngx-datatable-cell-template>
-            <span *ngIf="value" [matTooltip]="value | date:'medium'">
-              {{ parseTime(value) }}
-            </span>
-            <span *ngIf="!value">-</span>
-          </ng-template>
-        </ngx-datatable-column>
-        <ngx-datatable-column name="Job Name" prop="name">
-          <ng-template let-row="row" let-value="value" ngx-datatable-cell-template>
-            <span [matTooltip]="row.rawName">
-              ...{{ value }}
-            </span>
-          </ng-template>
-        </ngx-datatable-column>
-        <ngx-datatable-column
-          name="State"
-          [width]="120"
-          [resizeable]="false"
-          [draggable]="false"
-          [canAutoResize]="false">
-          <ng-template let-value="value" ngx-datatable-cell-template>
-            <span *ngIf="value" class="state-default state-{{ value }}">
-              {{ value }}
-            </span>
-            <span *ngIf="!value" class="state-PENDING">PENDING</span>
-          </ng-template>
-        </ngx-datatable-column>
-      </ngx-datatable>
+      <hi-job-list *ngSwitchCase="'list'" [jobs]="workflow.jobs" fxFill></hi-job-list>
       <ngx-json-viewer *ngSwitchCase="'json'" [json]="workflow.json" fxFill></ngx-json-viewer>
     </section>
   </section>

http://git-wip-us.apache.org/repos/asf/helix/blob/9da75925/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.ts b/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.ts
index f1f04ad..a8adcfa 100644
--- a/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.ts
+++ b/helix-front/client/app/workflow/workflow-detail/workflow-detail.component.ts
@@ -1,13 +1,9 @@
 import { Component, OnInit } from '@angular/core';
 import { ActivatedRoute } from '@angular/router';
 
-import * as moment from 'moment';
-import * as shape from 'd3-shape';
-import * as _ from 'lodash';
-
-import { Settings } from '../../core/settings';
-import { Workflow, Job } from '../shared/workflow.model';
+import { Workflow } from '../shared/workflow.model';
 import { WorkflowService } from '../shared/workflow.service';
+import { HelperService } from '../../shared/helper.service';
 
 @Component({
   selector: 'hi-workflow-detail',
@@ -20,21 +16,10 @@ export class WorkflowDetailComponent implements OnInit {
   workflow: Workflow;
   clusterName: string;
 
-  rowHeight = Settings.tableRowHeight;
-  headerHeight = Settings.tableHeaderHeight;
-  sorts = [
-    { prop: 'startTime', dir: 'desc'},
-    { prop: 'name', dir: 'asc'}
-  ];
-  messages = {
-    emptyMessage: 'The list is empty.',
-    totalMessage: 'total',
-    selectedMessage: 'selected'
-  };
-
   constructor(
-    private route: ActivatedRoute,
-    private service: WorkflowService
+    protected route: ActivatedRoute,
+    protected service: WorkflowService,
+    protected helper: HelperService
   ) { }
 
   ngOnInit() {
@@ -47,17 +32,8 @@ export class WorkflowDetailComponent implements OnInit {
       )
       .subscribe(
         workflow => this.workflow = workflow,
-        error => console.log(error),
+        error => this.helper.showError(error),
         () => this.isLoading = false
       );
   }
-
-  parseTime(rawTime: string): string {
-    return moment(parseInt(rawTime)).fromNow();
-  }
-
-  onSelect({ selected }) {
-    const row = selected[0];
-    // this.table.rowDetail.toggleExpandRow(row);
-  }
 }

http://git-wip-us.apache.org/repos/asf/helix/blob/9da75925/helix-front/client/app/workflow/workflow.module.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/workflow/workflow.module.ts b/helix-front/client/app/workflow/workflow.module.ts
index 7ff9868..73f3698 100644
--- a/helix-front/client/app/workflow/workflow.module.ts
+++ b/helix-front/client/app/workflow/workflow.module.ts
@@ -3,13 +3,16 @@ import { CommonModule } from '@angular/common';
 
 import { NgxDatatableModule } from '@swimlane/ngx-datatable';
 import { NgxChartsModule } from '@swimlane/ngx-charts';
-import { NgxGraphModule } from '@swimlane/ngx-graph';
+import { NgxGraphModule } from 'ngx-dag';
 
 import { WorkflowListComponent } from './workflow-list/workflow-list.component';
 import { WorkflowService } from './shared/workflow.service';
+import { JobService } from './shared/job.service';
 import { SharedModule } from '../shared/shared.module';
 import { WorkflowDetailComponent } from './workflow-detail/workflow-detail.component';
 import { WorkflowDagComponent } from './workflow-dag/workflow-dag.component';
+import { JobListComponent } from './job-list/job-list.component';
+import { JobDetailComponent } from './job-detail/job-detail.component';
 
 @NgModule({
   imports: [
@@ -20,12 +23,15 @@ import { WorkflowDagComponent } from './workflow-dag/workflow-dag.component';
     NgxGraphModule
   ],
   providers: [
-    WorkflowService
+    WorkflowService,
+    JobService
   ],
   declarations: [
     WorkflowListComponent,
     WorkflowDetailComponent,
-    WorkflowDagComponent
+    WorkflowDagComponent,
+    JobListComponent,
+    JobDetailComponent
   ]
 })
 export class WorkflowModule { }

http://git-wip-us.apache.org/repos/asf/helix/blob/9da75925/helix-front/package.json
----------------------------------------------------------------------
diff --git a/helix-front/package.json b/helix-front/package.json
index 8c8d1b4..e611637 100644
--- a/helix-front/package.json
+++ b/helix-front/package.json
@@ -35,7 +35,6 @@
     "@angular/router": "^5.1.1",
     "@swimlane/ngx-charts": "^7.0.1",
     "@swimlane/ngx-datatable": "^11.1.7",
-    "@swimlane/ngx-graph": "^4.0.2",
     "angulartics2": "^2.2.1",
     "body-parser": "^1.17.2",
     "core-js": "^2.4.1",
@@ -48,6 +47,7 @@
     "moment": "^2.22.2",
     "morgan": "^1.8.2",
     "ngx-clipboard": "^9.0.0",
+    "ngx-dag": "0.0.2",
     "ngx-json-viewer": "^2.3.0",
     "node-sass": "4.5.3",
     "request": "^2.81.0",


[09/14] helix git commit: add proper prompt after cluster creation

Posted by jx...@apache.org.
add proper prompt after cluster creation


Project: http://git-wip-us.apache.org/repos/asf/helix/repo
Commit: http://git-wip-us.apache.org/repos/asf/helix/commit/e94b82ab
Tree: http://git-wip-us.apache.org/repos/asf/helix/tree/e94b82ab
Diff: http://git-wip-us.apache.org/repos/asf/helix/diff/e94b82ab

Branch: refs/heads/master
Commit: e94b82abbe54036ab6911ae9dda74e6466f96eae
Parents: 813b8c6
Author: Vivo Xu <vx...@linkedin.com>
Authored: Fri Mar 9 14:23:56 2018 -0800
Committer: Vivo Xu <vx...@linkedin.com>
Committed: Wed Aug 8 15:38:01 2018 -0700

----------------------------------------------------------------------
 .../cluster-detail.component.html               |  2 +-
 .../cluster-detail.component.spec.ts            |  6 ++----
 .../cluster-detail/cluster-detail.component.ts  | 21 +++++---------------
 .../cluster-list/cluster-list.component.html    |  2 +-
 .../cluster-list/cluster-list.component.spec.ts |  4 ++--
 .../cluster-list/cluster-list.component.ts      | 14 ++++++++++++-
 .../instance-detail.component.spec.ts           |  6 ++----
 7 files changed, 26 insertions(+), 29 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/helix/blob/e94b82ab/helix-front/client/app/cluster/cluster-detail/cluster-detail.component.html
----------------------------------------------------------------------
diff --git a/helix-front/client/app/cluster/cluster-detail/cluster-detail.component.html b/helix-front/client/app/cluster/cluster-detail/cluster-detail.component.html
index 5b8a78c..e0fe3ac 100644
--- a/helix-front/client/app/cluster/cluster-detail/cluster-detail.component.html
+++ b/helix-front/client/app/cluster/cluster-detail/cluster-detail.component.html
@@ -34,7 +34,7 @@
           <mat-icon>add_circle</mat-icon>
           <span>Add an Instance</span>
         </button>
-        <button mat-menu-item *ngIf="false" (click)="deleteCluster()" disabled>
+        <button mat-menu-item *ngIf="false" (click)="deleteCluster()">
           <mat-icon>delete</mat-icon>
           <span>DELETE this Cluster</span>
         </button>

http://git-wip-us.apache.org/repos/asf/helix/blob/e94b82ab/helix-front/client/app/cluster/cluster-detail/cluster-detail.component.spec.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/cluster/cluster-detail/cluster-detail.component.spec.ts b/helix-front/client/app/cluster/cluster-detail/cluster-detail.component.spec.ts
index f55d259..458620a 100644
--- a/helix-front/client/app/cluster/cluster-detail/cluster-detail.component.spec.ts
+++ b/helix-front/client/app/cluster/cluster-detail/cluster-detail.component.spec.ts
@@ -1,8 +1,7 @@
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 import { NO_ERRORS_SCHEMA } from '@angular/core';
-import { HttpModule } from '@angular/http';
-import { RouterTestingModule } from '@angular/router/testing';
 
+import { TestingModule } from '../../../testing/testing.module';
 import { ClusterDetailComponent } from './cluster-detail.component';
 
 describe('ClusterDetailComponent', () => {
@@ -12,8 +11,7 @@ describe('ClusterDetailComponent', () => {
   beforeEach(async(() => {
     TestBed.configureTestingModule({
       imports: [
-        HttpModule,
-        RouterTestingModule
+        TestingModule
       ],
       declarations: [
         ClusterDetailComponent

http://git-wip-us.apache.org/repos/asf/helix/blob/e94b82ab/helix-front/client/app/cluster/cluster-detail/cluster-detail.component.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/cluster/cluster-detail/cluster-detail.component.ts b/helix-front/client/app/cluster/cluster-detail/cluster-detail.component.ts
index d2a3bb3..0fbc0c1 100644
--- a/helix-front/client/app/cluster/cluster-detail/cluster-detail.component.ts
+++ b/helix-front/client/app/cluster/cluster-detail/cluster-detail.component.ts
@@ -157,30 +157,19 @@ export class ClusterDetailComponent implements OnInit {
   }
 
   deleteCluster() {
-    // disable delete function right now since it's too dangerous
-    /*
-    this.dialog
-      .open(ConfirmDialogComponent, {
-        data: {
-          title: 'Confirmation',
-          message: 'Are you sure you want to delete this cluster?'
-        }
-      })
-      .afterClosed()
-      .subscribe(result => {
+    this.helperService
+      .showConfirmation('Are you sure you want to delete this cluster?')
+      .then(result => {
         if (result) {
           this.clusterService
             .remove(this.cluster.name)
             .subscribe(data => {
-              this.snackBar.open('Cluster deleted!', 'OK', {
-                duration: 2000,
-              });
+              this.helperService.showSnackBar('Cluster deleted!');
               // FIXME: should reload cluster list as well
               this.router.navigate(['..'], { relativeTo: this.route });
             });
-        }
+          }
       });
-      */
   }
 
 }

http://git-wip-us.apache.org/repos/asf/helix/blob/e94b82ab/helix-front/client/app/cluster/cluster-list/cluster-list.component.html
----------------------------------------------------------------------
diff --git a/helix-front/client/app/cluster/cluster-list/cluster-list.component.html b/helix-front/client/app/cluster/cluster-list/cluster-list.component.html
index c32b1b8..dad205d 100644
--- a/helix-front/client/app/cluster/cluster-list/cluster-list.component.html
+++ b/helix-front/client/app/cluster/cluster-list/cluster-list.component.html
@@ -11,7 +11,7 @@
     <button mat-mini-fab *ngIf="can" (click)="createCluster()">
       <mat-icon>add</mat-icon>
     </button>
-    <h3 mat-subheader>Clusters ({{ clusters.length }})</h3>
+    <h3 mat-subheader>Clusters in {{ service }} ({{ clusters.length }})</h3>
     <a *ngFor="let cluster of clusters"
       mat-list-item
       [routerLink]="[cluster.name]"

http://git-wip-us.apache.org/repos/asf/helix/blob/e94b82ab/helix-front/client/app/cluster/cluster-list/cluster-list.component.spec.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/cluster/cluster-list/cluster-list.component.spec.ts b/helix-front/client/app/cluster/cluster-list/cluster-list.component.spec.ts
index 9c46a86..ab81aca 100644
--- a/helix-front/client/app/cluster/cluster-list/cluster-list.component.spec.ts
+++ b/helix-front/client/app/cluster/cluster-list/cluster-list.component.spec.ts
@@ -1,7 +1,7 @@
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 import { NO_ERRORS_SCHEMA } from '@angular/core';
-import { HttpModule } from '@angular/http';
 
+import { TestingModule } from '../../../testing/testing.module';
 import { ClusterListComponent } from './cluster-list.component';
 
 describe('ClusterListComponent', () => {
@@ -11,7 +11,7 @@ describe('ClusterListComponent', () => {
   beforeEach(async(() => {
     TestBed.configureTestingModule({
       imports: [
-        HttpModule
+        TestingModule
       ],
       declarations: [
         ClusterListComponent

http://git-wip-us.apache.org/repos/asf/helix/blob/e94b82ab/helix-front/client/app/cluster/cluster-list/cluster-list.component.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/cluster/cluster-list/cluster-list.component.ts b/helix-front/client/app/cluster/cluster-list/cluster-list.component.ts
index 35d0b92..2e1cb06 100644
--- a/helix-front/client/app/cluster/cluster-list/cluster-list.component.ts
+++ b/helix-front/client/app/cluster/cluster-list/cluster-list.component.ts
@@ -1,5 +1,6 @@
 import { Component, OnInit } from '@angular/core';
 import { MatDialog, MatSnackBar } from '@angular/material';
+import { Router } from '@angular/router';
 
 import { ClusterService } from '../shared/cluster.service';
 import { Cluster } from '../shared/cluster.model';
@@ -17,16 +18,19 @@ export class ClusterListComponent implements OnInit {
   errorMessage: string = '';
   isLoading: boolean = true;
   can: boolean = false;
+  service = '';
 
   constructor(
     protected clusterService: ClusterService,
     protected dialog: MatDialog,
-    protected snackBar: MatSnackBar
+    protected snackBar: MatSnackBar,
+    protected router: Router
   ) { }
 
   ngOnInit() {
     this.loadClusters();
     this.clusterService.can().subscribe(data => this.can = data);
+    this.service = this.router.url.split('/')[1];
   }
 
   loadClusters() {
@@ -71,6 +75,14 @@ export class ClusterListComponent implements OnInit {
             this.snackBar.open('Cluster created!', 'OK', {
               duration: 2000,
             });
+            this.dialog.open(AlertDialogComponent, {
+              width: '600px',
+              data: {
+                title: 'What\'s Next',
+                message: 'New cluster is created yet not activated. When you are ready to activate this cluster,'
+                  + ' please select "Activate this Cluster" menu in the cluster operations to continue.'
+              }
+            });
             this.loadClusters();
           });
       }

http://git-wip-us.apache.org/repos/asf/helix/blob/e94b82ab/helix-front/client/app/instance/instance-detail/instance-detail.component.spec.ts
----------------------------------------------------------------------
diff --git a/helix-front/client/app/instance/instance-detail/instance-detail.component.spec.ts b/helix-front/client/app/instance/instance-detail/instance-detail.component.spec.ts
index f170b71..229e9d4 100644
--- a/helix-front/client/app/instance/instance-detail/instance-detail.component.spec.ts
+++ b/helix-front/client/app/instance/instance-detail/instance-detail.component.spec.ts
@@ -1,8 +1,7 @@
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 import { NO_ERRORS_SCHEMA } from '@angular/core';
-import { HttpModule } from '@angular/http';
-import { RouterTestingModule } from '@angular/router/testing';
 
+import { TestingModule } from '../../../testing/testing.module';
 import { InstanceDetailComponent } from './instance-detail.component';
 
 describe('InstanceDetailComponent', () => {
@@ -12,8 +11,7 @@ describe('InstanceDetailComponent', () => {
   beforeEach(async(() => {
     TestBed.configureTestingModule({
       imports: [
-        HttpModule,
-        RouterTestingModule
+        TestingModule
       ],
       declarations: [ InstanceDetailComponent ],
       schemas: [


[13/14] helix git commit: Skip admin check if called authorize

Posted by jx...@apache.org.
http://git-wip-us.apache.org/repos/asf/helix/blob/f1c50371/helix-front/package-lock.json
----------------------------------------------------------------------
diff --git a/helix-front/package-lock.json b/helix-front/package-lock.json
index 1123510..6398e0c 100644
--- a/helix-front/package-lock.json
+++ b/helix-front/package-lock.json
@@ -10,10 +10,10 @@
       "integrity": "sha512-EFFF7hBbVoTOzYfXuSlGhcDr8neafmwuBAIkzAekEjzik7OaTLq7LPG7As+ebed9ll+3DAGypnrpdIE1Tp/H/A==",
       "dev": true,
       "requires": {
-        "loader-utils": "1.1.0",
-        "source-map": "0.5.7",
-        "typescript": "2.6.2",
-        "webpack-sources": "1.1.0"
+        "loader-utils": "^1.1.0",
+        "source-map": "^0.5.6",
+        "typescript": "~2.6.1",
+        "webpack-sources": "^1.0.1"
       },
       "dependencies": {
         "source-map": {
@@ -36,7 +36,7 @@
       "integrity": "sha512-zxrNtTiv60liye/GGeRMnnGgLgAWoqlMTfPLMW0D1qJ4bbrPHtme010mpxS3QL4edcDtQseyXSFCnEkuo2MrRw==",
       "dev": true,
       "requires": {
-        "source-map": "0.5.7"
+        "source-map": "^0.5.6"
       },
       "dependencies": {
         "source-map": {
@@ -54,10 +54,10 @@
       "dev": true,
       "requires": {
         "@angular-devkit/core": "0.0.22",
-        "@ngtools/json-schema": "1.1.0",
+        "@ngtools/json-schema": "^1.1.0",
         "@schematics/schematics": "0.0.11",
-        "minimist": "1.2.0",
-        "rxjs": "5.5.5"
+        "minimist": "^1.2.0",
+        "rxjs": "^5.5.2"
       }
     },
     "@angular/animations": {
@@ -65,7 +65,7 @@
       "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-5.1.1.tgz",
       "integrity": "sha512-PHLBWDnAzr5b5l52pk5ZYmv/6m0YUe2ICwu5dmbS0d8Kf5dXadMphAWCDbljMF+djGyZeFq2/dQ/t7ygYl3YuA==",
       "requires": {
-        "tslib": "1.8.1"
+        "tslib": "^1.7.1"
       }
     },
     "@angular/cdk": {
@@ -73,7 +73,7 @@
       "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-5.0.1.tgz",
       "integrity": "sha512-uK4Vyaf06J8KqePzq35BxMHRGolt35EnbZf9wjCs7eYaghbQ7Pk2xUGoynu5Lj1wAOn5N1/C1nT2/aAH/EE2rw==",
       "requires": {
-        "tslib": "1.8.1"
+        "tslib": "^1.7.1"
       }
     },
     "@angular/cli": {
@@ -82,64 +82,64 @@
       "integrity": "sha512-cdXfUR1by7nCgg9H9HijV8aI36AmceMubBZ/k+UgICMd8DlkPCVBIWcUXLdoH/l6nuXb3gCucfJoThznlakNMw==",
       "dev": true,
       "requires": {
-        "@angular-devkit/build-optimizer": "0.0.36",
-        "@angular-devkit/schematics": "0.0.42",
+        "@angular-devkit/build-optimizer": "~0.0.36",
+        "@angular-devkit/schematics": "~0.0.42",
         "@ngtools/json-schema": "1.1.0",
         "@ngtools/webpack": "1.9.1",
-        "@schematics/angular": "0.1.11",
-        "autoprefixer": "6.7.7",
-        "chalk": "2.2.2",
-        "circular-dependency-plugin": "4.3.0",
-        "common-tags": "1.5.1",
-        "copy-webpack-plugin": "4.3.0",
-        "core-object": "3.1.5",
-        "css-loader": "0.28.7",
-        "cssnano": "3.10.0",
-        "denodeify": "1.2.1",
-        "ember-cli-string-utils": "1.1.0",
-        "exports-loader": "0.6.4",
-        "extract-text-webpack-plugin": "3.0.2",
-        "file-loader": "1.1.5",
-        "fs-extra": "4.0.3",
-        "glob": "7.1.2",
-        "html-webpack-plugin": "2.30.1",
-        "istanbul-instrumenter-loader": "2.0.0",
-        "karma-source-map-support": "1.2.0",
-        "less": "2.7.3",
-        "less-loader": "4.0.5",
-        "license-webpack-plugin": "1.1.1",
+        "@schematics/angular": "~0.1.11",
+        "autoprefixer": "^6.5.3",
+        "chalk": "~2.2.0",
+        "circular-dependency-plugin": "^4.2.1",
+        "common-tags": "^1.3.1",
+        "copy-webpack-plugin": "^4.1.1",
+        "core-object": "^3.1.0",
+        "css-loader": "^0.28.1",
+        "cssnano": "^3.10.0",
+        "denodeify": "^1.2.1",
+        "ember-cli-string-utils": "^1.0.0",
+        "exports-loader": "^0.6.3",
+        "extract-text-webpack-plugin": "^3.0.2",
+        "file-loader": "^1.1.5",
+        "fs-extra": "^4.0.0",
+        "glob": "^7.0.3",
+        "html-webpack-plugin": "^2.29.0",
+        "istanbul-instrumenter-loader": "^2.0.0",
+        "karma-source-map-support": "^1.2.0",
+        "less": "^2.7.2",
+        "less-loader": "^4.0.5",
+        "license-webpack-plugin": "^1.0.0",
         "loader-utils": "1.1.0",
-        "lodash": "4.17.4",
-        "memory-fs": "0.4.1",
-        "minimatch": "3.0.4",
-        "node-modules-path": "1.0.1",
-        "node-sass": "4.5.3",
-        "nopt": "4.0.1",
-        "opn": "5.1.0",
-        "portfinder": "1.0.13",
-        "postcss-custom-properties": "6.2.0",
-        "postcss-loader": "2.0.9",
-        "postcss-url": "7.3.0",
-        "raw-loader": "0.5.1",
-        "resolve": "1.5.0",
-        "rxjs": "5.5.5",
-        "sass-loader": "6.0.6",
-        "semver": "5.4.1",
-        "silent-error": "1.1.0",
-        "source-map-loader": "0.2.3",
-        "source-map-support": "0.4.18",
-        "style-loader": "0.13.2",
-        "stylus": "0.54.5",
-        "stylus-loader": "3.0.1",
-        "uglifyjs-webpack-plugin": "1.1.4",
-        "url-loader": "0.6.2",
-        "webpack": "3.10.0",
-        "webpack-dev-middleware": "1.12.2",
-        "webpack-dev-server": "2.9.7",
-        "webpack-merge": "4.1.1",
-        "webpack-sources": "1.1.0",
-        "webpack-subresource-integrity": "1.0.3",
-        "zone.js": "0.8.18"
+        "lodash": "^4.11.1",
+        "memory-fs": "^0.4.1",
+        "minimatch": "^3.0.4",
+        "node-modules-path": "^1.0.0",
+        "node-sass": "^4.3.0",
+        "nopt": "^4.0.1",
+        "opn": "~5.1.0",
+        "portfinder": "~1.0.12",
+        "postcss-custom-properties": "^6.1.0",
+        "postcss-loader": "^2.0.8",
+        "postcss-url": "^7.1.2",
+        "raw-loader": "^0.5.1",
+        "resolve": "^1.1.7",
+        "rxjs": "^5.5.2",
+        "sass-loader": "^6.0.3",
+        "semver": "^5.1.0",
+        "silent-error": "^1.0.0",
+        "source-map-loader": "^0.2.0",
+        "source-map-support": "^0.4.1",
+        "style-loader": "^0.13.1",
+        "stylus": "^0.54.5",
+        "stylus-loader": "^3.0.1",
+        "uglifyjs-webpack-plugin": "~1.1.2",
+        "url-loader": "^0.6.2",
+        "webpack": "~3.10.0",
+        "webpack-dev-middleware": "~1.12.0",
+        "webpack-dev-server": "~2.9.3",
+        "webpack-merge": "^4.1.0",
+        "webpack-sources": "^1.0.0",
+        "webpack-subresource-integrity": "^1.0.1",
+        "zone.js": "^0.8.14"
       },
       "dependencies": {
         "ansi-styles": {
@@ -148,7 +148,7 @@
           "integrity": "sha1-wVm41b4PnlpvNG2rlPFs4CIWG4g=",
           "dev": true,
           "requires": {
-            "color-convert": "1.9.1"
+            "color-convert": "^1.9.0"
           }
         },
         "chalk": {
@@ -157,9 +157,9 @@
           "integrity": "sha1-RAP1zxjzXAX1H73xUr9Yj5Vs98s=",
           "dev": true,
           "requires": {
-            "ansi-styles": "3.2.0",
-            "escape-string-regexp": "1.0.5",
-            "supports-color": "4.5.0"
+            "ansi-styles": "^3.1.0",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^4.0.0"
           }
         },
         "nopt": {
@@ -168,8 +168,8 @@
           "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
           "dev": true,
           "requires": {
-            "abbrev": "1.1.1",
-            "osenv": "0.1.4"
+            "abbrev": "1",
+            "osenv": "^0.1.4"
           }
         },
         "supports-color": {
@@ -178,7 +178,7 @@
           "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
           "dev": true,
           "requires": {
-            "has-flag": "2.0.0"
+            "has-flag": "^2.0.0"
           }
         }
       }
@@ -188,7 +188,7 @@
       "resolved": "https://registry.npmjs.org/@angular/common/-/common-5.1.1.tgz",
       "integrity": "sha512-SFRzdDthoiKaMLuV+TAwjKXFWwTRFGuidlWC3BhUf8/HzNSePAdvfdQcqbEaE5buMn403OV105S9Tyx5tILQeA==",
       "requires": {
-        "tslib": "1.8.1"
+        "tslib": "^1.7.1"
       }
     },
     "@angular/compiler": {
@@ -196,7 +196,7 @@
       "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-5.1.1.tgz",
       "integrity": "sha512-k4J2kRiBjtjkDcDut2JVUpqQGLJWd8j3Don+swzZHuEklbLmsVRGM6u/fmH0K9TMwKHtC5Ycap8kj4bWXUYfwg==",
       "requires": {
-        "tslib": "1.8.1"
+        "tslib": "^1.7.1"
       }
     },
     "@angular/compiler-cli": {
@@ -205,10 +205,10 @@
       "integrity": "sha512-X3n1V0fAsZzJDRLM2OPiOri8rrQ2ILFS0VDqPdHMa1HbpF0ZKe1Yyux2rhGSbS83a1Eanx6RqfDkrUalKEprbw==",
       "dev": true,
       "requires": {
-        "chokidar": "1.7.0",
-        "minimist": "1.2.0",
-        "reflect-metadata": "0.1.10",
-        "tsickle": "0.25.5"
+        "chokidar": "^1.4.2",
+        "minimist": "^1.2.0",
+        "reflect-metadata": "^0.1.2",
+        "tsickle": "^0.25.5"
       }
     },
     "@angular/core": {
@@ -216,7 +216,7 @@
       "resolved": "https://registry.npmjs.org/@angular/core/-/core-5.1.1.tgz",
       "integrity": "sha512-8HJ0lNM5Z+pf+JfOl5mAWgNfrdtnMhVcEGCEniJAQweKOfYCziuyB0ALkX/Q6jGmd2IshR36SarwCYEc5ttt/w==",
       "requires": {
-        "tslib": "1.8.1"
+        "tslib": "^1.7.1"
       }
     },
     "@angular/flex-layout": {
@@ -224,7 +224,7 @@
       "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-2.0.0-beta.12.tgz",
       "integrity": "sha1-gJcNwdYPJ/pBU3ZZkm8yOPdZ80M=",
       "requires": {
-        "tslib": "1.8.1"
+        "tslib": "^1.7.1"
       }
     },
     "@angular/forms": {
@@ -232,7 +232,7 @@
       "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-5.1.1.tgz",
       "integrity": "sha512-4iN/8N0DgnV82XIb/8PqlFIGrog8BHJlzQ9sdAlpT29biPFezFpqpsXkjLBouBc7oBFTgoyXMgWDj8IGRmwLGQ==",
       "requires": {
-        "tslib": "1.8.1"
+        "tslib": "^1.7.1"
       }
     },
     "@angular/http": {
@@ -240,7 +240,7 @@
       "resolved": "https://registry.npmjs.org/@angular/http/-/http-5.1.1.tgz",
       "integrity": "sha512-oeiLX00TaFlGS5Y4EAGnxxVitN8T9X8olhSC+XDDAAL3JHTAyh4dj7me8vNZk1VaqPFa9AXu4D34vu1Zsm0c1g==",
       "requires": {
-        "tslib": "1.8.1"
+        "tslib": "^1.7.1"
       }
     },
     "@angular/material": {
@@ -248,7 +248,7 @@
       "resolved": "https://registry.npmjs.org/@angular/material/-/material-5.0.1.tgz",
       "integrity": "sha512-k95i58ZIVneLE61a5JliM10NSasy9P5C2JJUESo3s/rxt9dq/9XOWpUvNCy49OHYBRFJBlsyrLM6E2V7/tmq4w==",
       "requires": {
-        "tslib": "1.8.1"
+        "tslib": "^1.7.1"
       }
     },
     "@angular/platform-browser": {
@@ -256,7 +256,7 @@
       "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-5.1.1.tgz",
       "integrity": "sha512-QpkNXoO2pqURQJxXPhZo6RFeirKbr56O0SwoMpYfXGGN1qEIicoWZHobCUTp7/jvjx5Xjc7886Fvu/qJrE7wVA==",
       "requires": {
-        "tslib": "1.8.1"
+        "tslib": "^1.7.1"
       }
     },
     "@angular/platform-browser-dynamic": {
@@ -264,7 +264,7 @@
       "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-5.1.1.tgz",
       "integrity": "sha512-xnin1eK5nF7EO4tYZvRlhT28DyhL3p4NKWsZQwfqyBwSF0T2mJ1vjhjCZVT0MmaOyt5D+0eUkHIhBDqeZyBMMQ==",
       "requires": {
-        "tslib": "1.8.1"
+        "tslib": "^1.7.1"
       }
     },
     "@angular/router": {
@@ -272,7 +272,7 @@
       "resolved": "https://registry.npmjs.org/@angular/router/-/router-5.1.1.tgz",
       "integrity": "sha512-96mBZS1b1Dt7HFOGKh5zI/1U6F3zT4cdjIaBmcCKkbyKhs3WRAPXxxCkuCwr6lWmBeQt4iEvSdXiHQbD0iCG7Q==",
       "requires": {
-        "tslib": "1.8.1"
+        "tslib": "^1.7.1"
       }
     },
     "@ngtools/json-schema": {
@@ -287,13 +287,13 @@
       "integrity": "sha512-+Fa/cpwiVnQuOMUUVmBH7+/xkhZHG/uEL60FBt6Pv8yFdLoXkgYPllJKsph5uWGpv40/zlnDYQv7k7yngeCX2A==",
       "dev": true,
       "requires": {
-        "chalk": "2.2.2",
-        "enhanced-resolve": "3.4.1",
-        "loader-utils": "1.1.0",
-        "magic-string": "0.22.4",
-        "semver": "5.4.1",
-        "source-map": "0.5.7",
-        "tree-kill": "1.2.0"
+        "chalk": "~2.2.0",
+        "enhanced-resolve": "^3.1.0",
+        "loader-utils": "^1.0.2",
+        "magic-string": "^0.22.3",
+        "semver": "^5.3.0",
+        "source-map": "^0.5.6",
+        "tree-kill": "^1.0.0"
       },
       "dependencies": {
         "ansi-styles": {
@@ -302,7 +302,7 @@
           "integrity": "sha1-wVm41b4PnlpvNG2rlPFs4CIWG4g=",
           "dev": true,
           "requires": {
-            "color-convert": "1.9.1"
+            "color-convert": "^1.9.0"
           }
         },
         "chalk": {
@@ -311,9 +311,9 @@
           "integrity": "sha1-RAP1zxjzXAX1H73xUr9Yj5Vs98s=",
           "dev": true,
           "requires": {
-            "ansi-styles": "3.2.0",
-            "escape-string-regexp": "1.0.5",
-            "supports-color": "4.5.0"
+            "ansi-styles": "^3.1.0",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^4.0.0"
           }
         },
         "source-map": {
@@ -328,7 +328,7 @@
           "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
           "dev": true,
           "requires": {
-            "has-flag": "2.0.0"
+            "has-flag": "^2.0.0"
           }
         }
       }
@@ -348,18 +348,49 @@
       "integrity": "sha512-HAXgAIuuAGjiIKohGlRUkmUTWYtNmclR12KHlQQxT9pHFdEb2OrpHjUp2YoV32jiU6jIZm4pf3ODwlPA0VbwnA==",
       "dev": true
     },
+    "@swimlane/ngx-charts": {
+      "version": "7.4.0",
+      "resolved": "https://registry.npmjs.org/@swimlane/ngx-charts/-/ngx-charts-7.4.0.tgz",
+      "integrity": "sha512-RcZas49AbHmKCX3PHiEI+VS2dgutfEzw7CvK/LXU1Plp8rMb+ly/zUoRIDJnyzBVuuy1z05miKUK6UolQlCzxQ==",
+      "requires": {
+        "d3-array": "^1.2.1",
+        "d3-brush": "^1.0.4",
+        "d3-color": "^1.0.3",
+        "d3-force": "^1.1.0",
+        "d3-format": "^1.2.0",
+        "d3-hierarchy": "^1.1.5",
+        "d3-interpolate": "^1.1.5",
+        "d3-scale": "^1.0.6",
+        "d3-selection": "^1.1.0",
+        "d3-shape": "^1.2.0",
+        "d3-time-format": "^2.1.0"
+      }
+    },
     "@swimlane/ngx-datatable": {
       "version": "11.1.7",
       "resolved": "https://registry.npmjs.org/@swimlane/ngx-datatable/-/ngx-datatable-11.1.7.tgz",
       "integrity": "sha1-1MoBo8DuZwcdohS3OdoZgXmk11Y="
     },
+    "@types/d3-selection": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-1.3.2.tgz",
+      "integrity": "sha512-K23sDOi7yMussv7aiqk097IWWbjFYbJpcDppQAcaf6DfmHxAsjr+6N4HJGokETLDuV7y/qJeeIJINPnkWJM5Hg=="
+    },
+    "@types/d3-transition": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-1.1.2.tgz",
+      "integrity": "sha512-sTENKlKkUaKUYjeYIj69VYIi3VKeBinY/pYdy5VkjNmEOIasUtZIyAY04waMU4Rq7u+czKQdcP7Aoaf5wpDGfA==",
+      "requires": {
+        "@types/d3-selection": "*"
+      }
+    },
     "@types/form-data": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz",
       "integrity": "sha1-7is7jqoRwJOCiZU2BrdFtzjFSx4=",
       "dev": true,
       "requires": {
-        "@types/node": "6.0.94"
+        "@types/node": "*"
       }
     },
     "@types/hammerjs": {
@@ -398,8 +429,8 @@
       "integrity": "sha512-IWq4Uhm7dShxPdnfbGtIv9ImhGFJ3HugoIfugUd+jt40Oxe6ZfWIEaHFvp4QmRNZVDj0G6dZfa+u0U0PF3bYpg==",
       "dev": true,
       "requires": {
-        "@types/form-data": "2.2.1",
-        "@types/node": "6.0.94"
+        "@types/form-data": "*",
+        "@types/node": "*"
       }
     },
     "@types/selenium-webdriver": {
@@ -408,6 +439,14 @@
       "integrity": "sha1-LePXGIGbwgFldUxKWa+36YM/Zwc=",
       "dev": true
     },
+    "@types/vis": {
+      "version": "4.18.9",
+      "resolved": "https://registry.npmjs.org/@types/vis/-/vis-4.18.9.tgz",
+      "integrity": "sha512-ltcjApa6T065dPc/uUaEJo06dcXrcWSfIVkiHc+C6QM5BQVK2drw8PYXILYVO3Ku+uuCpOEiBy7tuA5Lii4+nw==",
+      "requires": {
+        "moment": ">=2.13.0"
+      }
+    },
     "abbrev": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -418,7 +457,7 @@
       "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz",
       "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=",
       "requires": {
-        "mime-types": "2.1.17",
+        "mime-types": "~2.1.16",
         "negotiator": "0.6.1"
       }
     },
@@ -434,7 +473,7 @@
       "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=",
       "dev": true,
       "requires": {
-        "acorn": "4.0.13"
+        "acorn": "^4.0.3"
       },
       "dependencies": {
         "acorn": {
@@ -463,8 +502,8 @@
       "integrity": "sha1-1t4Q1a9hMtW9aSQn1G/FOFOQlMc=",
       "dev": true,
       "requires": {
-        "extend": "3.0.1",
-        "semver": "5.0.3"
+        "extend": "~3.0.0",
+        "semver": "~5.0.1"
       },
       "dependencies": {
         "semver": {
@@ -480,10 +519,10 @@
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.1.tgz",
       "integrity": "sha1-s4u4h22ehr7plJVqBOch6IskjrI=",
       "requires": {
-        "co": "4.6.0",
-        "fast-deep-equal": "1.0.0",
-        "fast-json-stable-stringify": "2.0.0",
-        "json-schema-traverse": "0.3.1"
+        "co": "^4.6.0",
+        "fast-deep-equal": "^1.0.0",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.3.0"
       }
     },
     "ajv-keywords": {
@@ -498,9 +537,9 @@
       "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
       "dev": true,
       "requires": {
-        "kind-of": "3.2.2",
-        "longest": "1.0.1",
-        "repeat-string": "1.6.1"
+        "kind-of": "^3.0.2",
+        "longest": "^1.0.1",
+        "repeat-string": "^1.5.2"
       }
     },
     "alphanum-sort": {
@@ -547,8 +586,8 @@
       "integrity": "sha1-VT3Lj5HjyImEXf26NMd3IbkLnXo=",
       "dev": true,
       "requires": {
-        "micromatch": "2.3.11",
-        "normalize-path": "2.1.1"
+        "micromatch": "^2.1.5",
+        "normalize-path": "^2.0.0"
       }
     },
     "app-root-path": {
@@ -563,7 +602,7 @@
       "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=",
       "dev": true,
       "requires": {
-        "default-require-extensions": "1.0.0"
+        "default-require-extensions": "^1.0.0"
       }
     },
     "aproba": {
@@ -576,8 +615,8 @@
       "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz",
       "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
       "requires": {
-        "delegates": "1.0.0",
-        "readable-stream": "2.3.3"
+        "delegates": "^1.0.0",
+        "readable-stream": "^2.0.6"
       }
     },
     "argparse": {
@@ -586,7 +625,7 @@
       "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=",
       "dev": true,
       "requires": {
-        "sprintf-js": "1.0.3"
+        "sprintf-js": "~1.0.2"
       }
     },
     "arr-diff": {
@@ -595,7 +634,7 @@
       "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=",
       "dev": true,
       "requires": {
-        "arr-flatten": "1.1.0"
+        "arr-flatten": "^1.0.1"
       }
     },
     "arr-flatten": {
@@ -620,8 +659,8 @@
       "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=",
       "dev": true,
       "requires": {
-        "define-properties": "1.1.2",
-        "es-abstract": "1.10.0"
+        "define-properties": "^1.1.2",
+        "es-abstract": "^1.7.0"
       }
     },
     "array-slice": {
@@ -636,7 +675,7 @@
       "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
       "dev": true,
       "requires": {
-        "array-uniq": "1.0.3"
+        "array-uniq": "^1.0.1"
       }
     },
     "array-uniq": {
@@ -681,9 +720,9 @@
       "integrity": "sha1-gRfvT37YfNj4kES1v/l6wkOhbJo=",
       "dev": true,
       "requires": {
-        "bn.js": "4.11.8",
-        "inherits": "2.0.3",
-        "minimalistic-assert": "1.0.0"
+        "bn.js": "^4.0.0",
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0"
       }
     },
     "assert": {
@@ -706,7 +745,7 @@
       "integrity": "sha1-YaKau2/MAm/qd+VtHG7FOnlZUfQ=",
       "dev": true,
       "requires": {
-        "lodash": "4.17.4"
+        "lodash": "^4.14.0"
       }
     },
     "async-each": {
@@ -731,12 +770,12 @@
       "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=",
       "dev": true,
       "requires": {
-        "browserslist": "1.7.7",
-        "caniuse-db": "1.0.30000783",
-        "normalize-range": "0.1.2",
-        "num2fraction": "1.2.2",
-        "postcss": "5.2.18",
-        "postcss-value-parser": "3.3.0"
+        "browserslist": "^1.7.6",
+        "caniuse-db": "^1.0.30000634",
+        "normalize-range": "^0.1.2",
+        "num2fraction": "^1.2.2",
+        "postcss": "^5.2.16",
+        "postcss-value-parser": "^3.2.3"
       }
     },
     "aws-sign2": {
@@ -755,9 +794,9 @@
       "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
       "dev": true,
       "requires": {
-        "chalk": "1.1.3",
-        "esutils": "2.0.2",
-        "js-tokens": "3.0.2"
+        "chalk": "^1.1.3",
+        "esutils": "^2.0.2",
+        "js-tokens": "^3.0.2"
       }
     },
     "babel-generator": {
@@ -766,14 +805,14 @@
       "integrity": "sha1-rBriAHC3n248odMmlhMFN3TyDcU=",
       "dev": true,
       "requires": {
-        "babel-messages": "6.23.0",
-        "babel-runtime": "6.26.0",
-        "babel-types": "6.26.0",
-        "detect-indent": "4.0.0",
-        "jsesc": "1.3.0",
-        "lodash": "4.17.4",
-        "source-map": "0.5.7",
-        "trim-right": "1.0.1"
+        "babel-messages": "^6.23.0",
+        "babel-runtime": "^6.26.0",
+        "babel-types": "^6.26.0",
+        "detect-indent": "^4.0.0",
+        "jsesc": "^1.3.0",
+        "lodash": "^4.17.4",
+        "source-map": "^0.5.6",
+        "trim-right": "^1.0.1"
       },
       "dependencies": {
         "jsesc": {
@@ -796,7 +835,7 @@
       "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.26.0"
+        "babel-runtime": "^6.22.0"
       }
     },
     "babel-runtime": {
@@ -805,8 +844,8 @@
       "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
       "dev": true,
       "requires": {
-        "core-js": "2.5.3",
-        "regenerator-runtime": "0.11.1"
+        "core-js": "^2.4.0",
+        "regenerator-runtime": "^0.11.0"
       }
     },
     "babel-template": {
@@ -815,11 +854,11 @@
       "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.26.0",
-        "babel-traverse": "6.26.0",
-        "babel-types": "6.26.0",
-        "babylon": "6.18.0",
-        "lodash": "4.17.4"
+        "babel-runtime": "^6.26.0",
+        "babel-traverse": "^6.26.0",
+        "babel-types": "^6.26.0",
+        "babylon": "^6.18.0",
+        "lodash": "^4.17.4"
       }
     },
     "babel-traverse": {
@@ -828,15 +867,15 @@
       "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=",
       "dev": true,
       "requires": {
-        "babel-code-frame": "6.26.0",
-        "babel-messages": "6.23.0",
-        "babel-runtime": "6.26.0",
-        "babel-types": "6.26.0",
-        "babylon": "6.18.0",
-        "debug": "2.6.9",
-        "globals": "9.18.0",
-        "invariant": "2.2.2",
-        "lodash": "4.17.4"
+        "babel-code-frame": "^6.26.0",
+        "babel-messages": "^6.23.0",
+        "babel-runtime": "^6.26.0",
+        "babel-types": "^6.26.0",
+        "babylon": "^6.18.0",
+        "debug": "^2.6.8",
+        "globals": "^9.18.0",
+        "invariant": "^2.2.2",
+        "lodash": "^4.17.4"
       }
     },
     "babel-types": {
@@ -845,10 +884,10 @@
       "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.26.0",
-        "esutils": "2.0.2",
-        "lodash": "4.17.4",
-        "to-fast-properties": "1.0.3"
+        "babel-runtime": "^6.26.0",
+        "esutils": "^2.0.2",
+        "lodash": "^4.17.4",
+        "to-fast-properties": "^1.0.3"
       }
     },
     "babylon": {
@@ -863,6 +902,14 @@
       "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=",
       "dev": true
     },
+    "backoff": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz",
+      "integrity": "sha1-9hbtqdPktmuMp/ynn2lXIsX44m8=",
+      "requires": {
+        "precond": "0.2"
+      }
+    },
     "balanced-match": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@@ -906,7 +953,7 @@
       "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
       "optional": true,
       "requires": {
-        "tweetnacl": "0.14.5"
+        "tweetnacl": "^0.14.3"
       }
     },
     "better-assert": {
@@ -941,7 +988,7 @@
       "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
       "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
       "requires": {
-        "inherits": "2.0.3"
+        "inherits": "~2.0.0"
       }
     },
     "blocking-proxy": {
@@ -950,7 +997,7 @@
       "integrity": "sha1-RikF4Nz76pcPQao3Ij3anAexkSs=",
       "dev": true,
       "requires": {
-        "minimist": "1.2.0"
+        "minimist": "^1.2.0"
       }
     },
     "bluebird": {
@@ -971,15 +1018,15 @@
       "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=",
       "requires": {
         "bytes": "3.0.0",
-        "content-type": "1.0.4",
+        "content-type": "~1.0.4",
         "debug": "2.6.9",
-        "depd": "1.1.1",
-        "http-errors": "1.6.2",
+        "depd": "~1.1.1",
+        "http-errors": "~1.6.2",
         "iconv-lite": "0.4.19",
-        "on-finished": "2.3.0",
+        "on-finished": "~2.3.0",
         "qs": "6.5.1",
         "raw-body": "2.3.2",
-        "type-is": "1.6.15"
+        "type-is": "~1.6.15"
       }
     },
     "bonjour": {
@@ -988,12 +1035,12 @@
       "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=",
       "dev": true,
       "requires": {
-        "array-flatten": "2.1.1",
-        "deep-equal": "1.0.1",
-        "dns-equal": "1.0.0",
-        "dns-txt": "2.0.2",
-        "multicast-dns": "6.2.1",
-        "multicast-dns-service-types": "1.1.0"
+        "array-flatten": "^2.1.0",
+        "deep-equal": "^1.0.1",
+        "dns-equal": "^1.0.0",
+        "dns-txt": "^2.0.2",
+        "multicast-dns": "^6.0.1",
+        "multicast-dns-service-types": "^1.1.0"
       },
       "dependencies": {
         "array-flatten": {
@@ -1015,7 +1062,7 @@
       "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz",
       "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=",
       "requires": {
-        "hoek": "4.2.0"
+        "hoek": "4.x.x"
       }
     },
     "brace-expansion": {
@@ -1023,7 +1070,7 @@
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
       "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
       "requires": {
-        "balanced-match": "1.0.0",
+        "balanced-match": "^1.0.0",
         "concat-map": "0.0.1"
       }
     },
@@ -1033,9 +1080,9 @@
       "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=",
       "dev": true,
       "requires": {
-        "expand-range": "1.8.2",
-        "preserve": "0.2.0",
-        "repeat-element": "1.1.2"
+        "expand-range": "^1.8.1",
+        "preserve": "^0.2.0",
+        "repeat-element": "^1.1.2"
       }
     },
     "brorand": {
@@ -1050,12 +1097,12 @@
       "integrity": "sha1-OLerVe24Bv8tzaGn8WIHc6R3xJ8=",
       "dev": true,
       "requires": {
-        "buffer-xor": "1.0.3",
-        "cipher-base": "1.0.4",
-        "create-hash": "1.1.3",
-        "evp_bytestokey": "1.0.3",
-        "inherits": "2.0.3",
-        "safe-buffer": "5.1.1"
+        "buffer-xor": "^1.0.3",
+        "cipher-base": "^1.0.0",
+        "create-hash": "^1.1.0",
+        "evp_bytestokey": "^1.0.3",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
       }
     },
     "browserify-cipher": {
@@ -1064,9 +1111,9 @@
       "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=",
       "dev": true,
       "requires": {
-        "browserify-aes": "1.1.1",
-        "browserify-des": "1.0.0",
-        "evp_bytestokey": "1.0.3"
+        "browserify-aes": "^1.0.4",
+        "browserify-des": "^1.0.0",
+        "evp_bytestokey": "^1.0.0"
       }
     },
     "browserify-des": {
@@ -1075,9 +1122,9 @@
       "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=",
       "dev": true,
       "requires": {
-        "cipher-base": "1.0.4",
-        "des.js": "1.0.0",
-        "inherits": "2.0.3"
+        "cipher-base": "^1.0.1",
+        "des.js": "^1.0.0",
+        "inherits": "^2.0.1"
       }
     },
     "browserify-rsa": {
@@ -1086,8 +1133,8 @@
       "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
       "dev": true,
       "requires": {
-        "bn.js": "4.11.8",
-        "randombytes": "2.0.5"
+        "bn.js": "^4.1.0",
+        "randombytes": "^2.0.1"
       }
     },
     "browserify-sign": {
@@ -1096,13 +1143,13 @@
       "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
       "dev": true,
       "requires": {
-        "bn.js": "4.11.8",
-        "browserify-rsa": "4.0.1",
-        "create-hash": "1.1.3",
-        "create-hmac": "1.1.6",
-        "elliptic": "6.4.0",
-        "inherits": "2.0.3",
-        "parse-asn1": "5.1.0"
+        "bn.js": "^4.1.1",
+        "browserify-rsa": "^4.0.0",
+        "create-hash": "^1.1.0",
+        "create-hmac": "^1.1.2",
+        "elliptic": "^6.0.0",
+        "inherits": "^2.0.1",
+        "parse-asn1": "^5.0.0"
       }
     },
     "browserify-zlib": {
@@ -1111,7 +1158,7 @@
       "integrity": "sha1-KGlFnZqjviRf6P4sofRuLn9U1z8=",
       "dev": true,
       "requires": {
-        "pako": "1.0.6"
+        "pako": "~1.0.5"
       }
     },
     "browserslist": {
@@ -1120,8 +1167,8 @@
       "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=",
       "dev": true,
       "requires": {
-        "caniuse-db": "1.0.30000783",
-        "electron-to-chromium": "1.3.28"
+        "caniuse-db": "^1.0.30000639",
+        "electron-to-chromium": "^1.2.7"
       }
     },
     "buffer": {
@@ -1130,9 +1177,9 @@
       "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
       "dev": true,
       "requires": {
-        "base64-js": "1.2.1",
-        "ieee754": "1.1.8",
-        "isarray": "1.0.0"
+        "base64-js": "^1.0.2",
+        "ieee754": "^1.1.4",
+        "isarray": "^1.0.0"
       }
     },
     "buffer-indexof": {
@@ -1158,6 +1205,17 @@
       "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
       "dev": true
     },
+    "bunyan": {
+      "version": "1.8.12",
+      "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz",
+      "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=",
+      "requires": {
+        "dtrace-provider": "~0.8",
+        "moment": "^2.10.6",
+        "mv": "~2",
+        "safe-json-stringify": "~1"
+      }
+    },
     "bytes": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
@@ -1169,19 +1227,19 @@
       "integrity": "sha512-dRHYcs9LvG9cHgdPzjiI+/eS7e1xRhULrcyOx04RZQsszNJXU2SL9CyG60yLnge282Qq5nwTv+ieK2fH+WPZmA==",
       "dev": true,
       "requires": {
-        "bluebird": "3.5.1",
-        "chownr": "1.0.1",
-        "glob": "7.1.2",
-        "graceful-fs": "4.1.11",
-        "lru-cache": "4.1.1",
-        "mississippi": "1.3.0",
-        "mkdirp": "0.5.1",
-        "move-concurrently": "1.0.1",
-        "promise-inflight": "1.0.1",
-        "rimraf": "2.6.2",
-        "ssri": "5.0.0",
-        "unique-filename": "1.1.0",
-        "y18n": "3.2.1"
+        "bluebird": "^3.5.0",
+        "chownr": "^1.0.1",
+        "glob": "^7.1.2",
+        "graceful-fs": "^4.1.11",
+        "lru-cache": "^4.1.1",
+        "mississippi": "^1.3.0",
+        "mkdirp": "^0.5.1",
+        "move-concurrently": "^1.0.1",
+        "promise-inflight": "^1.0.1",
+        "rimraf": "^2.6.1",
+        "ssri": "^5.0.0",
+        "unique-filename": "^1.1.0",
+        "y18n": "^3.2.1"
       }
     },
     "callsite": {
@@ -1196,8 +1254,8 @@
       "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=",
       "dev": true,
       "requires": {
-        "no-case": "2.3.2",
-        "upper-case": "1.1.3"
+        "no-case": "^2.2.0",
+        "upper-case": "^1.1.1"
       }
     },
     "camelcase": {
@@ -1210,8 +1268,8 @@
       "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
       "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
       "requires": {
-        "camelcase": "2.1.1",
-        "map-obj": "1.0.1"
+        "camelcase": "^2.0.0",
+        "map-obj": "^1.0.0"
       }
     },
     "caniuse-api": {
@@ -1220,10 +1278,10 @@
       "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=",
       "dev": true,
       "requires": {
-        "browserslist": "1.7.7",
-        "caniuse-db": "1.0.30000783",
-        "lodash.memoize": "4.1.2",
-        "lodash.uniq": "4.5.0"
+        "browserslist": "^1.3.6",
+        "caniuse-db": "^1.0.30000529",
+        "lodash.memoize": "^4.1.2",
+        "lodash.uniq": "^4.5.0"
       }
     },
     "caniuse-db": {
@@ -1243,8 +1301,8 @@
       "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=",
       "dev": true,
       "requires": {
-        "align-text": "0.1.4",
-        "lazy-cache": "1.0.4"
+        "align-text": "^0.1.3",
+        "lazy-cache": "^1.0.3"
       },
       "dependencies": {
         "lazy-cache": {
@@ -1260,11 +1318,11 @@
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
       "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
       "requires": {
-        "ansi-styles": "2.2.1",
-        "escape-string-regexp": "1.0.5",
-        "has-ansi": "2.0.0",
-        "strip-ansi": "3.0.1",
-        "supports-color": "2.0.0"
+        "ansi-styles": "^2.2.1",
+        "escape-string-regexp": "^1.0.2",
+        "has-ansi": "^2.0.0",
+        "strip-ansi": "^3.0.0",
+        "supports-color": "^2.0.0"
       }
     },
     "chokidar": {
@@ -1273,15 +1331,15 @@
       "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=",
       "dev": true,
       "requires": {
-        "anymatch": "1.3.2",
-        "async-each": "1.0.1",
-        "fsevents": "1.1.3",
-        "glob-parent": "2.0.0",
-        "inherits": "2.0.3",
-        "is-binary-path": "1.0.1",
-        "is-glob": "2.0.1",
-        "path-is-absolute": "1.0.1",
-        "readdirp": "2.1.0"
+        "anymatch": "^1.3.0",
+        "async-each": "^1.0.0",
+        "fsevents": "^1.0.0",
+        "glob-parent": "^2.0.0",
+        "inherits": "^2.0.1",
+        "is-binary-path": "^1.0.0",
+        "is-glob": "^2.0.0",
+        "path-is-absolute": "^1.0.0",
+        "readdirp": "^2.0.0"
       },
       "dependencies": {
         "is-extglob": {
@@ -1296,7 +1354,7 @@
           "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
           "dev": true,
           "requires": {
-            "is-extglob": "1.0.0"
+            "is-extglob": "^1.0.0"
           }
         }
       }
@@ -1313,8 +1371,8 @@
       "integrity": "sha1-h2Dk7MJy9MNjUy+SbYdKriwTl94=",
       "dev": true,
       "requires": {
-        "inherits": "2.0.3",
-        "safe-buffer": "5.1.1"
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
       }
     },
     "circular-dependency-plugin": {
@@ -1329,7 +1387,7 @@
       "integrity": "sha1-TzZ0WzIAhJJVf0ZBLWbVDLmbzlE=",
       "dev": true,
       "requires": {
-        "chalk": "1.1.3"
+        "chalk": "^1.1.3"
       }
     },
     "clean-css": {
@@ -1338,7 +1396,7 @@
       "integrity": "sha1-Nc7ornaHpJuYA09w3gDE7dOCYwE=",
       "dev": true,
       "requires": {
-        "source-map": "0.5.7"
+        "source-map": "0.5.x"
       },
       "dependencies": {
         "source-map": {
@@ -1354,9 +1412,9 @@
       "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
       "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
       "requires": {
-        "string-width": "1.0.2",
-        "strip-ansi": "3.0.1",
-        "wrap-ansi": "2.1.0"
+        "string-width": "^1.0.1",
+        "strip-ansi": "^3.0.1",
+        "wrap-ansi": "^2.0.0"
       }
     },
     "clone": {
@@ -1371,10 +1429,10 @@
       "integrity": "sha1-NIxhrpzb4O3+BT2R/0zFIdeQ7eg=",
       "dev": true,
       "requires": {
-        "for-own": "1.0.0",
-        "is-plain-object": "2.0.4",
-        "kind-of": "3.2.2",
-        "shallow-clone": "0.1.2"
+        "for-own": "^1.0.0",
+        "is-plain-object": "^2.0.1",
+        "kind-of": "^3.2.2",
+        "shallow-clone": "^0.1.2"
       }
     },
     "co": {
@@ -1388,7 +1446,7 @@
       "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=",
       "dev": true,
       "requires": {
-        "q": "1.5.1"
+        "q": "^1.1.2"
       }
     },
     "code-point-at": {
@@ -1402,12 +1460,12 @@
       "integrity": "sha512-nYwOr49+IV09e7C4aXkVALRz0+XpHqZiUUcxHuDZH4xP1FBcHINyr3qvVhv5Gfm7XRmoLx32tsIhrQhW/gBcog==",
       "dev": true,
       "requires": {
-        "app-root-path": "2.0.1",
-        "css-selector-tokenizer": "0.7.0",
-        "cssauron": "1.4.0",
-        "semver-dsl": "1.0.1",
-        "source-map": "0.5.7",
-        "sprintf-js": "1.0.3"
+        "app-root-path": "^2.0.1",
+        "css-selector-tokenizer": "^0.7.0",
+        "cssauron": "^1.4.0",
+        "semver-dsl": "^1.0.1",
+        "source-map": "^0.5.6",
+        "sprintf-js": "^1.0.3"
       },
       "dependencies": {
         "source-map": {
@@ -1424,9 +1482,9 @@
       "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=",
       "dev": true,
       "requires": {
-        "clone": "1.0.3",
-        "color-convert": "1.9.1",
-        "color-string": "0.3.0"
+        "clone": "^1.0.2",
+        "color-convert": "^1.3.0",
+        "color-string": "^0.3.0"
       }
     },
     "color-convert": {
@@ -1435,7 +1493,7 @@
       "integrity": "sha1-wSYRB66y8pTr/+ye2eytUppgl+0=",
       "dev": true,
       "requires": {
-        "color-name": "1.1.3"
+        "color-name": "^1.1.1"
       }
     },
     "color-name": {
@@ -1450,7 +1508,7 @@
       "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=",
       "dev": true,
       "requires": {
-        "color-name": "1.1.3"
+        "color-name": "^1.0.0"
       }
     },
     "colormin": {
@@ -1459,9 +1517,9 @@
       "integrity": "sha1-6i90IKcrlogaOKrlnsEkpvcpgTM=",
       "dev": true,
       "requires": {
-        "color": "0.11.4",
+        "color": "^0.11.0",
         "css-color-names": "0.0.4",
-        "has": "1.0.1"
+        "has": "^1.0.1"
       }
     },
     "colors": {
@@ -1476,7 +1534,7 @@
       "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=",
       "dev": true,
       "requires": {
-        "lodash": "4.17.4"
+        "lodash": "^4.5.0"
       }
     },
     "combined-stream": {
@@ -1484,7 +1542,7 @@
       "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
       "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=",
       "requires": {
-        "delayed-stream": "1.0.0"
+        "delayed-stream": "~1.0.0"
       }
     },
     "commander": {
@@ -1499,7 +1557,7 @@
       "integrity": "sha512-NrUYGY5TApAk9KB+IZXkR3GR4tA3g26HDsoiGt4kCMHZ727gOGkC+UNfq0Z22jE15bLkc/6RV5Jw1RBW6Usg6A==",
       "dev": true,
       "requires": {
-        "babel-runtime": "6.26.0"
+        "babel-runtime": "^6.26.0"
       }
     },
     "commondir": {
@@ -1532,7 +1590,7 @@
       "integrity": "sha1-xZpcmdt2dn6YdlAOJx72OzSTvWY=",
       "dev": true,
       "requires": {
-        "mime-db": "1.30.0"
+        "mime-db": ">= 1.30.0 < 2"
       }
     },
     "compression": {
@@ -1541,13 +1599,13 @@
       "integrity": "sha1-7/JgPvwuIs+G810uuTWJ+YdTc9s=",
       "dev": true,
       "requires": {
-        "accepts": "1.3.4",
+        "accepts": "~1.3.4",
         "bytes": "3.0.0",
-        "compressible": "2.0.12",
+        "compressible": "~2.0.11",
         "debug": "2.6.9",
-        "on-headers": "1.0.1",
+        "on-headers": "~1.0.1",
         "safe-buffer": "5.1.1",
-        "vary": "1.1.2"
+        "vary": "~1.1.2"
       }
     },
     "concat-map": {
@@ -1561,9 +1619,9 @@
       "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=",
       "dev": true,
       "requires": {
-        "inherits": "2.0.3",
-        "readable-stream": "2.3.3",
-        "typedarray": "0.0.6"
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.2.2",
+        "typedarray": "^0.0.6"
       }
     },
     "concurrently": {
@@ -1574,12 +1632,12 @@
       "requires": {
         "chalk": "0.5.1",
         "commander": "2.6.0",
-        "date-fns": "1.29.0",
-        "lodash": "4.17.4",
+        "date-fns": "^1.23.0",
+        "lodash": "^4.5.1",
         "rx": "2.3.24",
-        "spawn-command": "0.0.2-1",
-        "supports-color": "3.2.3",
-        "tree-kill": "1.2.0"
+        "spawn-command": "^0.0.2-1",
+        "supports-color": "^3.2.3",
+        "tree-kill": "^1.1.0"
       },
       "dependencies": {
         "ansi-regex": {
@@ -1600,11 +1658,11 @@
           "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=",
           "dev": true,
           "requires": {
-            "ansi-styles": "1.1.0",
-            "escape-string-regexp": "1.0.5",
-            "has-ansi": "0.1.0",
-            "strip-ansi": "0.3.0",
-            "supports-color": "0.2.0"
+            "ansi-styles": "^1.1.0",
+            "escape-string-regexp": "^1.0.0",
+            "has-ansi": "^0.1.0",
+            "strip-ansi": "^0.3.0",
+            "supports-color": "^0.2.0"
           },
           "dependencies": {
             "supports-color": {
@@ -1627,7 +1685,7 @@
           "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=",
           "dev": true,
           "requires": {
-            "ansi-regex": "0.2.1"
+            "ansi-regex": "^0.2.0"
           }
         },
         "has-flag": {
@@ -1642,7 +1700,7 @@
           "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=",
           "dev": true,
           "requires": {
-            "ansi-regex": "0.2.1"
+            "ansi-regex": "^0.2.1"
           }
         },
         "supports-color": {
@@ -1651,7 +1709,7 @@
           "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=",
           "dev": true,
           "requires": {
-            "has-flag": "1.0.0"
+            "has-flag": "^1.0.0"
           }
         }
       }
@@ -1662,14 +1720,14 @@
       "integrity": "sha1-w1eB0FAdJowlxUuLF/YkDopPsCE=",
       "dev": true,
       "requires": {
-        "graceful-fs": "4.1.11",
-        "mkdirp": "0.5.1",
-        "object-assign": "4.1.1",
-        "os-tmpdir": "1.0.2",
-        "osenv": "0.1.4",
-        "uuid": "2.0.3",
-        "write-file-atomic": "1.3.4",
-        "xdg-basedir": "2.0.0"
+        "graceful-fs": "^4.1.2",
+        "mkdirp": "^0.5.0",
+        "object-assign": "^4.0.1",
+        "os-tmpdir": "^1.0.0",
+        "osenv": "^0.1.0",
+        "uuid": "^2.0.1",
+        "write-file-atomic": "^1.1.2",
+        "xdg-basedir": "^2.0.0"
       },
       "dependencies": {
         "uuid": {
@@ -1688,7 +1746,7 @@
       "requires": {
         "debug": "2.6.9",
         "finalhandler": "1.0.6",
-        "parseurl": "1.3.2",
+        "parseurl": "~1.3.2",
         "utils-merge": "1.0.1"
       },
       "dependencies": {
@@ -1699,12 +1757,12 @@
           "dev": true,
           "requires": {
             "debug": "2.6.9",
-            "encodeurl": "1.0.1",
-            "escape-html": "1.0.3",
-            "on-finished": "2.3.0",
-            "parseurl": "1.3.2",
-            "statuses": "1.3.1",
-            "unpipe": "1.0.0"
+            "encodeurl": "~1.0.1",
+            "escape-html": "~1.0.3",
+            "on-finished": "~2.3.0",
+            "parseurl": "~1.3.2",
+            "statuses": "~1.3.1",
+            "unpipe": "~1.0.0"
           }
         },
         "statuses": {
@@ -1727,7 +1785,7 @@
       "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=",
       "dev": true,
       "requires": {
-        "date-now": "0.1.4"
+        "date-now": "^0.1.4"
       }
     },
     "console-control-strings": {
@@ -1773,12 +1831,12 @@
       "integrity": "sha1-kilzmMrjSTf8r9bsgTnBgFHwteA=",
       "dev": true,
       "requires": {
-        "aproba": "1.2.0",
-        "fs-write-stream-atomic": "1.0.10",
-        "iferr": "0.1.5",
-        "mkdirp": "0.5.1",
-        "rimraf": "2.6.2",
-        "run-queue": "1.0.3"
+        "aproba": "^1.1.1",
+        "fs-write-stream-atomic": "^1.0.8",
+        "iferr": "^0.1.5",
+        "mkdirp": "^0.5.1",
+        "rimraf": "^2.5.4",
+        "run-queue": "^1.0.0"
       }
     },
     "copy-webpack-plugin": {
@@ -1787,16 +1845,16 @@
       "integrity": "sha512-5o1/xyWm8OYDmLFKAWMuPU3A/jZ4Z6kZSZGh36KD2XmtxnRa8lQyLx7bCNQm08BPaR/oqUdtJOr9jWfnYINp9g==",
       "dev": true,
       "requires": {
-        "cacache": "10.0.1",
-        "find-cache-dir": "1.0.0",
-        "globby": "7.1.1",
-        "is-glob": "4.0.0",
-        "loader-utils": "0.2.17",
-        "lodash": "4.17.4",
-        "minimatch": "3.0.4",
-        "p-limit": "1.1.0",
-        "pify": "3.0.0",
-        "serialize-javascript": "1.4.0"
+        "cacache": "^10.0.1",
+        "find-cache-dir": "^1.0.0",
+        "globby": "^7.1.1",
+        "is-glob": "^4.0.0",
+        "loader-utils": "^0.2.15",
+        "lodash": "^4.3.0",
+        "minimatch": "^3.0.4",
+        "p-limit": "^1.0.0",
+        "pify": "^3.0.0",
+        "serialize-javascript": "^1.4.0"
       },
       "dependencies": {
         "loader-utils": {
@@ -1805,10 +1863,10 @@
           "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=",
           "dev": true,
           "requires": {
-            "big.js": "3.2.0",
-            "emojis-list": "2.1.0",
-            "json5": "0.5.1",
-            "object-assign": "4.1.1"
+            "big.js": "^3.1.3",
+            "emojis-list": "^2.0.0",
+            "json5": "^0.5.0",
+            "object-assign": "^4.0.1"
           }
         },
         "pify": {
@@ -1830,7 +1888,7 @@
       "integrity": "sha1-+mJ7h1Aq3JgEXkRnjpqOw7nA0qk=",
       "dev": true,
       "requires": {
-        "chalk": "2.3.0"
+        "chalk": "^2.0.0"
       },
       "dependencies": {
         "ansi-styles": {
@@ -1839,7 +1897,7 @@
           "integrity": "sha1-wVm41b4PnlpvNG2rlPFs4CIWG4g=",
           "dev": true,
           "requires": {
-            "color-convert": "1.9.1"
+            "color-convert": "^1.9.0"
           }
         },
         "chalk": {
@@ -1848,9 +1906,9 @@
           "integrity": "sha1-tepI78nBeT3MybR2fJORTT8tUro=",
           "dev": true,
           "requires": {
-            "ansi-styles": "3.2.0",
-            "escape-string-regexp": "1.0.5",
-            "supports-color": "4.5.0"
+            "ansi-styles": "^3.1.0",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^4.0.0"
           }
         },
         "supports-color": {
@@ -1859,7 +1917,7 @@
           "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
           "dev": true,
           "requires": {
-            "has-flag": "2.0.0"
+            "has-flag": "^2.0.0"
           }
         }
       }
@@ -1875,13 +1933,13 @@
       "integrity": "sha1-YXPOvVb6wELB9DkO33r2wHx8uJI=",
       "dev": true,
       "requires": {
-        "is-directory": "0.3.1",
-        "js-yaml": "3.7.0",
-        "minimist": "1.2.0",
-        "object-assign": "4.1.1",
-        "os-homedir": "1.0.2",
-        "parse-json": "2.2.0",
-        "require-from-string": "1.2.1"
+        "is-directory": "^0.3.1",
+        "js-yaml": "^3.4.3",
+        "minimist": "^1.2.0",
+        "object-assign": "^4.1.0",
+        "os-homedir": "^1.0.1",
+        "parse-json": "^2.2.0",
+        "require-from-string": "^1.1.0"
       }
     },
     "crc": {
@@ -1895,8 +1953,8 @@
       "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=",
       "dev": true,
       "requires": {
-        "bn.js": "4.11.8",
-        "elliptic": "6.4.0"
+        "bn.js": "^4.1.0",
+        "elliptic": "^6.0.0"
       }
     },
     "create-hash": {
@@ -1905,10 +1963,10 @@
       "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=",
       "dev": true,
       "requires": {
-        "cipher-base": "1.0.4",
-        "inherits": "2.0.3",
-        "ripemd160": "2.0.1",
-        "sha.js": "2.4.9"
+        "cipher-base": "^1.0.1",
+        "inherits": "^2.0.1",
+        "ripemd160": "^2.0.0",
+        "sha.js": "^2.4.0"
       }
     },
     "create-hmac": {
@@ -1917,12 +1975,12 @@
       "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=",
       "dev": true,
       "requires": {
-        "cipher-base": "1.0.4",
-        "create-hash": "1.1.3",
-        "inherits": "2.0.3",
-        "ripemd160": "2.0.1",
-        "safe-buffer": "5.1.1",
-        "sha.js": "2.4.9"
+        "cipher-base": "^1.0.3",
+        "create-hash": "^1.1.0",
+        "inherits": "^2.0.1",
+        "ripemd160": "^2.0.0",
+        "safe-buffer": "^5.0.1",
+        "sha.js": "^2.4.8"
       }
     },
     "cross-spawn": {
@@ -1930,8 +1988,8 @@
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
       "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=",
       "requires": {
-        "lru-cache": "4.1.1",
-        "which": "1.3.0"
+        "lru-cache": "^4.0.1",
+        "which": "^1.2.9"
       }
     },
     "cryptiles": {
@@ -1939,7 +1997,7 @@
       "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
       "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=",
       "requires": {
-        "boom": "5.2.0"
+        "boom": "5.x.x"
       },
       "dependencies": {
         "boom": {
@@ -1947,7 +2005,7 @@
           "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz",
           "integrity": "sha1-XdnabuOl8wIHdDYpDLcX0/SlTgI=",
           "requires": {
-            "hoek": "4.2.0"
+            "hoek": "4.x.x"
           }
         }
       }
@@ -1958,17 +2016,17 @@
       "integrity": "sha1-OWz58xN/A+S45TLFj2mCVOAPgOw=",
       "dev": true,
       "requires": {
-        "browserify-cipher": "1.0.0",
-        "browserify-sign": "4.0.4",
-        "create-ecdh": "4.0.0",
-        "create-hash": "1.1.3",
-        "create-hmac": "1.1.6",
-        "diffie-hellman": "5.0.2",
-        "inherits": "2.0.3",
-        "pbkdf2": "3.0.14",
-        "public-encrypt": "4.0.0",
-        "randombytes": "2.0.5",
-        "randomfill": "1.0.3"
+        "browserify-cipher": "^1.0.0",
+        "browserify-sign": "^4.0.0",
+        "create-ecdh": "^4.0.0",
+        "create-hash": "^1.1.0",
+        "create-hmac": "^1.1.0",
+        "diffie-hellman": "^5.0.0",
+        "inherits": "^2.0.1",
+        "pbkdf2": "^3.0.3",
+        "public-encrypt": "^4.0.0",
+        "randombytes": "^2.0.0",
+        "randomfill": "^1.0.3"
       }
     },
     "css-color-names": {
@@ -1983,20 +2041,20 @@
       "integrity": "sha512-GxMpax8a/VgcfRrVy0gXD6yLd5ePYbXX/5zGgTVYp4wXtJklS8Z2VaUArJgc//f6/Dzil7BaJObdSv8eKKCPgg==",
       "dev": true,
       "requires": {
-        "babel-code-frame": "6.26.0",
-        "css-selector-tokenizer": "0.7.0",
-        "cssnano": "3.10.0",
-        "icss-utils": "2.1.0",
-        "loader-utils": "1.1.0",
-        "lodash.camelcase": "4.3.0",
-        "object-assign": "4.1.1",
-        "postcss": "5.2.18",
-        "postcss-modules-extract-imports": "1.1.0",
-        "postcss-modules-local-by-default": "1.2.0",
-        "postcss-modules-scope": "1.1.0",
-        "postcss-modules-values": "1.3.0",
-        "postcss-value-parser": "3.3.0",
-        "source-list-map": "2.0.0"
+        "babel-code-frame": "^6.11.0",
+        "css-selector-tokenizer": "^0.7.0",
+        "cssnano": ">=2.6.1 <4",
+        "icss-utils": "^2.1.0",
+        "loader-utils": "^1.0.2",
+        "lodash.camelcase": "^4.3.0",
+        "object-assign": "^4.0.1",
+        "postcss": "^5.0.6",
+        "postcss-modules-extract-imports": "^1.0.0",
+        "postcss-modules-local-by-default": "^1.0.1",
+        "postcss-modules-scope": "^1.0.0",
+        "postcss-modules-values": "^1.1.0",
+        "postcss-value-parser": "^3.3.0",
+        "source-list-map": "^2.0.0"
       }
     },
     "css-parse": {
@@ -2011,10 +2069,10 @@
       "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
       "dev": true,
       "requires": {
-        "boolbase": "1.0.0",
-        "css-what": "2.1.0",
+        "boolbase": "~1.0.0",
+        "css-what": "2.1",
         "domutils": "1.5.1",
-        "nth-check": "1.0.1"
+        "nth-check": "~1.0.1"
       }
     },
     "css-selector-tokenizer": {
@@ -2023,9 +2081,9 @@
       "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=",
       "dev": true,
       "requires": {
-        "cssesc": "0.1.0",
-        "fastparse": "1.1.1",
-        "regexpu-core": "1.0.0"
+        "cssesc": "^0.1.0",
+        "fastparse": "^1.1.1",
+        "regexpu-core": "^1.0.0"
       }
     },
     "css-what": {
@@ -2040,7 +2098,7 @@
       "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=",
       "dev": true,
       "requires": {
-        "through": "2.3.8"
+        "through": "X.X.X"
       }
     },
     "cssesc": {
@@ -2055,38 +2113,38 @@
       "integrity": "sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=",
       "dev": true,
       "requires": {
-        "autoprefixer": "6.7.7",
-        "decamelize": "1.2.0",
-        "defined": "1.0.0",
-        "has": "1.0.1",
-        "object-assign": "4.1.1",
-        "postcss": "5.2.18",
-        "postcss-calc": "5.3.1",
-        "postcss-colormin": "2.2.2",
-        "postcss-convert-values": "2.6.1",
-        "postcss-discard-comments": "2.0.4",
-        "postcss-discard-duplicates": "2.1.0",
-        "postcss-discard-empty": "2.1.0",
-        "postcss-discard-overridden": "0.1.1",
-        "postcss-discard-unused": "2.2.3",
-        "postcss-filter-plugins": "2.0.2",
-        "postcss-merge-idents": "2.1.7",
-        "postcss-merge-longhand": "2.0.2",
-        "postcss-merge-rules": "2.1.2",
-        "postcss-minify-font-values": "1.0.5",
-        "postcss-minify-gradients": "1.0.5",
-        "postcss-minify-params": "1.2.2",
-        "postcss-minify-selectors": "2.1.1",
-        "postcss-normalize-charset": "1.1.1",
-        "postcss-normalize-url": "3.0.8",
-        "postcss-ordered-values": "2.2.3",
-        "postcss-reduce-idents": "2.4.0",
-        "postcss-reduce-initial": "1.0.1",
-        "postcss-reduce-transforms": "1.0.4",
-        "postcss-svgo": "2.1.6",
-        "postcss-unique-selectors": "2.0.2",
-        "postcss-value-parser": "3.3.0",
-        "postcss-zindex": "2.2.0"
+        "autoprefixer": "^6.3.1",
+        "decamelize": "^1.1.2",
+        "defined": "^1.0.0",
+        "has": "^1.0.1",
+        "object-assign": "^4.0.1",
+        "postcss": "^5.0.14",
+        "postcss-calc": "^5.2.0",
+        "postcss-colormin": "^2.1.8",
+        "postcss-convert-values": "^2.3.4",
+        "postcss-discard-comments": "^2.0.4",
+        "postcss-discard-duplicates": "^2.0.1",
+        "postcss-discard-empty": "^2.0.1",
+        "postcss-discard-overridden": "^0.1.1",
+        "postcss-discard-unused": "^2.2.1",
+        "postcss-filter-plugins": "^2.0.0",
+        "postcss-merge-idents": "^2.1.5",
+        "postcss-merge-longhand": "^2.0.1",
+        "postcss-merge-rules": "^2.0.3",
+        "postcss-minify-font-values": "^1.0.2",
+        "postcss-minify-gradients": "^1.0.1",
+        "postcss-minify-params": "^1.0.4",
+        "postcss-minify-selectors": "^2.0.4",
+        "postcss-normalize-charset": "^1.1.0",
+        "postcss-normalize-url": "^3.0.7",
+        "postcss-ordered-values": "^2.1.0",
+        "postcss-reduce-idents": "^2.2.2",
+        "postcss-reduce-initial": "^1.0.0",
+        "postcss-reduce-transforms": "^1.0.3",
+        "postcss-svgo": "^2.1.1",
+        "postcss-unique-selectors": "^2.0.2",
+        "postcss-value-parser": "^3.2.3",
+        "postcss-zindex": "^2.0.1"
       }
     },
     "csso": {
@@ -2095,8 +2153,8 @@
       "integrity": "sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U=",
       "dev": true,
       "requires": {
-        "clap": "1.2.3",
-        "source-map": "0.5.7"
+        "clap": "^1.0.9",
+        "source-map": "^0.5.3"
       },
       "dependencies": {
         "source-map": {
@@ -2118,7 +2176,7 @@
       "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
       "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
       "requires": {
-        "array-find-index": "1.0.2"
+        "array-find-index": "^1.0.1"
       }
     },
     "custom-event": {
@@ -2139,7 +2197,166 @@
       "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
       "dev": true,
       "requires": {
-        "es5-ext": "0.10.37"
+        "es5-ext": "^0.10.9"
+      }
+    },
+    "d3-array": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.1.tgz",
+      "integrity": "sha512-CyINJQ0SOUHojDdFDH4JEM0552vCR1utGyLHegJHyYH0JyCpSeTPxi4OBqHMA2jJZq4NH782LtaJWBImqI/HBw=="
+    },
+    "d3-brush": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.0.4.tgz",
+      "integrity": "sha1-AMLyOAGfJPbAoZSibUGhUw/+e8Q=",
+      "requires": {
+        "d3-dispatch": "1",
+        "d3-drag": "1",
+        "d3-interpolate": "1",
+        "d3-selection": "1",
+        "d3-transition": "1"
+      }
+    },
+    "d3-collection": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.4.tgz",
+      "integrity": "sha1-NC39EoN8kJdPM/HMCnha6lcNzcI="
+    },
+    "d3-color": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.2.0.tgz",
+      "integrity": "sha512-dmL9Zr/v39aSSMnLOTd58in2RbregCg4UtGyUArvEKTTN6S3HKEy+ziBWVYo9PTzRyVW+pUBHUtRKz0HYX+SQg=="
+    },
+    "d3-dispatch": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.3.tgz",
+      "integrity": "sha1-RuFJHqqbWMNY/OW+TovtYm54cfg="
+    },
+    "d3-drag": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.1.tgz",
+      "integrity": "sha512-Cg8/K2rTtzxzrb0fmnYOUeZHvwa4PHzwXOLZZPwtEs2SKLLKLXeYwZKBB+DlOxUvFmarOnmt//cU4+3US2lyyQ==",
+      "requires": {
+        "d3-dispatch": "1",
+        "d3-selection": "1"
+      }
+    },
+    "d3-ease": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.3.tgz",
+      "integrity": "sha1-aL+8NJM4o4DETYrMT7wzBKotjA4="
+    },
+    "d3-force": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.1.0.tgz",
+      "integrity": "sha512-2HVQz3/VCQs0QeRNZTYb7GxoUCeb6bOzMp/cGcLa87awY9ZsPvXOGeZm0iaGBjXic6I1ysKwMn+g+5jSAdzwcg==",
+      "requires": {
+        "d3-collection": "1",
+        "d3-dispatch": "1",
+        "d3-quadtree": "1",
+        "d3-timer": "1"
+      }
+    },
+    "d3-format": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.3.0.tgz",
+      "integrity": "sha512-ycfLEIzHVZC3rOvuBOKVyQXSiUyCDjeAPIj9n/wugrr+s5AcTQC2Bz6aKkubG7rQaQF0SGW/OV4UEJB9nfioFg=="
+    },
+    "d3-hierarchy": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.6.tgz",
+      "integrity": "sha512-nn4bhBnwWnMSoZgkBXD7vRyZ0xVUsNMQRKytWYHhP1I4qHw+qzApCTgSQTZqMdf4XXZbTMqA59hFusga+THA/g=="
+    },
+    "d3-interpolate": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.2.0.tgz",
+      "integrity": "sha512-zLvTk8CREPFfc/2XglPQriAsXkzoRDAyBzndtKJWrZmHw7kmOWHNS11e40kPTd/oGk8P5mFJW5uBbcFQ+ybxyA==",
+      "requires": {
+        "d3-color": "1"
+      }
+    },
+    "d3-path": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.5.tgz",
+      "integrity": "sha1-JB6xhJvZ6egCHA0KeZ+KDo5EF2Q="
+    },
+    "d3-quadtree": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.3.tgz",
+      "integrity": "sha1-rHmH4+I/6AWpkPKOG1DTj8uCJDg="
+    },
+    "d3-scale": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-1.0.7.tgz",
+      "integrity": "sha512-KvU92czp2/qse5tUfGms6Kjig0AhHOwkzXG0+PqIJB3ke0WUv088AHMZI0OssO9NCkXt4RP8yju9rpH8aGB7Lw==",
+      "requires": {
+        "d3-array": "^1.2.0",
+        "d3-collection": "1",
+        "d3-color": "1",
+        "d3-format": "1",
+        "d3-interpolate": "1",
+        "d3-time": "1",
+        "d3-time-format": "2"
+      }
+    },
+    "d3-selection": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.3.0.tgz",
+      "integrity": "sha512-qgpUOg9tl5CirdqESUAu0t9MU/t3O9klYfGfyKsXEmhyxyzLpzpeh08gaxBUTQw1uXIOkr/30Ut2YRjSSxlmHA=="
+    },
+    "d3-shape": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.2.0.tgz",
+      "integrity": "sha1-RdAVOPBkuv0F6j1tLLdI/YxB93c=",
+      "requires": {
+        "d3-path": "1"
+      }
+    },
+    "d3-time": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.8.tgz",
+      "integrity": "sha512-YRZkNhphZh3KcnBfitvF3c6E0JOFGikHZ4YqD+Lzv83ZHn1/u6yGenRU1m+KAk9J1GnZMnKcrtfvSktlA1DXNQ=="
+    },
+    "d3-time-format": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.1.tgz",
+      "integrity": "sha512-8kAkymq2WMfzW7e+s/IUNAtN/y3gZXGRrdGfo6R8NKPAA85UBTxZg5E61bR6nLwjPjj4d3zywSQe1CkYLPFyrw==",
+      "requires": {
+        "d3-time": "1"
+      }
+    },
+    "d3-timer": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.7.tgz",
+      "integrity": "sha512-vMZXR88XujmG/L5oB96NNKH5lCWwiLM/S2HyyAQLcjWJCloK5shxta4CwOFYLZoY3AWX73v8Lgv4cCAdWtRmOA=="
+    },
+    "d3-transition": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.1.1.tgz",
+      "integrity": "sha512-xeg8oggyQ+y5eb4J13iDgKIjUcEfIOZs2BqV/eEmXm2twx80wTzJ4tB4vaZ5BKfz7XsI/DFmQL5me6O27/5ykQ==",
+      "requires": {
+        "d3-color": "1",
+        "d3-dispatch": "1",
+        "d3-ease": "1",
+        "d3-interpolate": "1",
+        "d3-selection": "^1.1.0",
+        "d3-timer": "1"
+      }
+    },
+    "dagre": {
+      "version": "0.7.4",
+      "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.7.4.tgz",
+      "integrity": "sha1-3nLw50pVDOEc5jjwoTb+1xI5gCI=",
+      "requires": {
+        "graphlib": "^1.0.5",
+        "lodash": "^3.10.0"
+      },
+      "dependencies": {
+        "lodash": {
+          "version": "3.10.1",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
+          "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y="
+        }
       }
     },
     "dashdash": {
@@ -2147,7 +2364,7 @@
       "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
       "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
       "requires": {
-        "assert-plus": "1.0.0"
+        "assert-plus": "^1.0.0"
       }
     },
     "date-fns": {
@@ -2193,7 +2410,7 @@
       "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=",
       "dev": true,
       "requires": {
-        "strip-bom": "2.0.0"
+        "strip-bom": "^2.0.0"
       }
     },
     "define-properties": {
@@ -2202,8 +2419,8 @@
       "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=",
       "dev": true,
       "requires": {
-        "foreach": "2.0.5",
-        "object-keys": "1.0.11"
+        "foreach": "^2.0.5",
+        "object-keys": "^1.0.8"
       }
     },
     "defined": {
@@ -2218,12 +2435,12 @@
       "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=",
       "dev": true,
       "requires": {
-        "globby": "6.1.0",
-        "is-path-cwd": "1.0.0",
-        "is-path-in-cwd": "1.0.0",
-        "p-map": "1.2.0",
-        "pify": "3.0.0",
-        "rimraf": "2.6.2"
+        "globby": "^6.1.0",
+        "is-path-cwd": "^1.0.0",
+        "is-path-in-cwd": "^1.0.0",
+        "p-map": "^1.1.1",
+        "pify": "^3.0.0",
+        "rimraf": "^2.2.8"
       },
       "dependencies": {
         "globby": {
@@ -2232,11 +2449,11 @@
           "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
           "dev": true,
           "requires": {
-            "array-union": "1.0.2",
-            "glob": "7.1.2",
-            "object-assign": "4.1.1",
-            "pify": "2.3.0",
-            "pinkie-promise": "2.0.1"
+            "array-union": "^1.0.1",
+            "glob": "^7.0.3",
+            "object-assign": "^4.0.1",
+            "pify": "^2.0.0",
+            "pinkie-promise": "^2.0.0"
           },
           "dependencies": {
             "pify": {
@@ -2282,8 +2499,8 @@
       "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=",
       "dev": true,
       "requires": {
-        "inherits": "2.0.3",
-        "minimalistic-assert": "1.0.0"
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0"
       }
     },
     "destroy": {
@@ -2297,7 +2514,7 @@
       "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=",
       "dev": true,
       "requires": {
-        "repeating": "2.0.1"
+        "repeating": "^2.0.0"
       }
     },
     "detect-node": {
@@ -2324,9 +2541,9 @@
       "integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=",
       "dev": true,
       "requires": {
-        "bn.js": "4.11.8",
-        "miller-rabin": "4.0.1",
-        "randombytes": "2.0.5"
+        "bn.js": "^4.1.0",
+        "miller-rabin": "^4.0.0",
+        "randombytes": "^2.0.0"
       }
     },
     "dir-glob": {
@@ -2335,8 +2552,8 @@
       "integrity": "sha1-CyBdK2rvmCOMooZZioIE0p0KADQ=",
       "dev": true,
       "requires": {
-        "arrify": "1.0.1",
-        "path-type": "3.0.0"
+        "arrify": "^1.0.1",
+        "path-type": "^3.0.0"
       },
       "dependencies": {
         "path-type": {
@@ -2345,7 +2562,7 @@
           "integrity": "sha1-zvMdyOCho7sNEFwM2Xzzv0f0428=",
           "dev": true,
           "requires": {
-            "pify": "3.0.0"
+            "pify": "^3.0.0"
           }
         },
         "pify": {
@@ -2368,8 +2585,8 @@
       "integrity": "sha512-kN+DjfGF7dJGUL7nWRktL9Z18t1rWP3aQlyZdY8XlpvU3Nc6GeFTQApftcjtWKxAZfiggZSGrCEoszNgvnpwDg==",
       "dev": true,
       "requires": {
-        "ip": "1.1.5",
-        "safe-buffer": "5.1.1"
+        "ip": "^1.1.0",
+        "safe-buffer": "^5.0.1"
       }
     },
     "dns-txt": {
@@ -2378,7 +2595,7 @@
       "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=",
       "dev": true,
       "requires": {
-        "buffer-indexof": "1.1.1"
+        "buffer-indexof": "^1.0.0"
       }
     },
     "dom-converter": {
@@ -2387,7 +2604,7 @@
       "integrity": "sha1-pF71cnuJDJv/5tfIduexnLDhfzs=",
       "dev": true,
       "requires": {
-        "utila": "0.3.3"
+        "utila": "~0.3"
       },
       "dependencies": {
         "utila": {
@@ -2404,10 +2621,10 @@
       "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=",
       "dev": true,
       "requires": {
-        "custom-event": "1.0.1",
-        "ent": "2.2.0",
-        "extend": "3.0.1",
-        "void-elements": "2.0.1"
+        "custom-event": "~1.0.0",
+        "ent": "~2.2.0",
+        "extend": "^3.0.0",
+        "void-elements": "^2.0.0"
       }
     },
     "dom-serializer": {
@@ -2416,8 +2633,8 @@
       "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=",
       "dev": true,
       "requires": {
-        "domelementtype": "1.1.3",
-        "entities": "1.1.1"
+        "domelementtype": "~1.1.1",
+        "entities": "~1.1.1"
       },
       "dependencies": {
         "domelementtype": {
@@ -2446,7 +2663,7 @@
       "integrity": "sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=",
       "dev": true,
       "requires": {
-        "domelementtype": "1.3.0"
+        "domelementtype": "1"
       }
     },
     "domutils": {
@@ -2455,8 +2672,8 @@
       "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
       "dev": true,
       "requires": {
-        "dom-serializer": "0.1.0",
-        "domelementtype": "1.3.0"
+        "dom-serializer": "0",
+        "domelementtype": "1"
       }
     },
     "dotenv": {
@@ -2464,6 +2681,23 @@
       "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz",
       "integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0="
     },
+    "dtrace-provider": {
+      "version": "0.8.7",
+      "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.7.tgz",
+      "integrity": "sha1-3JObTT4GIM/gwc2APQ0tftBP/QQ=",
+      "optional": true,
+      "requires": {
+        "nan": "^2.10.0"
+      },
+      "dependencies": {
+        "nan": {
+          "version": "2.10.0",
+          "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
+          "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==",
+          "optional": true
+        }
+      }
+    },
     "duplexer": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
@@ -2476,10 +2710,10 @@
       "integrity": "sha512-j5goxHTwVED1Fpe5hh3q9R93Kip0Bg2KVAt4f8CEYM3UEwYcPSvWbXaUQOzdX/HtiNomipv+gU7ASQPDbV7pGQ==",
       "dev": true,
       "requires": {
-        "end-of-stream": "1.4.0",
-        "inherits": "2.0.3",
-        "readable-stream": "2.3.3",
-        "stream-shift": "1.0.0"
+        "end-of-stream": "^1.0.0",
+        "inherits": "^2.0.1",
+        "readable-stream": "^2.0.0",
+        "stream-shift": "^1.0.0"
       }
     },
     "ecc-jsbn": {
@@ -2488,7 +2722,7 @@
       "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
       "optional": true,
       "requires": {
-        "jsbn": "0.1.1"
+        "jsbn": "~0.1.0"
       }
     },
     "ee-first": {
@@ -2514,13 +2748,13 @@
       "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=",
       "dev": true,
       "requires": {
-        "bn.js": "4.11.8",
-        "brorand": "1.1.0",
-        "hash.js": "1.1.3",
-        "hmac-drbg": "1.0.1",
-        "inherits": "2.0.3",
-        "minimalistic-assert": "1.0.0",
-        "minimalistic-crypto-utils": "1.0.1"
+        "bn.js": "^4.4.0",
+        "brorand": "^1.0.1",
+        "hash.js": "^1.0.0",
+        "hmac-drbg": "^1.0.0",
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0",
+        "minimalistic-crypto-utils": "^1.0.0"
       }
     },
     "ember-cli-string-utils": {
@@ -2529,6 +2763,11 @@
       "integrity": "sha1-ObZ3/CgF9VFzc1N2/O8njqpEUqE=",
       "dev": true
     },
+    "emitter-component": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.1.tgz",
+      "integrity": "sha1-Bl4tvtaVm/RwZ57avq95gdEAOrY="
+    },
     "emojis-list": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
@@ -2546,7 +2785,7 @@
       "integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=",
       "dev": true,
       "requires": {
-        "once": "1.4.0"
+        "once": "^1.4.0"
       }
     },
     "engine.io": {
@@ -2569,7 +2808,7 @@
           "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=",
           "dev": true,
           "requires": {
-            "mime-types": "2.1.17",
+            "mime-types": "~2.1.11",
             "negotiator": "0.6.1"
           }
         },
@@ -2653,10 +2892,10 @@
       "integrity": "sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24=",
       "dev": true,
       "requires": {
-        "graceful-fs": "4.1.11",
-        "memory-fs": "0.4.1",
-        "object-assign": "4.1.1",
-        "tapable": "0.2.8"
+        "graceful-fs": "^4.1.2",
+        "memory-fs": "^0.4.0",
+        "object-assign": "^4.0.1",
+        "tapable": "^0.2.7"
       }
     },
     "ent": {
@@ -2677,7 +2916,7 @@
       "integrity": "sha1-w4bOimKD8U/AlWO3FWCQjJv1MCY=",
       "dev": true,
       "requires": {
-        "prr": "1.0.1"
+        "prr": "~1.0.1"
       }
     },
     "error-ex": {
@@ -2685,7 +2924,7 @@
       "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
       "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=",
       "requires": {
-        "is-arrayish": "0.2.1"
+        "is-arrayish": "^0.2.1"
       }
     },
     "es-abstract": {
@@ -2694,11 +2933,11 @@
       "integrity": "sha1-Hss2wZeEKgDY7kwt/YZGu5fWCGQ=",
       "dev": true,
       "requires": {
-        "es-to-primitive": "1.1.1",
-        "function-bind": "1.1.1",
-        "has": "1.0.1",
-        "is-callable": "1.1.3",
-        "is-regex": "1.0.4"
+        "es-to-primitive": "^1.1.1",
+        "function-bind": "^1.1.1",
+        "has": "^1.0.1",
+        "is-callable": "^1.1.3",
+        "is-regex": "^1.0.4"
       }
     },
     "es-to-primitive": {
@@ -2707,9 +2946,9 @@
       "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=",
       "dev": true,
       "requires": {
-        "is-callable": "1.1.3",
-        "is-date-object": "1.0.1",
-        "is-symbol": "1.0.1"
+        "is-callable": "^1.1.1",
+        "is-date-object": "^1.0.1",
+        "is-symbol": "^1.0.1"
       }
     },
     "es5-ext": {
@@ -2718,8 +2957,8 @@
       "integrity": "sha1-DudB0Ui4AGm6J9AgOTdWryV978M=",
       "dev": true,
       "requires": {
-        "es6-iterator": "2.0.3",
-        "es6-symbol": "3.1.1"
+        "es6-iterator": "~2.0.1",
+        "es6-symbol": "~3.1.1"
       }
     },
     "es6-iterator": {
@@ -2728,9 +2967,9 @@
       "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
       "dev": true,
       "requires": {
-        "d": "1.0.0",
-        "es5-ext": "0.10.37",
-        "es6-symbol": "3.1.1"
+        "d": "1",
+        "es5-ext": "^0.10.35",
+        "es6-symbol": "^3.1.1"
       }
     },
     "es6-map": {
@@ -2739,12 +2978,12 @@
       "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=",
       "dev": true,
       "requires": {
-        "d": "1.0.0",
-        "es5-ext": "0.10.37",
-        "es6-iterator": "2.0.3",
-        "es6-set": "0.1.5",
-        "es6-symbol": "3.1.1",
-        "event-emitter": "0.3.5"
+        "d": "1",
+        "es5-ext": "~0.10.14",
+        "es6-iterator": "~2.0.1",
+        "es6-set": "~0.1.5",
+        "es6-symbol": "~3.1.1",
+        "event-emitter": "~0.3.5"
       }
     },
     "es6-promise": {
@@ -2759,11 +2998,11 @@
       "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=",
       "dev": true,
       "requires": {
-        "d": "1.0.0",
-        "es5-ext": "0.10.37",
-        "es6-iterator": "2.0.3",
+        "d": "1",
+        "es5-ext": "~0.10.14",
+        "es6-iterator": "~2.0.1",
         "es6-symbol": "3.1.1",
-        "event-emitter": "0.3.5"
+        "event-emitter": "~0.3.5"
       }
     },
     "es6-symbol": {
@@ -2772,8 +3011,8 @@
       "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
       "dev": true,
       "requires": {
-        "d": "1.0.0",
-        "es5-ext": "0.10.37"
+        "d": "1",
+        "es5-ext": "~0.10.14"
       }
     },
     "es6-weak-map": {
@@ -2782,10 +3021,10 @@
       "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=",
       "dev": true,
       "requires": {
-        "d": "1.0.0",
-        "es5-ext": "0.10.37",
-        "es6-iterator": "2.0.3",
-        "es6-symbol": "3.1.1"
+        "d": "1",
+        "es5-ext": "^0.10.14",
+        "es6-iterator": "^2.0.1",
+        "es6-symbol": "^3.1.1"
       }
     },
     "escape-html": {
@@ -2804,10 +3043,10 @@
       "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=",
       "dev": true,
       "requires": {
-        "es6-map": "0.1.5",
-        "es6-weak-map": "2.0.2",
-        "esrecurse": "4.2.0",
-        "estraverse": "4.2.0"
+        "es6-map": "^0.1.3",
+        "es6-weak-map": "^2.0.1",
+        "esrecurse": "^4.1.0",
+        "estraverse": "^4.1.1"
       }
     },
     "esprima": {
@@ -2822,8 +3061,8 @@
       "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=",
       "dev": true,
       "requires": {
-        "estraverse": "4.2.0",
-        "object-assign": "4.1.1"
+        "estraverse": "^4.1.0",
+        "object-assign": "^4.0.1"
       }
     },
     "estraverse": {
@@ -2849,8 +3088,8 @@
       "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
       "dev": true,
       "requires": {
-        "d": "1.0.0",
-        "es5-ext": "0.10.37"
+        "d": "1",
+        "es5-ext": "~0.10.14"
       }
     },
     "event-stream": {
@@ -2859,13 +3098,13 @@
       "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=",
       "dev": true,
       "requires": {
-        "duplexer": "0.1.1",
-        "from": "0.1.7",
-        "map-stream": "0.1.0",
+        "duplexer": "~0.1.1",
+        "from": "~0",
+        "map-stream": "~0.1.0",
         "pause-stream": "0.0.11",
-        "split": "0.3.3",
-        "stream-combiner": "0.0.4",
-        "through": "2.3.8"
+        "split": "0.3",
+        "stream-combiner": "~0.0.4",
+        "through": "~2.3.1"
       }
     },
     "eventemitter3": {
@@ -2886,7 +3125,7 @@
       "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=",
       "dev": true,
       "requires": {
-        "original": "1.0.0"
+        "original": ">=0.0.5"
       }
     },
     "evp_bytestokey": {
@@ -2895,8 +3134,8 @@
       "integrity": "sha1-f8vbGY3HGVlDLv4ThCaE4FJaywI=",
       "dev": true,
       "requires": {
-        "md5.js": "1.3.4",
-        "safe-buffer": "5.1.1"
+        "md5.js": "^1.3.4",
+        "safe-buffer": "^5.1.1"
       }
     },
     "execa": {
@@ -2905,13 +3144,13 @@
       "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
       "dev": true,
       "requires": {
-        "cross-spawn": "5.1.0",
-        "get-stream": "3.0.0",
-        "is-stream": "1.1.0",
-        "npm-run-path": "2.0.2",
-        "p-finally": "1.0.0",
-        "signal-exit": "3.0.2",
-        "strip-eof": "1.0.0"
+        "cross-spawn": "^5.0.1",
+        "get-stream": "^3.0.0",
+        "is-stream": "^1.1.0",
+        "npm-run-path": "^2.0.0",
+        "p-finally": "^1.0.0",
+        "signal-exit": "^3.0.0",
+        "strip-eof": "^1.0.0"
       },
       "dependencies": {
         "cross-spawn": {
@@ -2920,9 +3159,9 @@
           "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
           "dev": true,
           "requires": {
-            "lru-cache": "4.1.1",
-            "shebang-command": "1.2.0",
-            "which": "1.3.0"
+            "lru-cache": "^4.0.1",
+            "shebang-command": "^1.2.0",
+            "which": "^1.2.9"
           }
         }
       }
@@ -2939,9 +3178,9 @@
       "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=",
       "dev": true,
       "requires": {
-        "array-slice": "0.2.3",
-        "array-unique": "0.2.1",
-        "braces": "0.1.5"
+        "array-slice": "^0.2.3",
+        "array-unique": "^0.2.1",
+        "braces": "^0.1.2"
       },
       "dependencies": {
         "braces": {
@@ -2950,7 +3189,7 @@
           "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=",
           "dev": true,
           "requires": {
-            "expand-range": "0.1.1"
+            "expand-range": "^0.1.0"
           }
         },
         "expand-range": {
@@ -2959,8 +3198,8 @@
           "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=",
           "dev": true,
           "requires": {
-            "is-number": "0.1.1",
-            "repeat-string": "0.2.2"
+            "is-number": "^0.1.1",
+            "repeat-string": "^0.2.2"
           }
         },
         "is-number": {
@@ -2983,7 +3222,7 @@
       "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=",
       "dev": true,
       "requires": {
-        "is-posix-bracket": "0.1.1"
+        "is-posix-bracket": "^0.1.0"
       }
     },
     "expand-range": {
@@ -2992,7 +3231,7 @@
       "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=",
       "dev": true,
       "requires": {
-        "fill-range": "2.2.3"
+        "fill-range": "^2.1.0"
       }
     },
     "exports-loader": {
@@ -3001,8 +3240,8 @@
       "integrity": "sha1-1w/GEhl1s1/BKDDPUnVL4nQPyIY=",
       "dev": true,
       "requires": {
-        "loader-utils": "1.1.0",
-        "source-map": "0.5.7"
+        "loader-utils": "^1.0.2",
+        "source-map": "0.5.x"
       },
       "dependencies": {
         "source-map": {
@@ -3018,36 +3257,36 @@
       "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz",
       "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=",
       "requires": {
-        "accepts": "1.3.4",
+        "accepts": "~1.3.4",
         "array-flatten": "1.1.1",
         "body-parser": "1.18.2",
         "content-disposition": "0.5.2",
-        "content-type": "1.0.4",
+        "content-type": "~1.0.4",
         "cookie": "0.3.1",
         "cookie-signature": "1.0.6",
         "debug": "2.6.9",
-        "depd": "1.1.1",
-        "encodeurl": "1.0.1",
-        "escape-html": "1.0.3",
-        "etag": "1.8.1",
+        "depd": "~1.1.1",
+        "encodeurl": "~1.0.1",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
         "finalhandler": "1.1.0",
         "fresh": "0.5.2",
         "merge-descriptors": "1.0.1",
-        "methods": "1.1.2",
-        "on-finished": "2.3.0",
-        "parseurl": "1.3.2",
+        "methods": "~1.1.2",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.2",
         "path-to-regexp": "0.1.7",
-        "proxy-addr": "2.0.2",
+        "proxy-addr": "~2.0.2",
         "qs": "6.5.1",
-        "range-parser": "1.2.0",
+        "range-parser": "~1.2.0",
         "safe-buffer": "5.1.1",
         "send": "0.16.1",
         "serve-static": "1.13.1",
         "setprototypeof": "1.1.0",
-        "statuses": "1.3.1",
-        "type-is": "1.6.15",
+        "statuses": "~1.3.1",
+        "type-is": "~1.6.15",
         "utils-merge": "1.0.1",
-        "vary": "1.1.2"
+        "vary": "~1.1.2"
       },
       "dependencies": {
         "setprototypeof": {
@@ -3071,10 +3310,10 @@
         "cookie-signature": "1.0.6",
         "crc": "3.4.4",
         "debug": "2.6.9",
-        "depd": "1.1.1",
-        "on-headers": "1.0.1",
-        "parseurl": "1.3.2",
-        "uid-safe": "2.1.5",
+        "depd": "~1.1.1",
+        "on-headers": "~1.0.1",
+        "parseurl": "~1.3.2",
+        "uid-safe": "~2.1.5",
         "utils-merge": "1.0.1"
       }
     },
@@ -3089,7 +3328,7 @@
       "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=",
       "dev": true,
       "requires": {
-        "is-extglob": "1.0.0"
+        "is-extglob": "^1.0.0"
       },
       "dependencies": {
         "is-extglob": {
@@ -3106,10 +3345,10 @@
       "integrity": "sha1-XwQ+qgL5dQqSWLeMCm4NwUCPsvc=",
       "dev": true,
       "requires": {
-        "async": "2.6.0",
-        "loader-utils": "1.1.0",
-        "schema-utils": "0.3.0",
-        "webpack-sources": "1.1.0"
+        "async": "^2.4.1",
+        "loader-utils": "^1.1.0",
+        "schema-utils": "^0.3.0",
+        "webpack-sources": "^1.0.1"
       }
     },
     "extract-zip": {
@@ -3168,7 +3407,7 @@
       "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=",
       "dev": true,
       "requires": {
-        "websocket-driver": "0.7.0"
+        "websocket-driver": ">=0.5.1"
       }
     },
     "fd-slicer": {
@@ -3177,7 +3416,7 @@
       "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
       "dev": true,
       "requires": {
-        "pend": "1.2.0"
+        "pend": "~1.2.0"
       }
     },
     "file-loader": {
@@ -3186,8 +3425,8 @@
       "integrity": "sha512-RzGHDatcVNpGISTvCpfUfOGpYuSR7HSsSg87ki+wF6rw1Hm0RALPTiAdsxAq1UwLf0RRhbe22/eHK6nhXspiOQ==",
       "dev": true,
       "requires": {
-        "loader-utils": "1.1.0",
-        "schema-utils": "0.3.0"
+        "loader-utils": "^1.0.2",
+        "schema-utils": "^0.3.0"
       }
     },
     "filename-regex": {
@@ -3202,8 +3441,8 @@
       "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=",
       "dev": true,
       "requires": {
-        "glob": "7.1.2",
-        "minimatch": "3.0.4"
+        "glob": "^7.0.3",
+        "minimatch": "^3.0.3"
       }
     },
     "fill-range": {
@@ -3212,11 +3451,11 @@
       "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=",
       "dev": true,
       "requires": {
-        "is-number": "2.1.0",
-        "isobject": "2.1.0",
-        "randomatic": "1.1.7",
-        "repeat-element": "1.1.2",
-        "repeat-string": "1.6.1"
+        "is-number": "^2.1.0",
+        "isobject": "^2.0.0",
+        "randomatic": "^1.1.3",
+        "repeat-element": "^1.1.2",
+        "repeat-string": "^1.5.2"
       },
       "dependencies": {
         "isobject": {
@@ -3236,12 +3475,12 @@
       "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=",
       "requires": {
         "debug": "2.6.9",
-        "encodeurl": "1.0.1",
-        "escape-html": "1.0.3",
-        "on-finished": "2.3.0",
-        "parseurl": "1.3.2",
-        "statuses": "1.3.1",
-        "unpipe": "1.0.0"
+        "encodeurl": "~1.0.1",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.2",
+        "statuses": "~1.3.1",
+        "unpipe": "~1.0.0"
       },
       "dependencies": {
         "statuses": {
@@ -3257,9 +3496,9 @@
       "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=",
       "dev": true,
       "requires": {
-        "commondir": "1.0.1",
-        "make-dir": "1.1.0",
-        "pkg-dir": "2.0.0"
+        "commondir": "^1.0.1",
+        "make-dir": "^1.0.0",
+        "pkg-dir": "^2.0.0"
       }
     },
     "find-up": {
@@ -3267,8 +3506,8 @@
       "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
       "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
       "requires": {
-        "path-exists": "2.1.0",
-        "pinkie-promise": "2.0.1"
+        "path-exists": "^2.0.0",
+        "pinkie-promise": "^2.0.0"
       }
     },
     "flatten": {
@@ -3283,8 +3522,8 @@
       "integrity": "sha1-yBuQ2HRnZvGmCaRoCZRsRd2K5Bc=",
       "dev": true,
       "requires": {
-        "inherits": "2.0.3",
-        "readable-stream": "2.3.3"
+        "inherits": "^2.0.1",
+        "readable-stream": "^2.0.4"
       }
     },
     "for-in": {
@@ -3299,7 +3538,7 @@
       "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=",
       "dev": true,
       "requires": {
-        "for-in": "1.0.2"
+        "for-in": "^1.0.1"
       }
     },
     "foreach": {
@@ -3318,9 +3557,9 @@
       "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz",
       "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=",
       "requires": {
-        "asynckit": "0.4.0",
-        "combined-stream": "1.0.5",
-        "mime-types": "2.1.17"
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.5",
+        "mime-types": "^2.1.12"
       }
     },
     "forwarded": {
@@ -3345,8 +3584,8 @@
       "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
       "dev": true,
       "requires": {
-        "inherits": "2.0.3",
-        "readable-stream": "2.3.3"
+        "inherits": "^2.0.1",
+        "readable-stream": "^2.0.0"
       }
     },
     "fs-access": {
@@ -3355,7 +3594,7 @@
       "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=",
       "dev": true,
       "requires": {
-        "null-check": "1.0.0"
+        "null-check": "^1.0.0"
       }
     },
     "fs-extra": {
@@ -3364,9 +3603,9 @@
       "integrity": "sha1-DYUhIuW8W+tFP7Ao6cDJvzY0DJQ=",
       "dev": true,
       "requires": {
-        "graceful-fs": "4.1.11",
-        "jsonfile": "4.0.0",
-        "universalify": "0.1.1"
+        "graceful-fs": "^4.1.2",
+        "jsonfile": "^4.0.0",
+        "universalify": "^0.1.0"
       }
     },
     "fs-write-stream-atomic": {
@@ -3375,10 +3614,10 @@
       "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
       "dev": true,
       "requires": {
-        "graceful-fs": "4.1.11",
-        "iferr": "0.1.5",
-        "imurmurhash": "0.1.4",
-        "readable-stream": "2.3.3"
+        "graceful-fs": "^4.1.2",
+        "iferr": "^0.1.5",
+        "imurmurhash": "^0.1.4",
+        "readable-stream": "1 || 2"
       }
     },
     "fs.realpath": {
@@ -3393,8 +3632,8 @@
       "dev": true,
       "optional": true,
       "requires": {
-        "nan": "2.8.0",
-        "node-pre-gyp": "0.6.39"
+        "nan": "^2.3.0",
+        "node-pre-gyp": "^0.6.39"
       },
       "dependencies": {
         "abbrev": {
@@ -3409,8 +3648,8 @@
           "dev": true,
           "optional": true,
           "requires": {
-            "co": "4.6.0",
-            "json-stable-stringify": "1.0.1"
+            "co": "^4.6.0",
+            "json-stable-stringify": "^1.0.1"
           }
         },
         "ansi-regex": {
@@ -3430,8 +3669,8 @@
           "dev": true,
           "optional": true,
           "requires": {
-            "delegates": "1.0.0",
-            "readable-stream": "2.2.9"
+            "delegates": "^1.0.0",
+            "readable-stream": "^2.0.6"
           }
         },
         "asn1": {
@@ -3475,7 +3714,7 @@
           "dev": true,
           "optional": true,
           "requires": {
-            "tweetnacl": "0.14.5"
+            "tweetnacl": "^0.14.3"
           }
         },
         "block-stream": {
@@ -3483,7 +3722,7 @@
           "bundled": true,
           "dev": true,
           "requires": {
-            "inherits": "2.0.3"
+            "inherits": "~2.0.0"
           }
         },
         "boom": {
@@ -3491,7 +3730,7 @@
           "bundled": true,
           "dev": true,
           "requires": {
-            "hoek": "2.16.3"
+            "hoek": "2.x.x"
           }
         },
         "brace-expansion": {
@@ -3499,7 +3738,7 @@
           

<TRUNCATED>